From ff1d27ed14109d80a0e25fc879a42e50ac5c4816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=96z?= Date: Mon, 11 Jan 2021 19:08:55 +0100 Subject: [PATCH 1/9] Fixes #34 - Make stricter PMD rule priority (#652) --- gradle-scripts/pmd.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle-scripts/pmd.gradle b/gradle-scripts/pmd.gradle index fe618601d8..fb6736ea88 100644 --- a/gradle-scripts/pmd.gradle +++ b/gradle-scripts/pmd.gradle @@ -1,11 +1,10 @@ /** * PMD tool - * If a rule priority, with a level greater than or equal to 2, is violated; the build should fail. */ pmd { toolVersion = pmdVersion ruleSetConfig = rootProject.resources.text.fromFile('config/pmd/pmd.xml') - rulePriority = 2 + rulePriority = 1 consoleOutput = true } From 091fac8bacd2b40ad46b3d86e47e8e17a0b1a8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=96z?= Date: Mon, 11 Jan 2021 19:09:49 +0100 Subject: [PATCH 2/9] Add generic pagination for the grapqhl request (#649) * Implement the generic version of the grapqhl pagination to be able to add another graphql request. --- .../helpers/GraphQlBaseRequestImpl.java | 86 +++++++++++++++++++ .../helpers/ResourceKeyIdGraphQlRequest.java | 62 +++---------- .../FetchCustomObjectsGraphQlRequest.java | 63 ++++++++++++++ .../commons/models/GraphQlBaseRequest.java | 22 +++++ .../commons/models/GraphQlBaseResource.java | 5 ++ .../commons/models/GraphQlBaseResult.java | 7 ++ .../commons/models/GraphQlQueryResources.java | 3 +- .../sync/commons/models/ResourceKeyId.java | 3 +- .../models/ResourceKeyIdGraphQlResult.java | 2 +- .../sync/commons/utils/CtpQueryUtils.java | 17 ++-- .../sync/commons/utils/GraphQlQueryAll.java | 76 ++++++++-------- .../ResourceKeyIdGraphQlRequestTest.java | 5 +- .../FetchCustomObjectsGraphQlRequestTest.java | 84 ++++++++++++++++++ .../sync/commons/utils/CtpQueryUtilsTest.java | 5 +- .../commons/utils/GraphQlQueryAllTest.java | 4 +- 15 files changed, 335 insertions(+), 109 deletions(-) create mode 100644 src/main/java/com/commercetools/sync/commons/helpers/GraphQlBaseRequestImpl.java create mode 100644 src/main/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequest.java create mode 100644 src/main/java/com/commercetools/sync/commons/models/GraphQlBaseRequest.java create mode 100644 src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResource.java create mode 100644 src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResult.java create mode 100644 src/test/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequestTest.java diff --git a/src/main/java/com/commercetools/sync/commons/helpers/GraphQlBaseRequestImpl.java b/src/main/java/com/commercetools/sync/commons/helpers/GraphQlBaseRequestImpl.java new file mode 100644 index 0000000000..a61f099ab3 --- /dev/null +++ b/src/main/java/com/commercetools/sync/commons/helpers/GraphQlBaseRequestImpl.java @@ -0,0 +1,86 @@ +package com.commercetools.sync.commons.helpers; + +import com.commercetools.sync.commons.models.GraphQlBaseRequest; +import com.commercetools.sync.commons.models.GraphQlBaseResource; +import com.commercetools.sync.commons.models.GraphQlBaseResult; +import com.fasterxml.jackson.databind.JsonNode; +import io.sphere.sdk.client.HttpRequestIntent; +import io.sphere.sdk.http.HttpMethod; +import io.sphere.sdk.http.HttpResponse; +import io.sphere.sdk.json.SphereJsonUtils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static java.lang.String.format; + +public abstract class GraphQlBaseRequestImpl> + implements GraphQlBaseRequest { + + protected String queryPredicate = null; + protected long limit = 500; + + /** + * This method adds a predicate string to the request. + * + * @param predicate - a string representing a query predicate. + * @return - an instance of this class. + */ + @Nonnull + @Override + public GraphQlBaseRequest withPredicate(final String predicate) { + + this.queryPredicate = predicate; + return this; + } + + /** + * This method adds a limit to the request. + * + * @param limit - a number representing the query limit parameter + * @return - an instance of this class + */ + @Nonnull + @Override + public GraphQlBaseRequest withLimit(final long limit) { + + this.limit = limit; + return this; + } + + @Override + public HttpRequestIntent httpRequestIntent() { + + final String body = format("{\"query\": \"{%s}\"}", buildQueryString()); + return HttpRequestIntent.of(HttpMethod.POST, "/graphql", body); + } + + /** + * Deserialize the body of {@param httpResponse}. + * + * @param httpResponse httpResponse of the request. + * @param resourceName resource type in the query (i.e "customers", "products", "customObjects") + * @param clazz the type of the class to deserialize. + * @return the deserialized body of the graphql request. + */ + @Nullable + public T deserializeWithResourceName( + @Nonnull final HttpResponse httpResponse, + @Nonnull final String resourceName, + @Nonnull final Class clazz) { + + final JsonNode rootJsonNode = SphereJsonUtils.parse(httpResponse.getResponseBody()); + if (rootJsonNode.isNull()) { + return null; + } + JsonNode result = rootJsonNode.get("data").get(resourceName); + return SphereJsonUtils.readObject(result, clazz); + } + + /** + * This method builds a string matching the required format needed in the CTP graphql API. + * + * @return a string representing a graphql query + */ + protected abstract String buildQueryString(); +} diff --git a/src/main/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequest.java b/src/main/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequest.java index d921ed9dea..791309fe69 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequest.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequest.java @@ -2,13 +2,9 @@ import com.commercetools.sync.commons.models.GraphQlQueryResources; import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; -import com.fasterxml.jackson.databind.JsonNode; -import io.sphere.sdk.client.HttpRequestIntent; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.client.SphereRequest; -import io.sphere.sdk.http.HttpMethod; import io.sphere.sdk.http.HttpResponse; -import io.sphere.sdk.json.SphereJsonUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -19,16 +15,18 @@ import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.StringUtils.isBlank; -public class ResourceKeyIdGraphQlRequest implements SphereRequest { +/** + * A SphereRequest implementation to allow {@link SphereClient} to execute graphQL queries on CTP. It provides a + * POST request to the CTP graphql API containing body to fetch a set of ids matching given keys of a resource + * defined in endpoint parameter. + */ +public class ResourceKeyIdGraphQlRequest extends GraphQlBaseRequestImpl { protected final Set keysToSearch; protected final GraphQlQueryResources resource; - private long limit = 500; - private String queryPredicate = null; /** - * A SphereRequest implementation to allow {@link SphereClient} to execute graphQL queries on CTP. It provides a - * POST request to the CTP graphql API containing body to fetch a set of ids matching given keys of a resource - * defined in endpoint parameter. + * Takes {@code keysToSearch} and query resource name {@link GraphQlQueryResources} to instantiate a new + * {@link ResourceKeyIdGraphQlRequest} instance, which is an implementation of the {@link SphereRequest}. * * @param keysToSearch - a set of keys to fetch matching ids for. * @param resource - a string representing the name of the resource endpoint. @@ -40,49 +38,10 @@ public ResourceKeyIdGraphQlRequest(@Nonnull final Set keysToSearch, this.resource = resource; } - /** - * This method adds a predicate string to the request. - * - * @param predicate - a string representing a query predicate. - * @return - an instance of this class. - */ - @Nonnull - public ResourceKeyIdGraphQlRequest withPredicate(final String predicate) { - - this.queryPredicate = predicate; - return this; - } - - /** - * This method adds a limit to the request. - * - * @param limit - a number representing the query limit parameter - * @return - an instance of this class - */ - @Nonnull - public ResourceKeyIdGraphQlRequest withLimit(final long limit) { - - this.limit = limit; - return this; - } - @Nullable @Override public ResourceKeyIdGraphQlResult deserialize(final HttpResponse httpResponse) { - - final JsonNode rootJsonNode = SphereJsonUtils.parse(httpResponse.getResponseBody()); - if (rootJsonNode.isNull()) { - return null; - } - JsonNode result = rootJsonNode.get("data").get(resource.getName()); - return SphereJsonUtils.readObject(result, ResourceKeyIdGraphQlResult.class); - } - - @Override - public HttpRequestIntent httpRequestIntent() { - - final String body = format("{\"query\": \"{%s}\"}", buildQueryString()); - return HttpRequestIntent.of(HttpMethod.POST, "/graphql", body); + return deserializeWithResourceName(httpResponse, resource.getName(), ResourceKeyIdGraphQlResult.class); } /** @@ -92,7 +51,8 @@ public HttpRequestIntent httpRequestIntent() { * @return a string representing a graphql query */ @Nonnull - String buildQueryString() { + @Override + protected String buildQueryString() { return format( "%s(limit: %d, where: \\\"%s\\\", sort: [\\\"id asc\\\"]) { results { id key } }", diff --git a/src/main/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequest.java b/src/main/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequest.java new file mode 100644 index 0000000000..c344ae6976 --- /dev/null +++ b/src/main/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequest.java @@ -0,0 +1,63 @@ +package com.commercetools.sync.commons.models; + +import com.commercetools.sync.commons.helpers.GraphQlBaseRequestImpl; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.client.SphereRequest; +import io.sphere.sdk.http.HttpResponse; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.time.Instant; + +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isBlank; + +/** + * A SphereRequest implementation to allow {@link SphereClient} to execute graphQL queries on CTP. It provides a + * POST request to the CTP graphql API containing body to fetch custom object resource with the given container name + * defined in endpoint parameter. + */ +public class FetchCustomObjectsGraphQlRequest extends GraphQlBaseRequestImpl { + + private final String container; + private final Instant lastModifiedAt; + private final String resourceName = GraphQlQueryResources.CUSTOM_OBJECTS.getName(); + + /** + * Takes {@code container} and {@code lastModifiedAt} to instantiate a new {@link FetchCustomObjectsGraphQlRequest} + * instance, which is an implementation of the {@link SphereRequest}. + * + * @param container - A namespace to group custom objects. + * @param lastModifiedAt - lastModifiedAt will be used in where param. + */ + public FetchCustomObjectsGraphQlRequest( + @Nonnull final String container, + @Nonnull final Instant lastModifiedAt) { + + this.container = container; + this.lastModifiedAt = lastModifiedAt; + } + + @Nullable + @Override + public ResourceKeyIdGraphQlResult deserialize(final HttpResponse httpResponse) { + + return deserializeWithResourceName(httpResponse, resourceName, ResourceKeyIdGraphQlResult.class); + } + + @Nonnull + @Override + protected String buildQueryString() { + + return format( + "%s(container: \\\"%s\\\", limit: %d, where: \\\"%s\\\", sort: [\\\"id asc\\\"]) { results { id key } }", + this.resourceName, this.container, this.limit, createWhereQuery()); + } + + @Nonnull + private String createWhereQuery() { + final String whereQuery = format("lastModifiedAt < \\\\\\\"%s\\\\\\\"", lastModifiedAt); + return isBlank(this.queryPredicate) ? whereQuery : format("%s AND %s", whereQuery, queryPredicate); + } +} + diff --git a/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseRequest.java b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseRequest.java new file mode 100644 index 0000000000..ceb6324ecb --- /dev/null +++ b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseRequest.java @@ -0,0 +1,22 @@ +package com.commercetools.sync.commons.models; + +import io.sphere.sdk.client.SphereRequest; + +public interface GraphQlBaseRequest> extends SphereRequest +{ + /** + * This method adds a predicate string to the request. + * + * @param predicate - a string representing a query predicate. + * @return - an instance of this class. + */ + GraphQlBaseRequest withPredicate(final String predicate); + + /** + * This method adds a limit to the request. + * + * @param limit - a number representing the query limit parameter + * @return - an instance of this class + */ + GraphQlBaseRequest withLimit(final long limit); +} diff --git a/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResource.java b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResource.java new file mode 100644 index 0000000000..e5c4216010 --- /dev/null +++ b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResource.java @@ -0,0 +1,5 @@ +package com.commercetools.sync.commons.models; + +public interface GraphQlBaseResource { + String getId(); +} diff --git a/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResult.java b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResult.java new file mode 100644 index 0000000000..08d2c04f1e --- /dev/null +++ b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResult.java @@ -0,0 +1,7 @@ +package com.commercetools.sync.commons.models; + +import java.util.Set; + +public interface GraphQlBaseResult { + Set getResults(); +} diff --git a/src/main/java/com/commercetools/sync/commons/models/GraphQlQueryResources.java b/src/main/java/com/commercetools/sync/commons/models/GraphQlQueryResources.java index 6d4d781b14..ca5a136420 100644 --- a/src/main/java/com/commercetools/sync/commons/models/GraphQlQueryResources.java +++ b/src/main/java/com/commercetools/sync/commons/models/GraphQlQueryResources.java @@ -10,7 +10,8 @@ public enum GraphQlQueryResources { STATES("states"), TAX_CATEGORIES("taxCategories"), TYPES("typeDefinitions"), - SHOPPING_LISTS("shoppingLists"); + SHOPPING_LISTS("shoppingLists"), + CUSTOM_OBJECTS("customObjects"); private final String name; diff --git a/src/main/java/com/commercetools/sync/commons/models/ResourceKeyId.java b/src/main/java/com/commercetools/sync/commons/models/ResourceKeyId.java index b5e4542850..ec1fe9af2e 100644 --- a/src/main/java/com/commercetools/sync/commons/models/ResourceKeyId.java +++ b/src/main/java/com/commercetools/sync/commons/models/ResourceKeyId.java @@ -5,7 +5,7 @@ import javax.annotation.Nonnull; -public final class ResourceKeyId { +public final class ResourceKeyId implements GraphQlBaseResource { private final String key; private final String id; @@ -19,6 +19,7 @@ public String getKey() { return key; } + @Override public String getId() { return id; } diff --git a/src/main/java/com/commercetools/sync/commons/models/ResourceKeyIdGraphQlResult.java b/src/main/java/com/commercetools/sync/commons/models/ResourceKeyIdGraphQlResult.java index c9ad79d575..031ed15dae 100644 --- a/src/main/java/com/commercetools/sync/commons/models/ResourceKeyIdGraphQlResult.java +++ b/src/main/java/com/commercetools/sync/commons/models/ResourceKeyIdGraphQlResult.java @@ -5,7 +5,7 @@ import java.util.Set; -public class ResourceKeyIdGraphQlResult { +public class ResourceKeyIdGraphQlResult implements GraphQlBaseResult { private final Set results; @JsonCreator diff --git a/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java b/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java index 14a3dddde4..6dbf8e3bf3 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java @@ -1,7 +1,8 @@ package com.commercetools.sync.commons.utils; -import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; -import com.commercetools.sync.commons.models.ResourceKeyId; +import com.commercetools.sync.commons.models.GraphQlBaseRequest; +import com.commercetools.sync.commons.models.GraphQlBaseResource; +import com.commercetools.sync.commons.models.GraphQlBaseResult; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.models.ResourceView; import io.sphere.sdk.queries.QueryDsl; @@ -126,15 +127,17 @@ public final class CtpQueryUtils { *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in parallel. * * @param client commercetools client - * @param resourceKeyIdGraphQlRequest graphql query containing predicates and pagination limits + * @param graphQlRequest graphql query containing predicates and pagination limits * @param pageConsumer consumer applied on every page queried * @return a completion stage containing void as a result after the consumer was applied on all pages. */ @Nonnull - public static CompletionStage queryAll(@Nonnull final SphereClient client, - @Nonnull final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest, - @Nonnull final Consumer> pageConsumer) { - GraphQlQueryAll graphQlQueryAll = GraphQlQueryAll.of(client, resourceKeyIdGraphQlRequest, DEFAULT_PAGE_SIZE); + public static , U extends GraphQlBaseResource> CompletionStage + queryAll(@Nonnull final SphereClient client, + @Nonnull final GraphQlBaseRequest graphQlRequest, + @Nonnull final Consumer> pageConsumer) { + + GraphQlQueryAll graphQlQueryAll = GraphQlQueryAll.of(client, graphQlRequest, DEFAULT_PAGE_SIZE); return graphQlQueryAll.run(pageConsumer); } diff --git a/src/main/java/com/commercetools/sync/commons/utils/GraphQlQueryAll.java b/src/main/java/com/commercetools/sync/commons/utils/GraphQlQueryAll.java index 97253f4436..354bc7e936 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/GraphQlQueryAll.java +++ b/src/main/java/com/commercetools/sync/commons/utils/GraphQlQueryAll.java @@ -1,12 +1,11 @@ package com.commercetools.sync.commons.utils; -import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; -import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; -import com.commercetools.sync.commons.models.ResourceKeyId; +import com.commercetools.sync.commons.models.GraphQlBaseRequest; +import com.commercetools.sync.commons.models.GraphQlBaseResource; +import com.commercetools.sync.commons.models.GraphQlBaseResult; import io.sphere.sdk.client.SphereClient; import javax.annotation.Nonnull; -import java.util.Iterator; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; @@ -16,73 +15,67 @@ import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.apache.commons.lang3.StringUtils.isBlank; -final class GraphQlQueryAll { +final class GraphQlQueryAll, U extends GraphQlBaseResource> { private final SphereClient client; - private final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest; + private final GraphQlBaseRequest graphqlRequest; private final long pageSize; - private Consumer> pageConsumer; + private Consumer> pageConsumer; private GraphQlQueryAll(@Nonnull final SphereClient client, - @Nonnull final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest, + @Nonnull final GraphQlBaseRequest graphqlRequest, final long pageSize) { this.client = client; - this.resourceKeyIdGraphQlRequest = withDefaults(resourceKeyIdGraphQlRequest, pageSize); + this.graphqlRequest = graphqlRequest.withLimit(pageSize); this.pageSize = pageSize; } @Nonnull - private static ResourceKeyIdGraphQlRequest withDefaults( - @Nonnull final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest, - final long pageSize) { + static , U extends GraphQlBaseResource> GraphQlQueryAll of( + @Nonnull final SphereClient client, + @Nonnull final GraphQlBaseRequest graphqlRequest, + final int pageSize) { - return resourceKeyIdGraphQlRequest.withLimit(pageSize); - } - - @Nonnull - static GraphQlQueryAll of(@Nonnull final SphereClient client, - @Nonnull final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest, - final int pageSize) { - - return new GraphQlQueryAll(client, resourceKeyIdGraphQlRequest, pageSize); + return new GraphQlQueryAll<>(client, graphqlRequest, pageSize); } /** - * Given a {@link Consumer} to a page of results of type {@code BaseGraphQlResult}, this method sets this instance's + * Given a {@link Consumer} to a page of results of type {@link T}, this method sets this instance's * {@code pageConsumer} to the supplied value, then it makes requests to fetch the entire result space of a - * graphql query request {@code BaseGraphQlRequest} to CTP, while accepting the consumer on each fetched page. + * graphql query request {@link GraphQlBaseRequest} to CTP, while accepting the consumer on each fetched + * page. * * @param pageConsumer the consumer to accept on each fetched page of the result space. * @return a future containing void after the consumer accepted all the pages. */ @Nonnull - CompletionStage run(@Nonnull final Consumer> pageConsumer) { + CompletionStage run(@Nonnull final Consumer> pageConsumer) { this.pageConsumer = pageConsumer; - final CompletionStage firstPage = client.execute(resourceKeyIdGraphQlRequest); + final CompletionStage firstPage = client.execute(graphqlRequest); return queryNextPages(firstPage); } /** * Given a completion stage {@code currentPageStage} containing a current graphql result - * {@link ResourceKeyIdGraphQlResult}, this method composes the completion stage by first checking if the result + * {@link GraphQlBaseRequest}, this method composes the completion stage by first checking if the result * is null or not. If it is not, then it recursively (by calling itself with the next page's completion stage * result) composes to the supplied stage, stages of the all next pages' processing. If there is no next page, * then the result of the {@code currentPageStage} would be null and this method would just return a completed * future containing null result, which in turn signals the last page of processing. * - * @param currentPageStage a future containing a graphql result {@link ResourceKeyIdGraphQlResult}. + * @param currentPageStage a future containing a graphql result {@link GraphQlBaseRequest}. */ @Nonnull private CompletionStage queryNextPages( - @Nonnull final CompletionStage currentPageStage) { + @Nonnull final CompletionStage currentPageStage) { return currentPageStage.thenCompose(currentPage -> currentPage != null ? queryNextPages(processPageAndGetNext(currentPage)) : completedFuture(null)); } /** - * Given a graphql query result representing a page {@link ResourceKeyIdGraphQlResult}, this method checks if there + * Given a graphql query result representing a page {@link GraphQlBaseRequest}, this method checks if there * are elements in the result (size > 0), then it consumes the resultant list using this instance's {@code * pageConsumer}. Then it attempts to fetch the next page if it exists and returns a completion stage * containing the result of the next page. If there is a next page, then a new future of the next page is returned. @@ -93,9 +86,9 @@ private CompletionStage queryNextPages( * the method returns a completed future containing null. */ @Nonnull - private CompletionStage processPageAndGetNext( - @Nonnull final ResourceKeyIdGraphQlResult page) { - final Set currentPageElements = page.getResults(); + private CompletionStage processPageAndGetNext( + @Nonnull final T page) { + final Set currentPageElements = page.getResults(); if (!currentPageElements.isEmpty()) { consumePageElements(currentPageElements); return getNextPageStage(currentPageElements); @@ -103,35 +96,34 @@ private CompletionStage processPageAndGetNext( return completedFuture(null); } - private void consumePageElements(@Nonnull final Set pageElements) { + private void consumePageElements(@Nonnull final Set pageElements) { pageConsumer.accept(pageElements); } /** - * Given a list of page elements of type {@code ResourceKeyId}, this method checks if this page is the last page or - * not by checking if the result size is equal to this instance's {@code pageSize}). If it is, then it means + * Given a list of page elements of type {@link U}, this method checks if this page is the last + * page or not by checking if the result size is equal to this instance's {@code pageSize}). If it is, then it means * there might be still more results. However, if not, then it means for sure there are no more results and this * is the last page. If there is a next page, the id of the last element in the list is used and a future is * created containing the fetched results which have an id greater than the id of the last element in the list * and this future is returned. If there are no more results, the method returns a completed future containing null. * - * @param pageElements set of page elements of type {@code ResourceKeyId}. + * @param pageElements set of page elements of type {@link U}. * @return a future containing the fetched results which have an id greater than the id of the last element * in the set. */ @Nonnull - private CompletionStage getNextPageStage( - @Nonnull final Set pageElements) { + private CompletionStage getNextPageStage( + @Nonnull final Set pageElements) { if (pageElements.size() == pageSize) { String lastElementId = EMPTY; - Iterator iterator = pageElements.iterator(); - while (iterator.hasNext()) { - lastElementId = iterator.next().getId(); + for (U pageElement : pageElements) { + lastElementId = pageElement.getId(); } final String queryPredicate = isBlank(lastElementId) ? null : format("id > \\\\\\\"%s\\\\\\\"", lastElementId); - return client.execute(resourceKeyIdGraphQlRequest.withPredicate(queryPredicate)); + return client.execute(graphqlRequest.withPredicate(queryPredicate)); } return completedFuture(null); } diff --git a/src/test/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequestTest.java b/src/test/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequestTest.java index 33256a8fae..eb32fdb7c2 100644 --- a/src/test/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequestTest.java +++ b/src/test/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequestTest.java @@ -1,5 +1,6 @@ package com.commercetools.sync.commons.helpers; +import com.commercetools.sync.commons.models.GraphQlBaseRequest; import com.commercetools.sync.commons.models.GraphQlQueryResources; import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; import com.fasterxml.jackson.core.JsonProcessingException; @@ -97,7 +98,7 @@ void httpRequestIntent_WithSomeEmptyAndNullKeys_ShouldReturnCorrectQueryString() @Test void httpRequestIntent_WithKeyAndExplicitLimit_ShouldReturnCorrectQueryString() { //preparation - final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + final GraphQlBaseRequest resourceKeyIdGraphQlRequest = new ResourceKeyIdGraphQlRequest(singleton("key1"), GraphQlQueryResources.CATEGORIES).withLimit(10); //test @@ -117,7 +118,7 @@ void httpRequestIntent_WithKeyAndExplicitLimit_ShouldReturnCorrectQueryString() @Test void httpRequestIntent_WithKeyAndPredicate_ShouldReturnCorrectQueryString() { //preparation - final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + final GraphQlBaseRequest resourceKeyIdGraphQlRequest = new ResourceKeyIdGraphQlRequest(singleton("key1"), GraphQlQueryResources.CATEGORIES) .withPredicate("id > \\\\\\\"id" + "\\\\\\\""); diff --git a/src/test/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequestTest.java b/src/test/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequestTest.java new file mode 100644 index 0000000000..5d11d6f857 --- /dev/null +++ b/src/test/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequestTest.java @@ -0,0 +1,84 @@ +package com.commercetools.sync.commons.models; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.sphere.sdk.client.HttpRequestIntent; +import io.sphere.sdk.http.HttpMethod; +import io.sphere.sdk.http.HttpResponse; +import io.sphere.sdk.http.StringHttpRequestBody; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static org.assertj.core.api.Assertions.assertThat; + +class FetchCustomObjectsGraphQlRequestTest { + + @Test + void httpRequestIntent_WithoutPredicate_ShouldReturnCorrectQueryString() { + //preparation + final Instant lastModifiedAt = Instant.parse("2021-01-07T00:00:00Z"); + final FetchCustomObjectsGraphQlRequest request = + new FetchCustomObjectsGraphQlRequest("container", lastModifiedAt); + + //test + final HttpRequestIntent httpRequestIntent = request.httpRequestIntent(); + + //assertions + assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); + assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); + final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); + assertThat(requestBody).isNotNull(); + assertThat(requestBody.getString()) + .isEqualTo("{\"query\": \"{customObjects(container: \\\"container\\\", limit: 500, " + + "where: \\\"lastModifiedAt < \\\\\\\"2021-01-07T00:00:00Z\\\\\\\"\\\", " + + "sort: [\\\"id asc\\\"]) { results { id key } }}\"}"); + } + + @Test + void httpRequestIntent_WithPredicate_ShouldReturnCorrectQueryString() { + //preparation + final Instant lastModifiedAt = Instant.parse("2021-01-07T00:00:00Z"); + final GraphQlBaseRequest request = + new FetchCustomObjectsGraphQlRequest("container", lastModifiedAt) + .withPredicate("id > \\\\\\\"id\\\\\\\""); + + //test + final HttpRequestIntent httpRequestIntent = request.httpRequestIntent(); + + //assertions + assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); + assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); + final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); + assertThat(requestBody).isNotNull(); + assertThat(requestBody.getString()) + .isEqualTo("{\"query\": \"{customObjects(container: \\\"container\\\", limit: 500, " + + "where: \\\"lastModifiedAt < \\\\\\\"2021-01-07T00:00:00Z\\\\\\\"" + + " AND id > \\\\\\\"id\\\\\\\"\\\", sort: [\\\"id asc\\\"])" + + " { results { id key } }}\"}"); + } + + @Test + void deserialize_WithMultipleResults_ShouldReturnCorrectResult() throws JsonProcessingException { + //preparation + String jsonAsString = "{\"data\":{\"customObjects\":{\"results\":[{\"id\":\"id-1\",\"key\":\"key-1\"}," + + "{\"id\":\"id-2\",\"key\":\"key-2\"},{\"id\":\"id-3\",\"key\":\"key-3\"}]}}}"; + + final HttpResponse httpResponse = HttpResponse.of(200, jsonAsString); + + final FetchCustomObjectsGraphQlRequest request = + new FetchCustomObjectsGraphQlRequest("containerName", Instant.now()); + + //test + final ResourceKeyIdGraphQlResult result = request.deserialize(httpResponse); + + //assertions + assertThat(result).isNotNull(); + assertThat(result.getResults()).hasSize(3); + assertThat(result.getResults()) + .extracting("key") + .containsExactlyInAnyOrder("key-1", "key-2", "key-3"); + assertThat(result.getResults()) + .extracting("id") + .containsExactlyInAnyOrder("id-1", "id-2", "id-3"); + } +} diff --git a/src/test/java/com/commercetools/sync/commons/utils/CtpQueryUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CtpQueryUtilsTest.java index 48f8999ee5..325698b41c 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CtpQueryUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CtpQueryUtilsTest.java @@ -1,6 +1,7 @@ package com.commercetools.sync.commons.utils; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; +import com.commercetools.sync.commons.models.GraphQlBaseRequest; import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; import com.commercetools.sync.commons.models.GraphQlQueryResources; import com.commercetools.sync.commons.models.ResourceKeyId; @@ -43,7 +44,7 @@ class CtpQueryUtilsTest { @Captor private ArgumentCaptor sphereRequestArgumentCaptor; @Captor - private ArgumentCaptor graphQlRequestArgumentCaptor; + private ArgumentCaptor> graphQlRequestArgumentCaptor; @BeforeEach void init() { @@ -209,4 +210,4 @@ private CategoryQuery getSecondPageQuery( .withLimit(500) .withFetchTotal(false); } -} \ No newline at end of file +} diff --git a/src/test/java/com/commercetools/sync/commons/utils/GraphQlQueryAllTest.java b/src/test/java/com/commercetools/sync/commons/utils/GraphQlQueryAllTest.java index cf88c5f515..55fc0a026e 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/GraphQlQueryAllTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/GraphQlQueryAllTest.java @@ -47,7 +47,7 @@ void run_WithConsumer_ShouldApplyConsumer() { results.add(resource4); when(pagedGraphQlQueryResult.getResults()).thenReturn(results); - final GraphQlQueryAll query = + final GraphQlQueryAll query = GraphQlQueryAll.of(sphereClient, mock(ResourceKeyIdGraphQlRequest.class), DEFAULT_PAGE_SIZE); final List resourceIds = new ArrayList<>(); @@ -67,7 +67,7 @@ void run_WithEmptyResults_ShouldSkipConsumer() { //preparation when(pagedGraphQlQueryResult.getResults()).thenReturn(emptySet()); - final GraphQlQueryAll query = + final GraphQlQueryAll query = GraphQlQueryAll.of(sphereClient, mock(ResourceKeyIdGraphQlRequest.class), DEFAULT_PAGE_SIZE); final List resourceIds = new ArrayList<>(); From e72c291856246a09176dacc5af997b6ebbb2de46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=96z?= Date: Mon, 11 Jan 2021 19:15:36 +0100 Subject: [PATCH 3/9] Add clean up implementation for the outdated pending reference resolution custom objects. (#651) * Use grapqhl for fetching custom objects. --- docs/RELEASE_NOTES.md | 11 + docs/usage/CLEANUP_GUIDE.md | 26 +++ docs/usage/PRODUCT_SYNC.md | 1 + docs/usage/STATE_SYNC.md | 2 + mkdocs.yml | 1 + ...nupUnresolvedReferenceCustomObjectsIT.java | 108 +++++++++ .../UnresolvedReferencesServiceImplIT.java | 3 +- .../UnresolvedTransitionsServiceImplIT.java | 3 +- ...eanupUnresolvedReferenceCustomObjects.java | 216 ++++++++++++++++++ .../sync/commons/utils/CtpQueryUtils.java | 28 ++- .../impl/UnresolvedReferencesServiceImpl.java | 2 +- .../UnresolvedTransitionsServiceImpl.java | 2 +- ...pUnresolvedReferenceCustomObjectsTest.java | 106 +++++++++ 13 files changed, 501 insertions(+), 8 deletions(-) create mode 100644 docs/usage/CLEANUP_GUIDE.md create mode 100644 src/integration-test/java/com/commercetools/sync/integration/commons/CleanupUnresolvedReferenceCustomObjectsIT.java create mode 100644 src/main/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjects.java create mode 100644 src/test/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjectsTest.java diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index 0522b8a792..7bd8170883 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -26,6 +26,17 @@ 7. Add Migration guide section which specifies explicitly if there are breaking changes and how to tackle them. +### 3.1.0 - Jan XX, 2021 +[Commits](https://github.com/commercetools/commercetools-sync-java/compare/3.0.2...3.1.0) | +[Javadoc](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/) | +[Jar](https://bintray.com/commercetools/maven/commercetools-sync-java/3.1.0) + +- 🎉 **New Features** (1) + - Added clean up implementation for the outdated pending reference resolution custom objects. [#650](https://github.com/commercetools/commercetools-sync-java/issues/650) + +- ✨ **Documentation** (1) + - Documentation added for the cleaning up of the unresolved references, check the [cleanup guide](https://github.com/commercetools/commercetools-sync-java/blob/master/docs/usage/CLEANUP_GUIDE.md) for more details. + --> ### 3.0.2 - Dec 16, 2020 diff --git a/docs/usage/CLEANUP_GUIDE.md b/docs/usage/CLEANUP_GUIDE.md new file mode 100644 index 0000000000..8507c27b88 --- /dev/null +++ b/docs/usage/CLEANUP_GUIDE.md @@ -0,0 +1,26 @@ +# Cleanup of old unresolved reference custom objects + +For some references such as state transitions and attribute references with product references, the library keeps tracks of unresolved drafts and persists them in a commercetools custom object and create/update them accordingly whenever the referenced drafts exist in the target project. + +It also means that created data might be never resolved, in this case keeping the old custom objects around forever can negatively influence the performance of your project and the time it takes to restore it from a backup. So deleting unused data ensures the best performance for your project. + +For the cleanup, commercetools-sync-java library exposes a ready to use method so that all custom objects created by the library can be cleaned up on demand. + +The cleanup can be defined with the parameter `deleteDaysAfterLastModification` (in days) object creation. For example, if you configure the `deleteDaysAfterLastModification` parameter to 30 days, cleanup method will delete the custom objects which haven't been modified in the last 30 days. +Additionally, an error callback could be configured to consume the error, that is called `errorCallback` whenever an error event occurs during the sync process. + +````java +final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = + CleanupUnresolvedReferenceCustomObjects.of(sphereClient) + .errorCallback(throwable -> logger.error("Unexcepted error.", throwable)) + .cleanup(30) + .join(); +```` + +The result of the previous code snippet is a `Statistics` object that contains all the statistics of the cleanup process. That includes a report message, the total number of deleted and failed custom objects. + +````java +statistics.getReportMessage(); // Summary: 100 custom objects were deleted in total (1 failed to delete). +statistics.getTotalDeleted(); // Returns 100 +statistics.getTotalFailed(); // Returns 1 +```` diff --git a/docs/usage/PRODUCT_SYNC.md b/docs/usage/PRODUCT_SYNC.md index 3e49537ac2..b4b61892e3 100644 --- a/docs/usage/PRODUCT_SYNC.md +++ b/docs/usage/PRODUCT_SYNC.md @@ -389,6 +389,7 @@ It being persisted as `CustomObject` means that the referenced productDrafts wit As soon, as the referenced productDrafts are supplied to the sync, the draft will be created/updated and the `CustomObject` will be removed from the target project. +Keeping the old custom objects around forever can negatively influence the performance of your project and the time it takes to restore it from a backup. Deleting unused data ensures the best performance for your project. Please have a look into the [Cleanup guide](CLEANUP_GUIDE.md) to cleanup old unresolved custom objects. ##### More examples of how to use the sync diff --git a/docs/usage/STATE_SYNC.md b/docs/usage/STATE_SYNC.md index 5ee3ff5abe..e0c6ab7e31 100644 --- a/docs/usage/STATE_SYNC.md +++ b/docs/usage/STATE_SYNC.md @@ -266,6 +266,8 @@ It being persisted as `CustomObject` means that the referenced StateDrafts with As soon, as the referenced StateDrafts are supplied to the sync, the draft will be created/updated and the `CustomObject` will be removed from the target project. +Keeping the old custom objects around forever can negatively influence the performance of your project and the time it takes to restore it from a backup. Deleting unused data ensures the best performance for your project. Please have a look into the [Cleanup guide](CLEANUP_GUIDE.md) to cleanup old unresolved custom objects. + #### More examples of how to use the sync 1. [Sync usages](https://github.com/commercetools/commercetools-sync-java/tree/master/src/integration-test/java/com/commercetools/sync/integration/externalsource/states/StateSyncIT.java). diff --git a/mkdocs.yml b/mkdocs.yml index 321977f52b..25b533f57b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -61,6 +61,7 @@ nav: - Advanced: - Sync Options: usage/SYNC_OPTIONS.md - Usage Tips: usage/IMPORTANT_USAGE_TIPS.md + - Cleanup: usage/CLEANUP_GUIDE.md - Javadoc: https://commercetools.github.io/commercetools-sync-java/v/3.0.2/ - Release notes: RELEASE_NOTES.md - Roadmap: https://github.com/commercetools/commercetools-sync-java/milestones diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/CleanupUnresolvedReferenceCustomObjectsIT.java b/src/integration-test/java/com/commercetools/sync/integration/commons/CleanupUnresolvedReferenceCustomObjectsIT.java new file mode 100644 index 0000000000..bc737a9ee4 --- /dev/null +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/CleanupUnresolvedReferenceCustomObjectsIT.java @@ -0,0 +1,108 @@ +package com.commercetools.sync.integration.commons; + +import com.commercetools.sync.commons.CleanupUnresolvedReferenceCustomObjects; +import com.commercetools.sync.commons.models.WaitingToBeResolved; +import com.commercetools.sync.commons.models.WaitingToBeResolvedTransitions; +import com.commercetools.sync.products.ProductSyncOptionsBuilder; +import com.commercetools.sync.services.UnresolvedReferencesService; +import com.commercetools.sync.services.UnresolvedTransitionsService; +import com.commercetools.sync.services.impl.UnresolvedReferencesServiceImpl; +import com.commercetools.sync.services.impl.UnresolvedTransitionsServiceImpl; +import com.commercetools.sync.states.StateSyncOptionsBuilder; +import io.sphere.sdk.json.SphereJsonUtils; +import io.sphere.sdk.models.Reference; +import io.sphere.sdk.products.ProductDraft; +import io.sphere.sdk.products.ProductDraftBuilder; +import io.sphere.sdk.states.State; +import io.sphere.sdk.states.StateDraftBuilder; +import io.sphere.sdk.states.StateType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedCustomObjects; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedTransitionsCustomObjects; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.services.impl.UnresolvedTransitionsServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + +class CleanupUnresolvedReferenceCustomObjectsIT { + private UnresolvedTransitionsService unresolvedTransitionsService; + private UnresolvedReferencesService unresolvedReferencesService; + + @BeforeEach + void setupTest() { + deleteWaitingToBeResolvedTransitionsCustomObjects(CTP_TARGET_CLIENT, CUSTOM_OBJECT_CONTAINER_KEY); + deleteWaitingToBeResolvedCustomObjects(CTP_TARGET_CLIENT); + + unresolvedReferencesService = new UnresolvedReferencesServiceImpl( + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build()); + + unresolvedTransitionsService = new UnresolvedTransitionsServiceImpl( + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build()); + } + + @Test + void cleanup_withDeleteDaysAfterLastModification_ShouldDeleteAndReturnCleanupStatistics() { + createSampleUnresolvedReferences(); + + final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = + CleanupUnresolvedReferenceCustomObjects.of(CTP_TARGET_CLIENT) + .pageSize(3) // for testing purpose, ensures the pagination works. + .cleanup(-1) // to be able to test it. + .join(); + + assertThat(statistics.getTotalDeleted()).isEqualTo(10); + assertThat(statistics.getTotalFailed()).isEqualTo(0); + assertThat(statistics.getReportMessage()) + .isEqualTo("Summary: 10 custom objects were deleted in total (0 failed to delete)."); + } + + void createSampleUnresolvedReferences() { + final ProductDraft sampleProductDraft = + SphereJsonUtils.readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, ProductDraft.class); + + final Set> sampleTransitions = new HashSet<>( + Arrays.asList(State.referenceOfId("id1"), State.referenceOfId("id2"))); + + final List productUnresolvedReferences = new ArrayList<>(); + final List transitionUnresolvedReferences = new ArrayList<>(); + + for (int i = 1; i <= 5; i++) { + productUnresolvedReferences.add(new WaitingToBeResolved( + ProductDraftBuilder.of(sampleProductDraft).key(format("productKey%s", i)).build(), + asSet("foo", "bar"))); + + transitionUnresolvedReferences.add(new WaitingToBeResolvedTransitions( + StateDraftBuilder.of(format("stateKeys%s", i), StateType.LINE_ITEM_STATE) + .transitions(sampleTransitions) + .build(), + asSet("foo", "bar"))); + } + + CompletableFuture.allOf( + CompletableFuture.allOf( + productUnresolvedReferences + .stream() + .map(unresolvedReferencesService::save) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)), + CompletableFuture.allOf( + transitionUnresolvedReferences + .stream() + .map(unresolvedTransitionsService::save) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + ).join(); + } +} diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedReferencesServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedReferencesServiceImplIT.java index 3f32e85e0e..9154645d9f 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedReferencesServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedReferencesServiceImplIT.java @@ -22,6 +22,7 @@ import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_SPECIAL_CHARS_RESOURCE_PATH; +import static com.commercetools.sync.services.impl.UnresolvedReferencesServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY; import static io.sphere.sdk.utils.SphereInternalUtils.asSet; import static java.util.Collections.singleton; import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; @@ -29,8 +30,6 @@ class UnresolvedReferencesServiceImplIT { - private static final String CUSTOM_OBJECT_CONTAINER_KEY = - "commercetools-sync-java.UnresolvedReferencesService.productDrafts"; private UnresolvedReferencesService unresolvedReferencesService; private List errorCallBackMessages; private List warningCallBackMessages; diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedTransitionsServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedTransitionsServiceImplIT.java index ad56be5784..1c30773e1c 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedTransitionsServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedTransitionsServiceImplIT.java @@ -25,6 +25,7 @@ import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedTransitionsCustomObjects; import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.services.impl.UnresolvedTransitionsServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY; import static io.sphere.sdk.utils.SphereInternalUtils.asSet; import static java.util.Collections.singleton; import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; @@ -32,8 +33,6 @@ class UnresolvedTransitionsServiceImplIT { - private static final String CUSTOM_OBJECT_CONTAINER_KEY = - "commercetools-sync-java.UnresolvedTransitionsService.stateDrafts"; private UnresolvedTransitionsService unresolvedTransitionsService; private List errorCallBackMessages; private List warningCallBackMessages; diff --git a/src/main/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjects.java b/src/main/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjects.java new file mode 100644 index 0000000000..2bfcfbbac0 --- /dev/null +++ b/src/main/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjects.java @@ -0,0 +1,216 @@ +package com.commercetools.sync.commons; + +import com.commercetools.sync.commons.models.FetchCustomObjectsGraphQlRequest; +import com.commercetools.sync.commons.models.ResourceKeyId; +import com.commercetools.sync.services.impl.UnresolvedReferencesServiceImpl; +import com.commercetools.sync.services.impl.UnresolvedTransitionsServiceImpl; +import com.fasterxml.jackson.databind.JsonNode; +import io.sphere.sdk.client.NotFoundException; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.customobjects.CustomObject; +import io.sphere.sdk.customobjects.commands.CustomObjectDeleteCommand; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import static com.commercetools.sync.commons.utils.CtpQueryUtils.queryAll; +import static java.lang.String.format; + +public class CleanupUnresolvedReferenceCustomObjects { + + private final SphereClient sphereClient; + private final Statistics statistics; + private int pageSize = 500; + private Consumer errorCallback; + + private CleanupUnresolvedReferenceCustomObjects(@Nonnull final SphereClient sphereClient) { + this.sphereClient = sphereClient; + this.statistics = new Statistics(); + } + + /** + * Creates new instance of {@link CleanupUnresolvedReferenceCustomObjects} which has the functionality to run + * cleanup helpers. + * + * @param sphereClient the client object. + * @return new instance of {@link CleanupUnresolvedReferenceCustomObjects} + */ + public static CleanupUnresolvedReferenceCustomObjects of(@Nonnull final SphereClient sphereClient) { + return new CleanupUnresolvedReferenceCustomObjects(sphereClient); + } + + /** + * Sets the {@code errorCallback} function of the cleanup. This callback will be called whenever an event occurs + * that leads to an error alert from the cleanup process. + * + * @param errorCallback the new value to set to the error callback. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public CleanupUnresolvedReferenceCustomObjects errorCallback(@Nonnull final Consumer errorCallback) { + this.errorCallback = errorCallback; + return this; + } + + /** + * Given an {@code exception}, this method calls the {@code errorCallback} function. + * If {@code errorCallback} is null, this method does nothing. + * + * @param exception {@link Throwable} instance to supply as first param to the {@code errorCallback} + * function. + */ + private void applyErrorCallback(@Nonnull final Throwable exception) { + if (this.errorCallback != null) { + this.errorCallback.accept(exception); + } + } + + /** + * Configures the pageSize (limit), the maximum number of results to return from the grapqhl query, + * which can be set using the limit query parameter. The default page size is 500. + * + *

NOTE: Changing this value might negatively impact the performance of the cleanup and must be tested properly. + * + * @param pageSize int that indicates batch size of resources to process. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public CleanupUnresolvedReferenceCustomObjects pageSize(final int pageSize) { + this.pageSize = pageSize; + return this; + } + + /** + * Deletes the unresolved reference custom objects persisted by commercetools-sync-java library to + * handle reference resolution. The custom objects will be deleted if it hasn't been modified for the specified + * amount of days as given {@code deleteDaysAfterLastModification}. + * + *

Note: Keeping the unresolved references forever can negatively influence the performance of your project, + * so deleting unused data ensures the best performance for your project. + * + * @param deleteDaysAfterLastModification Days to query. The custom objects will be deleted if it hasn't been + * modified for the specified amount of days. + * @return an instance of {@link CompletableFuture}<{@link CleanupUnresolvedReferenceCustomObjects.Statistics} + * > which contains the processing time, the total number of custom objects that were deleted and failed + * to delete, and a proper summary message of the statistics. + */ + public CompletableFuture cleanup(final int deleteDaysAfterLastModification) { + + return CompletableFuture + .allOf(cleanupUnresolvedProductReferences(deleteDaysAfterLastModification), + cleanupUnresolvedStateReferences(deleteDaysAfterLastModification)) + .thenApply(ignoredResult -> statistics); + } + + private CompletableFuture cleanup( + @Nonnull final String containerName, + final int deleteDaysAfterLastModification) { + final Consumer> pageConsumer = resourceKeyIds -> + deleteCustomObjects(containerName, resourceKeyIds); + + return queryAll(sphereClient, + getRequest(containerName, deleteDaysAfterLastModification), pageConsumer, pageSize) + .toCompletableFuture(); + } + + private CompletableFuture cleanupUnresolvedProductReferences(final int deleteDaysAfterLastModification) { + return cleanup(UnresolvedReferencesServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY, + deleteDaysAfterLastModification); + } + + private CompletableFuture cleanupUnresolvedStateReferences(final int deleteDaysAfterLastModification) { + return cleanup(UnresolvedTransitionsServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY, + deleteDaysAfterLastModification); + } + + /** + * Prepares a graphql request to fetch the unresolved reference custom objects based on the given + * {@code containerName} and {@code deleteDaysAfterLastModification}. + * + * @param containerName container name (i.e "commercetools-sync-java.UnresolvedReferencesService.productDrafts") + * @param deleteDaysAfterLastModification Days to query lastModifiedAt. The custom objects will be deleted if + * it hasn't been modified for the specified amount of days. + */ + private FetchCustomObjectsGraphQlRequest getRequest( + @Nonnull final String containerName, + final int deleteDaysAfterLastModification) { + + final Instant lastModifiedAt = Instant.now().minus(deleteDaysAfterLastModification, ChronoUnit.DAYS); + return new FetchCustomObjectsGraphQlRequest(containerName, lastModifiedAt); + } + + /** + * Deletes all custom objects in the given {@link List} representing a page of custom object's key and ids. + * + *

Note: The deletion is done concurrently but it's blocked by page consumer to avoid race conditions like + * fetching and removing same custom objects in same time.

+ * + * @param resourceKeyIdSet a page of custom object's key and ids. + */ + private void deleteCustomObjects( + @Nonnull final String containerName, + @Nonnull final Set resourceKeyIdSet) { + + CompletableFuture.allOf( + resourceKeyIdSet + .stream() + .map(resourceKeyId -> executeDeletion(containerName, resourceKeyId)) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)).join(); + } + + private CompletionStage>> executeDeletion( + @Nonnull final String containerName, + @Nonnull final ResourceKeyId resourceKeyId) { + + return sphereClient.execute( + CustomObjectDeleteCommand.of(containerName, resourceKeyId.getKey(), JsonNode.class)) + .handle(this::handleDeleteCallback); + } + + private Optional> handleDeleteCallback( + @Nonnull final CustomObject resource, + @Nullable final Throwable throwable) { + + if (throwable == null) { + statistics.totalDeleted.incrementAndGet(); + return Optional.of(resource); + } else if (throwable instanceof NotFoundException) { + return Optional.empty(); + } + + applyErrorCallback(throwable); + statistics.totalFailed.incrementAndGet(); + return Optional.empty(); + } + + public static class Statistics { + final AtomicInteger totalDeleted; + final AtomicInteger totalFailed; + + private Statistics() { + this.totalDeleted = new AtomicInteger(); + this.totalFailed = new AtomicInteger(); + } + + public int getTotalDeleted() { + return totalDeleted.get(); + } + + public int getTotalFailed() { + return totalFailed.get(); + } + + public String getReportMessage() { + return format("Summary: %s custom objects were deleted in total (%s failed to delete).", + getTotalDeleted(), getTotalFailed()); + } + } +} diff --git a/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java b/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java index 6dbf8e3bf3..ba5385609f 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java @@ -129,18 +129,42 @@ public final class CtpQueryUtils { * @param client commercetools client * @param graphQlRequest graphql query containing predicates and pagination limits * @param pageConsumer consumer applied on every page queried + * @param pageSize the page size. * @return a completion stage containing void as a result after the consumer was applied on all pages. */ @Nonnull public static , U extends GraphQlBaseResource> CompletionStage queryAll(@Nonnull final SphereClient client, @Nonnull final GraphQlBaseRequest graphQlRequest, - @Nonnull final Consumer> pageConsumer) { + @Nonnull final Consumer> pageConsumer, + final int pageSize) { - GraphQlQueryAll graphQlQueryAll = GraphQlQueryAll.of(client, graphQlRequest, DEFAULT_PAGE_SIZE); + GraphQlQueryAll graphQlQueryAll = GraphQlQueryAll.of(client, graphQlRequest, pageSize); return graphQlQueryAll.run(pageConsumer); } + /** + * Creates a graphQL query to fetch all elements where keys matching given set of keys with pagination using a + * combination of limit and id sorting. + * + *

The method takes a {@link Consumer} that is applied on every page of the queried elements. + * + *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in parallel. + * + * @param client commercetools client + * @param graphQlRequest graphql query containing predicates and pagination limits + * @param pageConsumer consumer applied on every page queried + * @return a completion stage containing void as a result after the consumer was applied on all pages. + */ + @Nonnull + public static , U extends GraphQlBaseResource> CompletionStage + queryAll(@Nonnull final SphereClient client, + @Nonnull final GraphQlBaseRequest graphQlRequest, + @Nonnull final Consumer> pageConsumer) { + + return queryAll(client, graphQlRequest, pageConsumer, DEFAULT_PAGE_SIZE); + } + private CtpQueryUtils() { } } diff --git a/src/main/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImpl.java index cc1f5a45e6..a0e3f98175 100644 --- a/src/main/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImpl.java @@ -33,7 +33,7 @@ public class UnresolvedReferencesServiceImpl implements UnresolvedReferencesServ "Failed to save CustomObject with key: '%s' (hash of product key: '%s')."; private static final String DELETE_FAILED = "Failed to delete CustomObject with key: '%s' (hash of product key: '%s')."; - private static final String CUSTOM_OBJECT_CONTAINER_KEY = + public static final String CUSTOM_OBJECT_CONTAINER_KEY = "commercetools-sync-java.UnresolvedReferencesService.productDrafts"; diff --git a/src/main/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImpl.java index 23aaa7a524..c3c909bb38 100644 --- a/src/main/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImpl.java @@ -33,7 +33,7 @@ public class UnresolvedTransitionsServiceImpl implements UnresolvedTransitionsSe "Failed to save CustomObject with key: '%s' (hash of state key: '%s')."; private static final String DELETE_FAILED = "Failed to delete CustomObject with key: '%s' (hash of state key: '%s')."; - private static final String CUSTOM_OBJECT_CONTAINER_KEY = + public static final String CUSTOM_OBJECT_CONTAINER_KEY = "commercetools-sync-java.UnresolvedTransitionsService.stateDrafts"; diff --git a/src/test/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjectsTest.java b/src/test/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjectsTest.java new file mode 100644 index 0000000000..c71094c651 --- /dev/null +++ b/src/test/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjectsTest.java @@ -0,0 +1,106 @@ +package com.commercetools.sync.commons; + +import com.commercetools.sync.commons.models.FetchCustomObjectsGraphQlRequest; +import com.commercetools.sync.commons.models.ResourceKeyId; +import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; +import io.sphere.sdk.client.BadRequestException; +import io.sphere.sdk.client.NotFoundException; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.customobjects.CustomObject; +import io.sphere.sdk.customobjects.commands.CustomObjectDeleteCommand; +import io.sphere.sdk.utils.CompletableFutureUtils; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CleanupUnresolvedReferenceCustomObjectsTest { + private final SphereClient mockClient = mock(SphereClient.class); + private static final int deleteDaysAfterLastModification = 30; + + @Test + void cleanup_withDeleteDaysAfterLastModification_ShouldDeleteAndReturnCleanupStatistics() { + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); + when(resourceKeyIdGraphQlResult.getResults()).thenReturn(new HashSet<>(Arrays.asList( + new ResourceKeyId("coKey1", "coId1"), + new ResourceKeyId("coKey2", "coId2")))); + + when(mockClient.execute(any(FetchCustomObjectsGraphQlRequest.class))) + .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); + + final Throwable badRequestException = new BadRequestException("key is not valid"); + when(mockClient.execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(CompletableFutureUtils.failed(badRequestException)) + .thenReturn(CompletableFuture.completedFuture(mock(CustomObject.class))); + + final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = + CleanupUnresolvedReferenceCustomObjects.of(mockClient).cleanup(deleteDaysAfterLastModification) + .join(); + + assertThat(statistics.getTotalDeleted()).isEqualTo(3); + assertThat(statistics.getTotalFailed()).isEqualTo(1); + assertThat(statistics.getReportMessage()) + .isEqualTo("Summary: 3 custom objects were deleted in total (1 failed to delete)."); + } + + @Test + void cleanup_withNotFound404Exception_ShouldNotIncrementFailedCounter() { + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); + when(resourceKeyIdGraphQlResult.getResults()) + .thenReturn(Collections.singleton(new ResourceKeyId("coKey1", "coId1"))); + + when(mockClient.execute(any(FetchCustomObjectsGraphQlRequest.class))) + .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); + + when(mockClient.execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(CompletableFuture.completedFuture(mock(CustomObject.class))) + .thenReturn(CompletableFutureUtils.failed(new NotFoundException())); + + final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = + CleanupUnresolvedReferenceCustomObjects.of(mockClient).cleanup(deleteDaysAfterLastModification) + .join(); + + assertThat(statistics.getTotalDeleted()).isEqualTo(1); + assertThat(statistics.getTotalFailed()).isEqualTo(0); + assertThat(statistics.getReportMessage()) + .isEqualTo("Summary: 1 custom objects were deleted in total (0 failed to delete)."); + } + + @Test + void cleanup_withBadRequest400Exception_ShouldIncrementFailedCounterAndTriggerErrorCallback() { + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); + when(resourceKeyIdGraphQlResult.getResults()) + .thenReturn(Collections.singleton(new ResourceKeyId("coKey1", "coId1"))); + + when(mockClient.execute(any(FetchCustomObjectsGraphQlRequest.class))) + .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); + + final Throwable badRequestException = new BadRequestException("key is not valid"); + when(mockClient.execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(CompletableFuture.completedFuture(mock(CustomObject.class))) + .thenReturn(CompletableFutureUtils.failed(badRequestException)); + + final List exceptions = new ArrayList<>(); + final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = + CleanupUnresolvedReferenceCustomObjects.of(mockClient) + .errorCallback(exceptions::add) + .cleanup(deleteDaysAfterLastModification) + .join(); + + assertThat(statistics.getTotalDeleted()).isEqualTo(1); + assertThat(statistics.getTotalFailed()).isEqualTo(1); + assertThat(exceptions).contains(badRequestException); + assertThat(statistics.getReportMessage()) + .isEqualTo("Summary: 1 custom objects were deleted in total (1 failed to delete)."); + } + +} From 34b340bfaa99facb623c8c8316517c26f6d3540b Mon Sep 17 00:00:00 2001 From: aoz Date: Tue, 29 Dec 2020 16:52:14 +0100 Subject: [PATCH 4/9] Add spotless as a formatter, remove checkstyle.gradle --- build.gradle | 4 +- config/checkstyle/checkstyle.xml | 212 -------------------------- config/checkstyle/suppressions.xml | 11 -- docs/CONTRIBUTING.md | 12 ++ gradle-scripts/checkstyle.gradle | 9 -- gradle-scripts/execution-order.gradle | 12 +- gradle-scripts/plugins.gradle | 1 - gradle-scripts/spotless.gradle | 6 + 8 files changed, 24 insertions(+), 243 deletions(-) delete mode 100644 config/checkstyle/checkstyle.xml delete mode 100644 config/checkstyle/suppressions.xml delete mode 100644 gradle-scripts/checkstyle.gradle create mode 100644 gradle-scripts/spotless.gradle diff --git a/build.gradle b/build.gradle index 4b71eb4e0c..40d476732d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ plugins { id "com.github.ben-manes.versions" version '0.36.0' id 'ru.vyarus.mkdocs' version '2.0.1' id "com.github.spotbugs" version "4.6.0" + id 'com.diffplug.spotless' version '5.8.2' } ext{ @@ -13,7 +14,6 @@ ext{ mockitoJunitJupiterVersion = '3.7.0' jupiterApiVersion = '5.7.0' assertjVersion = '3.18.1' - checkstyleVersion = '8.2' pmdVersion = '6.14.0' jacocoVersion = '0.8.4' caffeineVersion = '2.8.5' @@ -29,7 +29,6 @@ apply from: "$rootDir/gradle-scripts/integration-tests.gradle" apply from: "$rootDir/gradle-scripts/test.gradle" apply from: "$rootDir/gradle-scripts/benchmark.gradle" apply from: "$rootDir/gradle-scripts/test-logger.gradle" -apply from: "$rootDir/gradle-scripts/checkstyle.gradle" apply from: "$rootDir/gradle-scripts/pmd.gradle" apply from: "$rootDir/gradle-scripts/jacoco.gradle" apply from: "$rootDir/gradle-scripts/spotbugs.gradle" @@ -40,6 +39,7 @@ apply from: "$rootDir/gradle-scripts/javadocs-publish.gradle" apply from: "$rootDir/gradle-scripts/set-library-version.gradle" apply from: "$rootDir/gradle-scripts/execution-order.gradle" apply from: "$rootDir/gradle-scripts/mkdocs.gradle" +apply from: "$rootDir/gradle-scripts/spotless.gradle" dependencies { implementation "com.commercetools.sdk.jvm.core:commercetools-models:${commercetoolsJvmSdkVersion}" diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml deleted file mode 100644 index f1b87f28f3..0000000000 --- a/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml deleted file mode 100644 index 098403d75f..0000000000 --- a/config/checkstyle/suppressions.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 88efc39aa2..f2fd3c64e4 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -18,6 +18,8 @@ Thanks for taking the time to contribute :+1::tada: All contributions are welcom - [Install to local maven repo](#install-to-local-maven-repo) - [Publish JavaDoc](#publish-javadoc) - [Build and publish to Bintray](#build-and-publish-to-bintray) + - [Run Spotless Style Check](#run-spotless-style-check) + - [Fix Spotless style violations](#fix-spotless-style-violations) - [Integration Tests](#integration-tests) - [Running](#running) @@ -85,6 +87,16 @@ are omitted because `javadoc` is previously created in `build` task, we just sho For more detailed information on the build and the release process, see [Build and Release](BUILD.md) documentation. +##### Run Spotless Style Check +````bash +./gradlew spotlessCheck +```` + +##### Fix Spotless style violations +````bash +./gradlew spotlessApply +```` + ### Integration Tests 1. The integration tests of the library require to have two CTP projects (a source project and a target project) where the diff --git a/gradle-scripts/checkstyle.gradle b/gradle-scripts/checkstyle.gradle deleted file mode 100644 index 792d28be74..0000000000 --- a/gradle-scripts/checkstyle.gradle +++ /dev/null @@ -1,9 +0,0 @@ -/** - * CheckStyle config - * If there are any warnings (maxWarnings exceed 0), build should fail. - */ -checkstyle { - toolVersion = checkstyleVersion - config = rootProject.resources.text.fromFile('config/checkstyle/checkstyle.xml') - maxWarnings = 0 -} diff --git a/gradle-scripts/execution-order.gradle b/gradle-scripts/execution-order.gradle index 53dc1a88c1..0c6bd2586e 100644 --- a/gradle-scripts/execution-order.gradle +++ b/gradle-scripts/execution-order.gradle @@ -9,10 +9,6 @@ compileTestJava.mustRunAfter compileJava compileIntegrationTestJava.mustRunAfter compileTestJava -//Checkstyle sub-tasks execution order -checkstyleTest.mustRunAfter checkstyleMain -checkstyleIntegrationTest.mustRunAfter checkstyleTest - //PMD sub-tasks execution order pmdTest.mustRunAfter pmdMain pmdIntegrationTest.mustRunAfter pmdTest @@ -28,10 +24,10 @@ jacocoTestReport.mustRunAfter jacocoTestCoverageVerification * Global execution order */ -// Ensure CheckStyle runs after Compile -checkstyleMain.mustRunAfter compileIntegrationTestJava -// Ensure PMD runs after Checkstyle -pmdMain.mustRunAfter checkstyleIntegrationTest +// Ensure spotlessApply runs after Compile +spotlessCheck.mustRunAfter compileIntegrationTestJava +// Ensure PMD runs after spotlessApply +pmdMain.mustRunAfter spotlessCheck // Ensure spotbugs runs after PMD spotbugsMain.mustRunAfter pmdIntegrationTest // Ensure unit tests are run after spotbugs diff --git a/gradle-scripts/plugins.gradle b/gradle-scripts/plugins.gradle index d76a32ff15..6bd8cb0c7a 100644 --- a/gradle-scripts/plugins.gradle +++ b/gradle-scripts/plugins.gradle @@ -1,6 +1,5 @@ apply plugin: 'java' apply plugin: 'maven' -apply plugin: 'checkstyle' apply plugin: 'pmd' apply plugin: 'jacoco' diff --git a/gradle-scripts/spotless.gradle b/gradle-scripts/spotless.gradle new file mode 100644 index 0000000000..087fea5c87 --- /dev/null +++ b/gradle-scripts/spotless.gradle @@ -0,0 +1,6 @@ +spotless { + java { + removeUnusedImports() + googleJavaFormat('1.7') + } +} From 0bb39eb7360c1863a3e3c7e50519683865748cfd Mon Sep 17 00:00:00 2001 From: aoz Date: Mon, 11 Jan 2021 20:38:57 +0100 Subject: [PATCH 5/9] Re-format codes the google java style guide with using "spotlessApply" gradle task. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * note:  use `git blame file.java --ignore-revs-file .git-blame-ignore-revs` command to ignore this revision to find a better git history. * More details for the Google Java Style: https://google.github.io/styleguide/javaguide.html --- .../sync/benchmark/BenchmarkUtils.java | 187 +- .../benchmark/CartDiscountSyncBenchmark.java | 524 +-- .../sync/benchmark/CategorySyncBenchmark.java | 46 +- .../benchmark/InventorySyncBenchmark.java | 195 +- .../sync/benchmark/ProductSyncBenchmark.java | 516 +-- .../benchmark/ProductTypeSyncBenchmark.java | 502 +-- .../sync/benchmark/TypeSyncBenchmark.java | 489 +-- ...nupUnresolvedReferenceCustomObjectsIT.java | 135 +- .../integration/commons/CtpQueryUtilsIT.java | 360 +- .../commons/utils/CartDiscountITUtils.java | 348 +- .../commons/utils/CategoryITUtils.java | 577 +-- .../commons/utils/ChannelITUtils.java | 85 +- .../commons/utils/CustomObjectITUtils.java | 269 +- .../commons/utils/CustomerGroupITUtils.java | 64 +- .../commons/utils/CustomerITUtils.java | 223 +- .../integration/commons/utils/ITUtils.java | 778 ++-- .../commons/utils/ProductITUtils.java | 384 +- .../commons/utils/ProductTypeITUtils.java | 930 ++--- .../commons/utils/ShoppingListITUtils.java | 578 +-- .../commons/utils/SphereClientUtils.java | 76 +- .../commons/utils/StateITUtils.java | 274 +- .../commons/utils/StoreITUtils.java | 70 +- .../commons/utils/TaxCategoryITUtils.java | 134 +- .../commons/utils/TypeITUtils.java | 234 +- .../cartdiscounts/CartDiscountSyncIT.java | 240 +- .../categories/CategorySyncIT.java | 751 ++-- .../customers/CustomerSyncIT.java | 231 +- .../products/ProductReferenceResolverIT.java | 324 +- .../products/ProductSyncIT.java | 1201 +++--- .../products/ProductSyncWithAssetsIT.java | 421 ++- .../KeepOtherVariantsSyncIT.java | 232 +- .../producttypes/ProductTypeSyncIT.java | 212 +- .../ProductTypeWithNestedAttributeSyncIT.java | 365 +- .../shoppinglists/ShoppingListSyncIT.java | 230 +- .../ctpprojectsource/types/TypeSyncIT.java | 205 +- .../cartdiscounts/CartDiscountSyncIT.java | 1148 +++--- .../categories/CategorySyncIT.java | 1166 +++--- .../CategoryReferenceResolutionUtilsIT.java | 149 +- .../customers/CustomerSyncIT.java | 365 +- .../customobjects/CustomObjectSyncIT.java | 480 +-- .../products/ProductReferenceResolverIT.java | 233 +- .../products/ProductSyncFilterIT.java | 336 +- .../products/ProductSyncIT.java | 1557 ++++---- .../products/ProductSyncWithAssetsIT.java | 622 ++-- ...tSyncWithNestedReferencedCategoriesIT.java | 1056 +++--- ...ncWithNestedReferencedCustomObjectsIT.java | 1135 +++--- ...yncWithNestedReferencedProductTypesIT.java | 1051 +++--- ...uctSyncWithNestedReferencedProductsIT.java | 1206 +++--- .../products/ProductSyncWithPricesIT.java | 1761 +++++---- ...ProductSyncWithReferencedCategoriesIT.java | 781 ++-- ...oductSyncWithReferencedProductTypesIT.java | 800 ++-- .../ProductSyncWithReferencedProductsIT.java | 764 ++-- ...yncWithReferencedProductsInAnyOrderIT.java | 1249 ++++--- .../products/SyncSingleLocaleIT.java | 318 +- .../producttypes/ProductTypeSyncIT.java | 1377 +++---- .../ProductTypeWithNestedAttributeSyncIT.java | 898 ++--- .../shoppinglists/ShoppingListSyncIT.java | 555 +-- .../externalsource/states/StateSyncIT.java | 1866 +++++----- .../taxcategories/TaxCategorySyncIT.java | 526 +-- .../externalsource/types/TypeSyncIT.java | 1356 ++++--- .../inventories/InventorySyncIT.java | 860 +++-- .../InventoryUpdateActionUtilsIT.java | 137 +- .../inventories/utils/InventoryITUtils.java | 375 +- .../impl/CartDiscountServiceImplIT.java | 670 ++-- .../services/impl/CategoryServiceImplIT.java | 926 ++--- .../services/impl/ChannelServiceImplIT.java | 203 +- .../impl/CustomObjectServiceImplIT.java | 559 +-- .../impl/CustomerGroupServiceImplIT.java | 108 +- .../services/impl/CustomerServiceImplIT.java | 440 +-- .../services/impl/InventoryServiceImplIT.java | 249 +- .../services/impl/ProductServiceImplIT.java | 1295 +++---- .../impl/ProductTypeServiceImplIT.java | 739 ++-- .../impl/ShoppingListServiceImplIT.java | 503 +-- .../services/impl/StateServiceImplIT.java | 817 +++-- .../impl/TaxCategoryServiceImplIT.java | 732 ++-- .../services/impl/TypeServiceImplIT.java | 769 ++-- .../UnresolvedReferencesServiceImplIT.java | 296 +- .../UnresolvedTransitionsServiceImplIT.java | 320 +- .../tests/utils/CompletionStageUtil.java | 11 +- .../sync/cartdiscounts/CartDiscountSync.java | 621 ++-- .../CartDiscountSyncOptions.java | 58 +- .../CartDiscountSyncOptionsBuilder.java | 101 +- .../helpers/CartDiscountBatchValidator.java | 112 +- .../CartDiscountCustomActionBuilder.java | 50 +- .../CartDiscountReferenceResolver.java | 102 +- .../helpers/CartDiscountSyncStatistics.java | 24 +- .../CartDiscountReferenceResolutionUtils.java | 151 +- .../utils/CartDiscountSyncUtils.java | 81 +- .../utils/CartDiscountUpdateActionUtils.java | 711 ++-- .../sync/categories/CategorySync.java | 1404 +++---- .../sync/categories/CategorySyncOptions.java | 55 +- .../CategorySyncOptionsBuilder.java | 100 +- .../helpers/AssetCustomActionBuilder.java | 56 +- .../helpers/CategoryAssetActionFactory.java | 66 +- .../helpers/CategoryBatchValidator.java | 144 +- .../helpers/CategoryCustomActionBuilder.java | 51 +- .../helpers/CategoryReferenceResolver.java | 351 +- .../helpers/CategorySyncStatistics.java | 211 +- .../utils/CategoryAssetUpdateActionUtils.java | 283 +- .../CategoryReferenceResolutionUtils.java | 176 +- .../categories/utils/CategorySyncUtils.java | 87 +- .../utils/CategoryUpdateActionUtils.java | 477 +-- .../helpers/ChannelCustomActionBuilder.java | 55 +- .../commercetools/sync/commons/BaseSync.java | 209 +- .../sync/commons/BaseSyncOptions.java | 466 +-- .../sync/commons/BaseSyncOptionsBuilder.java | 245 +- ...eanupUnresolvedReferenceCustomObjects.java | 362 +- .../BuildUpdateActionException.java | 18 +- .../exceptions/DuplicateKeyException.java | 18 +- .../exceptions/DuplicateNameException.java | 18 +- .../commons/exceptions/ExceptionUtils.java | 78 +- .../InvalidAttributeDefinitionException.java | 9 +- .../InvalidProductTypeException.java | 25 +- .../exceptions/InvalidReferenceException.java | 8 +- .../ReferenceReplacementException.java | 23 +- .../ReferenceResolutionException.java | 19 +- .../commons/exceptions/SyncException.java | 19 +- .../commons/helpers/AssetActionFactory.java | 93 +- .../helpers/AssetReferenceResolver.java | 78 +- .../commons/helpers/BaseBatchValidator.java | 124 +- .../helpers/BaseReferenceResolver.java | 142 +- .../commons/helpers/BaseSyncStatistics.java | 581 ++- .../helpers/CategoryReferencePair.java | 53 +- .../helpers/CustomReferenceResolver.java | 263 +- .../helpers/GenericCustomActionBuilder.java | 125 +- .../helpers/GraphQlBaseRequestImpl.java | 120 +- .../helpers/ResourceKeyIdGraphQlRequest.java | 143 +- .../FetchCustomObjectsGraphQlRequest.java | 104 +- .../commons/models/GraphQlBaseRequest.java | 32 +- .../commons/models/GraphQlBaseResource.java | 2 +- .../commons/models/GraphQlBaseResult.java | 2 +- .../commons/models/GraphQlQueryResources.java | 36 +- .../sync/commons/models/ResourceKeyId.java | 31 +- .../models/ResourceKeyIdGraphQlResult.java | 18 +- .../commons/models/WaitingToBeResolved.java | 99 +- .../WaitingToBeResolvedTransitions.java | 97 +- .../utils/AssetReferenceResolutionUtils.java | 50 +- .../utils/AssetsUpdateActionUtils.java | 506 +-- .../utils/ClientConfigurationUtils.java | 77 +- .../sync/commons/utils/CollectionUtils.java | 287 +- .../utils/CommonTypeUpdateActionUtils.java | 189 +- .../commons/utils/CompletableFutureUtils.java | 307 +- .../sync/commons/utils/CtpQueryUtils.java | 313 +- .../CustomTypeReferenceResolutionUtils.java | 97 +- .../utils/CustomUpdateActionUtils.java | 874 +++-- .../utils/EnumValuesUpdateActionUtils.java | 804 ++-- .../sync/commons/utils/FilterUtils.java | 65 +- .../utils/GenericUpdateActionUtils.java | 104 +- .../sync/commons/utils/GraphQlQueryAll.java | 235 +- .../sync/commons/utils/OptionalUtils.java | 110 +- .../sync/commons/utils/QuadConsumer.java | 32 +- .../sync/commons/utils/QueryAll.java | 296 +- .../utils/ResourceIdentifierUtils.java | 100 +- .../sync/commons/utils/StreamUtils.java | 36 +- .../sync/commons/utils/SyncSolutionInfo.java | 35 +- .../sync/commons/utils/SyncUtils.java | 157 +- .../sync/commons/utils/TriConsumer.java | 23 +- .../sync/commons/utils/TriFunction.java | 31 +- .../sync/customers/CustomerSync.java | 510 +-- .../sync/customers/CustomerSyncOptions.java | 53 +- .../customers/CustomerSyncOptionsBuilder.java | 100 +- .../helpers/CustomerBatchValidator.java | 399 +- .../helpers/CustomerReferenceResolver.java | 366 +- .../helpers/CustomerSyncStatistics.java | 24 +- .../utils/CustomerCustomActionBuilder.java | 84 +- .../CustomerReferenceResolutionUtils.java | 290 +- .../customers/utils/CustomerSyncUtils.java | 91 +- .../utils/CustomerUpdateActionUtils.java | 1915 +++++----- .../sync/customobjects/CustomObjectSync.java | 563 +-- .../CustomObjectSyncOptions.java | 67 +- .../CustomObjectSyncOptionsBuilder.java | 95 +- .../helpers/CustomObjectBatchValidator.java | 99 +- .../CustomObjectCompositeIdentifier.java | 206 +- .../helpers/CustomObjectSyncStatistics.java | 24 +- .../utils/CustomObjectSyncUtils.java | 35 +- .../internals/helpers/PriceCompositeId.java | 247 +- .../utils/UnorderedCollectionSyncUtils.java | 108 +- .../utils/UpdateActionsSortUtils.java | 93 +- .../sync/inventories/InventorySync.java | 575 +-- .../inventories/InventorySyncOptions.java | 84 +- .../InventorySyncOptionsBuilder.java | 131 +- .../helpers/InventoryBatchValidator.java | 138 +- .../helpers/InventoryCustomActionBuilder.java | 50 +- .../helpers/InventoryEntryIdentifier.java | 167 +- .../helpers/InventoryReferenceResolver.java | 446 +-- .../helpers/InventorySyncStatistics.java | 32 +- .../InventoryReferenceResolutionUtils.java | 164 +- .../inventories/utils/InventorySyncUtils.java | 86 +- .../utils/InventoryUpdateActionUtils.java | 182 +- .../sync/products/ActionGroup.java | 31 +- .../sync/products/AttributeMetaData.java | 74 +- .../sync/products/ProductSync.java | 874 ++--- .../sync/products/ProductSyncOptions.java | 96 +- .../products/ProductSyncOptionsBuilder.java | 143 +- .../sync/products/SyncFilter.java | 123 +- .../helpers/AssetCustomActionBuilder.java | 60 +- .../helpers/PriceCustomActionBuilder.java | 51 +- .../helpers/PriceReferenceResolver.java | 459 +-- .../helpers/ProductAssetActionFactory.java | 82 +- .../helpers/ProductBatchValidator.java | 589 +-- .../helpers/ProductReferenceResolver.java | 836 +++-- .../helpers/ProductSyncStatistics.java | 129 +- .../helpers/VariantReferenceResolver.java | 318 +- .../KeepOtherVariantsSync.java | 45 +- .../SyncSingleLocale.java | 482 +-- .../ProductReferenceResolutionUtils.java | 427 +-- .../sync/products/utils/ProductSyncUtils.java | 376 +- .../utils/ProductUpdateActionUtils.java | 1803 ++++----- .../ProductVariantAssetUpdateActionUtils.java | 330 +- ...ductVariantAttributeUpdateActionUtils.java | 114 +- .../ProductVariantPriceUpdateActionUtils.java | 256 +- .../ProductVariantUpdateActionUtils.java | 714 ++-- .../VariantReferenceResolutionUtils.java | 386 +- .../sync/producttypes/ProductTypeSync.java | 1239 ++++--- .../producttypes/ProductTypeSyncOptions.java | 58 +- .../ProductTypeSyncOptionsBuilder.java | 101 +- .../AttributeDefinitionReferenceResolver.java | 190 +- .../helpers/ProductTypeBatchValidator.java | 263 +- .../helpers/ProductTypeReferenceResolver.java | 100 +- .../helpers/ProductTypeSyncStatistics.java | 400 +- .../AttributeDefinitionUpdateActionUtils.java | 546 +-- ...AttributeDefinitionsUpdateActionUtils.java | 530 ++- .../LocalizedEnumValueUpdateActionUtils.java | 169 +- .../PlainEnumValueUpdateActionUtils.java | 159 +- .../ProductTypeReferenceResolutionUtils.java | 383 +- .../utils/ProductTypeSyncUtils.java | 80 +- .../utils/ProductTypeUpdateActionUtils.java | 152 +- .../sync/services/CartDiscountService.java | 130 +- .../sync/services/CategoryService.java | 183 +- .../sync/services/ChannelService.java | 96 +- .../sync/services/CustomObjectService.java | 174 +- .../sync/services/CustomerGroupService.java | 61 +- .../sync/services/CustomerService.java | 177 +- .../sync/services/InventoryService.java | 61 +- .../sync/services/ProductService.java | 191 +- .../sync/services/ProductTypeService.java | 223 +- .../sync/services/ShoppingListService.java | 161 +- .../sync/services/StateService.java | 219 +- .../sync/services/TaxCategoryService.java | 198 +- .../sync/services/TypeService.java | 180 +- .../services/UnresolvedReferencesService.java | 62 +- .../UnresolvedTransitionsService.java | 61 +- .../sync/services/impl/BaseService.java | 657 ++-- .../services/impl/BaseServiceWithKey.java | 215 +- .../impl/CartDiscountServiceImpl.java | 78 +- .../services/impl/CategoryServiceImpl.java | 135 +- .../services/impl/ChannelServiceImpl.java | 106 +- .../impl/CustomObjectServiceImpl.java | 333 +- .../impl/CustomerGroupServiceImpl.java | 62 +- .../services/impl/CustomerServiceImpl.java | 208 +- .../services/impl/InventoryServiceImpl.java | 78 +- .../services/impl/ProductServiceImpl.java | 159 +- .../services/impl/ProductTypeServiceImpl.java | 213 +- .../impl/ShoppingListServiceImpl.java | 104 +- .../sync/services/impl/StateServiceImpl.java | 143 +- .../services/impl/TaxCategoryServiceImpl.java | 114 +- .../sync/services/impl/TypeServiceImpl.java | 103 +- .../impl/UnresolvedReferencesServiceImpl.java | 175 +- .../UnresolvedTransitionsServiceImpl.java | 177 +- .../sync/shoppinglists/ShoppingListSync.java | 532 +-- .../ShoppingListSyncOptions.java | 62 +- .../ShoppingListSyncOptionsBuilder.java | 99 +- .../updateactions/AddLineItemWithSku.java | 106 +- .../AddTextLineItemWithAddedAt.java | 118 +- .../helpers/LineItemReferenceResolver.java | 91 +- .../helpers/ShoppingListBatchValidator.java | 372 +- .../ShoppingListReferenceResolver.java | 330 +- .../helpers/ShoppingListSyncStatistics.java | 24 +- .../TextLineItemReferenceResolver.java | 104 +- .../utils/LineItemCustomActionBuilder.java | 62 +- .../utils/LineItemUpdateActionUtils.java | 453 +-- .../ShoppingListCustomActionBuilder.java | 90 +- .../ShoppingListReferenceResolutionUtils.java | 397 +- .../utils/ShoppingListSyncUtils.java | 80 +- .../utils/ShoppingListUpdateActionUtils.java | 297 +- .../TextLineItemCustomActionBuilder.java | 68 +- .../utils/TextLineItemUpdateActionUtils.java | 506 +-- .../commercetools/sync/states/StateSync.java | 779 ++-- .../sync/states/StateSyncOptions.java | 48 +- .../sync/states/StateSyncOptionsBuilder.java | 103 +- .../states/helpers/StateBatchValidator.java | 201 +- .../helpers/StateReferenceResolver.java | 192 +- .../states/helpers/StateSyncStatistics.java | 129 +- .../utils/StateReferenceResolutionUtils.java | 166 +- .../sync/states/utils/StateSyncUtils.java | 77 +- .../states/utils/StateUpdateActionUtils.java | 346 +- .../sync/taxcategories/TaxCategorySync.java | 569 +-- .../taxcategories/TaxCategorySyncOptions.java | 56 +- .../TaxCategorySyncOptionsBuilder.java | 102 +- .../helpers/TaxCategoryBatchValidator.java | 223 +- .../helpers/TaxCategorySyncStatistics.java | 29 +- .../utils/TaxCategorySyncUtils.java | 61 +- .../utils/TaxCategoryUpdateActionUtils.java | 116 +- .../utils/TaxRatesUpdateActionUtils.java | 326 +- .../commercetools/sync/types/TypeSync.java | 581 +-- .../sync/types/TypeSyncOptions.java | 50 +- .../sync/types/TypeSyncOptionsBuilder.java | 97 +- .../types/helpers/TypeBatchValidator.java | 106 +- .../types/helpers/TypeSyncStatistics.java | 22 +- .../FieldDefinitionUpdateActionUtils.java | 324 +- .../FieldDefinitionsUpdateActionUtils.java | 418 ++- .../LocalizedEnumValueUpdateActionUtils.java | 88 +- .../PlainEnumValueUpdateActionUtils.java | 83 +- .../sync/types/utils/TypeSyncUtils.java | 98 +- .../types/utils/TypeUpdateActionUtils.java | 162 +- .../CartDiscountSyncOptionsBuilderTest.java | 539 +-- .../cartdiscounts/CartDiscountSyncTest.java | 489 +-- .../CartDiscountBatchValidatorTest.java | 210 +- .../CartDiscountReferenceResolverTest.java | 203 +- .../CartDiscountSyncStatisticsTest.java | 33 +- ...tDiscountReferenceResolutionUtilsTest.java | 117 +- .../utils/CartDiscountSyncUtilsTest.java | 456 +-- .../CartDiscountUpdateActionUtilsTest.java | 2438 ++++++------ .../categories/CategorySyncMockUtils.java | 415 +-- .../CategorySyncOptionsBuilderTest.java | 509 +-- .../categories/CategorySyncOptionsTest.java | 87 +- .../sync/categories/CategorySyncTest.java | 1121 +++--- .../CategoryAssetActionFactoryTest.java | 125 +- .../helpers/CategoryBatchValidatorTest.java | 206 +- .../CategoryReferenceResolverTest.java | 687 ++-- .../helpers/CategorySyncStatisticsTest.java | 544 +-- .../CategoryAssetUpdateActionUtilsTest.java | 653 ++-- .../CategoryReferenceResolutionUtilsTest.java | 269 +- .../utils/CategorySyncUtilsTest.java | 123 +- .../utils/CategoryUpdateActionUtilsTest.java | 955 ++--- .../BuildAssetsUpdateActionsTest.java | 773 ++-- .../sync/commons/BaseSyncTest.java | 76 +- ...pUnresolvedReferenceCustomObjectsTest.java | 177 +- .../commercetools/sync/commons/MockUtils.java | 300 +- .../actions/AbstractSetCustomFieldAssert.java | 58 +- .../actions/AbstractSetCustomTypeAssert.java | 101 +- .../actions/AbstractUpdateActionAssert.java | 43 +- .../actions/AssertionsForUpdateActions.java | 181 +- .../CategorySetAssetCustomFieldAssert.java | 63 +- .../CategorySetAssetCustomTypeAssert.java | 65 +- .../ProductSetAssetCustomFieldAssert.java | 77 +- .../ProductSetAssetCustomTypeAssert.java | 81 +- .../asserts/actions/SetCustomFieldAssert.java | 8 +- .../asserts/actions/SetCustomTypeAssert.java | 7 +- .../actions/SetPriceCustomFieldAssert.java | 63 +- .../actions/SetPriceCustomTypeAssert.java | 67 +- .../AbstractSyncStatisticsAssert.java | 57 +- .../statistics/AssertionsForStatistics.java | 234 +- .../CartDiscountSyncStatisticsAssert.java | 12 +- .../CategorySyncStatisticsAssert.java | 53 +- .../CustomObjectSyncStatisticsAssert.java | 11 +- .../CustomerSyncStatisticsAssert.java | 11 +- .../InventorySyncStatisticsAssert.java | 11 +- .../ProductSyncStatisticsAssert.java | 53 +- .../ProductTypeSyncStatisticsAssert.java | 59 +- .../ShoppingListSyncStatisticsAssert.java | 12 +- .../statistics/StateSyncStatisticsAssert.java | 56 +- .../TaxCategorySyncStatisticsAssert.java | 12 +- .../statistics/TypeSyncStatisticsAssert.java | 11 +- .../BuildUpdateActionExceptionTest.java | 76 +- .../exceptions/DuplicateKeyExceptionTest.java | 76 +- .../DuplicateNameExceptionTest.java | 76 +- ...validAttributeDefinitionExceptionTest.java | 30 +- .../InvalidProductTypeExceptionTest.java | 185 +- .../InvalidReferenceExceptionTest.java | 26 +- .../ReferenceReplacementExceptionTest.java | 197 +- .../ReferenceResolutionExceptionTest.java | 76 +- .../commons/exceptions/SyncExceptionTest.java | 76 +- .../helpers/AssetReferenceResolverTest.java | 297 +- .../helpers/BaseSyncStatisticsTest.java | 222 +- .../helpers/CategoryReferencePairTest.java | 59 +- .../ResourceKeyIdGraphQlRequestTest.java | 378 +- .../FetchCustomObjectsGraphQlRequestTest.java | 114 +- .../models/WaitingToBeResolvedTest.java | 423 ++- .../WaitingToBeResolvedTransitionsTest.java | 427 ++- .../AssetReferenceResolutionUtilsTest.java | 137 +- ...rtDiscountCustomUpdateActionUtilsTest.java | 78 +- ...egoryAssetCustomUpdateActionUtilsTest.java | 93 +- .../CategoryCustomUpdateActionUtilsTest.java | 77 +- .../ChannelCustomUpdateActionUtilsTest.java | 80 +- .../utils/ClientConfigurationUtilsTest.java | 43 +- .../commons/utils/CollectionUtilsTest.java | 360 +- .../CommonTypeUpdateActionUtilsTest.java | 624 ++-- .../sync/commons/utils/CtpQueryUtilsTest.java | 375 +- ...ustomTypeReferenceResolutionUtilsTest.java | 99 +- .../utils/CustomUpdateActionUtilsTest.java | 1639 +++++---- .../CustomerCustomUpdateActionUtilsTest.java | 74 +- .../EnumValuesUpdateActionUtilsTest.java | 419 +-- .../sync/commons/utils/FilterUtilsTest.java | 190 +- .../utils/GenericUpdateActionUtilsTest.java | 73 +- .../commons/utils/GraphQlQueryAllTest.java | 135 +- ...ntoryEntryCustomUpdateActionUtilsTest.java | 95 +- .../utils/LocalizedEnumValueFixtures.java | 74 +- .../sync/commons/utils/OptionalUtilsTest.java | 177 +- .../commons/utils/PlainEnumValueFixtures.java | 68 +- ...oductAssetCustomUpdateActionUtilsTest.java | 105 +- ...oductPriceCustomUpdateActionUtilsTest.java | 92 +- .../sync/commons/utils/QueryAllTest.java | 124 +- .../ResourceCustomUpdateActionUtilsTest.java | 1266 ++++--- .../utils/ResourceIdentifierUtilsTest.java | 293 +- ...oppingListCustomUpdateActionUtilsTest.java | 301 +- .../sync/commons/utils/StreamUtilsTest.java | 103 +- .../sync/commons/utils/SyncUtilsTest.java | 337 +- .../sync/commons/utils/TriFunctionTest.java | 140 +- ...tionOfFuturesToFutureOfCollectionTest.java | 464 ++- ...amValuesToFutureOfCompletedValuesTest.java | 387 +- ...esToFutureOfCompletedValuesStreamTest.java | 359 +- ...apValuesToFutureOfCompletedValuesTest.java | 746 ++-- .../MapValuesToFuturesTest.java | 460 ++- .../ToCompletableFuturesTest.java | 331 +- .../CustomerSyncOptionsBuilderTest.java | 515 ++- .../sync/customers/CustomerSyncTest.java | 1042 +++--- .../helpers/CustomerBatchValidatorTest.java | 759 ++-- .../CustomerReferenceResolverTest.java | 512 +-- .../helpers/CustomerSyncStatisticsTest.java | 34 +- .../utils/AddressUpdateActionUtilsTest.java | 3254 +++++++++-------- .../CustomerReferenceResolutionUtilsTest.java | 325 +- .../utils/CustomerSyncUtilsTest.java | 208 +- .../utils/CustomerUpdateActionUtilsTest.java | 798 ++-- .../utils/StoreUpdateActionUtilsTest.java | 472 ++- .../CustomObjectSyncOptionsBuilderTest.java | 363 +- .../CustomObjectSyncOptionsTest.java | 252 +- .../customobjects/CustomObjectSyncTest.java | 958 ++--- .../CustomObjectBatchValidatorTest.java | 120 +- .../CustomObjectCompositeIdentifierTest.java | 317 +- .../CustomObjectSyncStatisticsTest.java | 34 +- .../utils/CustomObjectSyncUtilsTest.java | 353 +- .../helpers/PriceCompositeIdTest.java | 631 ++-- .../helpers/UpdateActionsSortUtilsTest.java | 297 +- .../UnorderedCollectionSyncUtilsTest.java | 368 +- .../inventories/InventorySyncMockUtils.java | 211 +- .../inventories/InventorySyncOptionsTest.java | 62 +- .../sync/inventories/InventorySyncTest.java | 1039 +++--- .../helpers/InventoryBatchValidatorTest.java | 206 +- .../helpers/InventoryEntryIdentifierTest.java | 352 +- .../InventoryReferenceResolverTest.java | 391 +- .../helpers/InventorySyncStatisticsTest.java | 33 +- ...InventoryReferenceResolutionUtilsTest.java | 178 +- .../utils/InventorySyncUtilsTest.java | 299 +- .../utils/InventoryUpdateActionUtilsTest.java | 320 +- .../sync/products/ProductSyncMockUtils.java | 942 ++--- .../ProductSyncOptionsBuilderTest.java | 531 ++- .../sync/products/ProductSyncTest.java | 613 ++-- .../sync/products/SyncFilterTest.java | 120 +- ...iceCustomerGroupReferenceResolverTest.java | 223 +- .../helpers/PriceReferenceResolverTest.java | 585 +-- .../ProductAssetActionFactoryTest.java | 159 +- .../helpers/ProductBatchValidatorTest.java | 864 ++--- .../helpers/ProductSyncStatisticsTest.java | 258 +- .../helpers/VariantReferenceResolverTest.java | 868 +++-- .../CategoryReferenceResolverTest.java | 616 ++-- .../ProductTypeReferenceResolverTest.java | 274 +- .../StateReferenceResolverTest.java | 274 +- .../TaxCategoryReferenceResolverTest.java | 280 +- ...rtionUtilsForVariantReferenceResolver.java | 95 +- .../WithCategoryReferencesTest.java | 445 ++- .../WithCustomObjectReferencesTest.java | 553 ++- .../WithCustomerReferencesTest.java | 130 +- .../WithProductReferencesTest.java | 441 ++- .../WithProductTypeReferencesTest.java | 448 ++- .../WithCategoryReferencesTest.java | 303 +- .../WithCustomObjectReferencesTest.java | 320 +- .../WithCustomerReferencesTest.java | 92 +- .../WithNoReferencesTest.java | 89 +- .../WithProductReferencesTest.java | 302 +- .../WithProductTypeReferencesTest.java | 304 +- .../WithCategoryReferencesTest.java | 329 +- .../WithCustomObjectReferencesTest.java | 347 +- .../WithCustomerReferencesTest.java | 93 +- .../WithNoReferencesTest.java | 93 +- .../WithProductReferencesTest.java | 329 +- .../WithProductTypeReferencesTest.java | 331 +- .../ProductReferenceResolutionUtilsTest.java | 1007 ++--- .../products/utils/ProductSyncUtilsTest.java | 624 ++-- .../utils/ProductUpdateActionUtilsTest.java | 818 +++-- ...ductVariantAssetUpdateActionUtilsTest.java | 840 ++--- ...ductVariantPriceUpdateActionUtilsTest.java | 746 ++-- .../VariantReferenceResolutionUtilsTest.java | 913 ++--- .../BuildAddToCategoryUpdateActionsTest.java | 86 +- .../BuildChangeNameUpdateActionTest.java | 79 +- .../BuildChangeSlugUpdateActionTest.java | 80 +- ...ildPublishOrUnpublishUpdateActionTest.java | 358 +- ...ldRemoveFromCategoryUpdateActionsTest.java | 90 +- ...SetCategoryOrderHintUpdateActionsTest.java | 104 +- .../BuildSetDescriptionUpdateActionTest.java | 82 +- ...ildSetMetaDescriptionUpdateActionTest.java | 83 +- .../BuildSetMetaKeywordsUpdateActionTest.java | 81 +- .../BuildSetMetaTitleUpdateActionTest.java | 80 +- ...uildSetSearchKeywordsUpdateActionTest.java | 86 +- .../BuildSetTaxCategoryUpdateActionTest.java | 98 +- .../BuildTransitionStateUpdateActionTest.java | 124 +- ...ProductVariantImagesUpdateActionsTest.java | 640 ++-- .../BuildVariantAssetsUpdateActionsTest.java | 821 +++-- .../ProductVariantUpdateActionUtilsTest.java | 99 +- .../attributes/AttributeFixtures.java | 75 +- ...ductVariantAttributeUpdateActionsTest.java | 293 +- ...uctVariantAttributesUpdateActionsTest.java | 1517 ++++---- ...ProductVariantPricesUpdateActionsTest.java | 704 ++-- .../prices/PriceDraftFixtures.java | 485 +-- .../prices/PriceFixtures.java | 360 +- .../ProductTypeSyncOptionsBuilderTest.java | 509 +-- .../producttypes/ProductTypeSyncTest.java | 1136 +++--- ...ributeDefinitionReferenceResolverTest.java | 395 +- .../ProductTypeBatchValidatorTest.java | 591 +-- .../ProductTypeReferenceResolverTest.java | 509 ++- .../ProductTypeSyncStatisticsTest.java | 1165 +++--- ...ributeDefinitionUpdateActionUtilsTest.java | 1100 +++--- ...calizedEnumValueUpdateActionUtilsTest.java | 57 +- .../PlainEnumValueUpdateActionUtilsTest.java | 74 +- ...oductTypeReferenceResolutionUtilsTest.java | 565 +-- .../ProductTypeUpdateActionUtilsTest.java | 149 +- ...dAttributeDefinitionUpdateActionsTest.java | 1776 +++++---- .../BuildLocalizedEnumUpdateActionsTest.java | 456 ++- .../BuildPlainEnumUpdateActionsTest.java | 452 +-- .../services/impl/BaseServiceImplTest.java | 774 ++-- .../impl/CartDiscountServiceImplTest.java | 431 +-- .../impl/CustomObjectServiceImplTest.java | 329 +- .../impl/CustomerServiceImplTest.java | 348 +- .../services/impl/ProductServiceTest.java | 262 +- .../impl/ProductTypeServiceImplTest.java | 123 +- .../impl/ShoppingListServiceImplTest.java | 493 +-- .../services/impl/StateServiceImplTest.java | 451 +-- .../impl/TaxCategoryServiceImplTest.java | 333 +- .../UnresolvedReferencesServiceImplTest.java | 561 +-- .../UnresolvedTransitionsServiceImplTest.java | 555 +-- .../ShoppingListSyncOptionsBuilderTest.java | 549 ++- .../shoppinglists/ShoppingListSyncTest.java | 1389 +++---- .../LineItemReferenceResolverTest.java | 246 +- .../ShoppingListBatchValidatorTest.java | 590 +-- .../ShoppingListReferenceResolverTest.java | 622 ++-- .../ShoppingListSyncStatisticsTest.java | 33 +- .../TextLineItemReferenceResolverTest.java | 261 +- .../LineItemListUpdateActionUtilsTest.java | 1004 ++--- .../utils/LineItemUpdateActionUtilsTest.java | 827 +++-- .../utils/ShoppingListSyncUtilsTest.java | 254 +- .../ShoppingListUpdateActionUtilsTest.java | 585 +-- ...pingListsReferenceResolutionUtilsTest.java | 272 +- ...TextLineItemListUpdateActionUtilsTest.java | 719 ++-- .../TextLineItemUpdateActionUtilsTest.java | 1031 +++--- .../states/StateSyncOptionsBuilderTest.java | 210 +- .../sync/states/StateSyncOptionsTest.java | 234 +- .../sync/states/StateSyncTest.java | 367 +- .../helpers/StateBatchValidatorTest.java | 201 +- .../helpers/StateReferenceResolverTest.java | 337 +- .../helpers/StateSyncStatisticsTest.java | 256 +- .../StateReferenceResolutionUtilsTest.java | 178 +- .../sync/states/utils/StateSyncUtilsTest.java | 114 +- .../utils/StateUpdateActionUtilsTest.java | 499 +-- .../TaxCategorySyncOptionsBuilderTest.java | 229 +- .../TaxCategorySyncOptionsTest.java | 238 +- .../taxcategories/TaxCategorySyncTest.java | 786 ++-- .../TaxCategoryBatchValidatorTest.java | 287 +- .../TaxCategorySyncStatisticsTest.java | 45 +- .../utils/TaxCategorySyncUtilsTest.java | 68 +- .../TaxCategoryUpdateActionUtilsTest.java | 883 +++-- .../types/TypeSyncOptionsBuilderTest.java | 500 ++- .../sync/types/TypeSyncTest.java | 213 +- .../types/helpers/TypeBatchValidatorTest.java | 213 +- .../types/helpers/TypeSyncStatisticsTest.java | 34 +- ...BuildFieldDefinitionUpdateActionsTest.java | 1075 +++--- .../BuildLocalizedEnumUpdateActionsTest.java | 297 +- .../BuildPlainEnumUpdateActionsTest.java | 299 +- .../types/utils/FieldDefinitionFixtures.java | 113 +- .../FieldDefinitionUpdateActionUtilsTest.java | 538 +-- .../utils/TypeUpdateActionUtilsTest.java | 112 +- 560 files changed, 97842 insertions(+), 90495 deletions(-) diff --git a/src/benchmark/java/com/commercetools/sync/benchmark/BenchmarkUtils.java b/src/benchmark/java/com/commercetools/sync/benchmark/BenchmarkUtils.java index 379e7b747b..a7c18726a6 100644 --- a/src/benchmark/java/com/commercetools/sync/benchmark/BenchmarkUtils.java +++ b/src/benchmark/java/com/commercetools/sync/benchmark/BenchmarkUtils.java @@ -1,119 +1,122 @@ package com.commercetools.sync.benchmark; +import static java.util.Optional.ofNullable; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; - -import javax.annotation.Nonnull; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; - -import static java.util.Optional.ofNullable; +import javax.annotation.Nonnull; final class BenchmarkUtils { - private static final String BENCHMARK_RESULTS_FILE_NAME = "benchmarks.json"; - private static final String BENCHMARK_RESULTS_FILE_DIR = ofNullable(System.getenv("CI_BUILD_DIR")) - .map(path -> path + "/tmp_git_dir/benchmarks/").orElse(""); - private static final String BENCHMARK_RESULTS_FILE_PATH = BENCHMARK_RESULTS_FILE_DIR + BENCHMARK_RESULTS_FILE_NAME; - private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8; - private static final String EXECUTION_TIME = "executionTime"; - private static final String BRANCH_NAME = ofNullable(System.getenv("TRAVIS_COMMIT")) - .map(commitMessage -> commitMessage.substring(0, 7)) //Use smaller commit sha - .orElse("dev-local"); - - static final String PRODUCT_SYNC = "productSync"; - static final String INVENTORY_SYNC = "inventorySync"; - static final String CATEGORY_SYNC = "categorySync"; - static final String TYPE_SYNC = "typeSync"; - static final String PRODUCT_TYPE_SYNC = "productTypeSync"; - static final String CART_DISCOUNT_SYNC = "cartDiscountSync"; - static final String CREATES_ONLY = "createsOnly"; - static final String UPDATES_ONLY = "updatesOnly"; - static final String CREATES_AND_UPDATES = "mix"; - static final int NUMBER_OF_RESOURCE_UNDER_TEST = 1000; - static final String THRESHOLD_EXCEEDED_ERROR = "Total execution time of benchmark '%d' took longer than allowed" - + " threshold of '%d'."; - - - static void saveNewResult(@Nonnull final String sync, @Nonnull final String benchmark, final double newResult) - throws IOException { - - final JsonNode rootNode = new ObjectMapper().readTree(getFileContent()); - final JsonNode withNewResult = addNewResult(rootNode, sync, benchmark, newResult); - writeToFile(withNewResult.toString()); - } - - @Nonnull - private static String getFileContent() throws IOException { - - final byte[] fileBytes = Files.readAllBytes(Paths.get(BENCHMARK_RESULTS_FILE_PATH)); - return new String(fileBytes, UTF8_CHARSET); + private static final String BENCHMARK_RESULTS_FILE_NAME = "benchmarks.json"; + private static final String BENCHMARK_RESULTS_FILE_DIR = + ofNullable(System.getenv("CI_BUILD_DIR")) + .map(path -> path + "/tmp_git_dir/benchmarks/") + .orElse(""); + private static final String BENCHMARK_RESULTS_FILE_PATH = + BENCHMARK_RESULTS_FILE_DIR + BENCHMARK_RESULTS_FILE_NAME; + private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8; + private static final String EXECUTION_TIME = "executionTime"; + private static final String BRANCH_NAME = + ofNullable(System.getenv("TRAVIS_COMMIT")) + .map(commitMessage -> commitMessage.substring(0, 7)) // Use smaller commit sha + .orElse("dev-local"); + + static final String PRODUCT_SYNC = "productSync"; + static final String INVENTORY_SYNC = "inventorySync"; + static final String CATEGORY_SYNC = "categorySync"; + static final String TYPE_SYNC = "typeSync"; + static final String PRODUCT_TYPE_SYNC = "productTypeSync"; + static final String CART_DISCOUNT_SYNC = "cartDiscountSync"; + static final String CREATES_ONLY = "createsOnly"; + static final String UPDATES_ONLY = "updatesOnly"; + static final String CREATES_AND_UPDATES = "mix"; + static final int NUMBER_OF_RESOURCE_UNDER_TEST = 1000; + static final String THRESHOLD_EXCEEDED_ERROR = + "Total execution time of benchmark '%d' took longer than allowed" + " threshold of '%d'."; + + static void saveNewResult( + @Nonnull final String sync, @Nonnull final String benchmark, final double newResult) + throws IOException { + + final JsonNode rootNode = new ObjectMapper().readTree(getFileContent()); + final JsonNode withNewResult = addNewResult(rootNode, sync, benchmark, newResult); + writeToFile(withNewResult.toString()); + } + + @Nonnull + private static String getFileContent() throws IOException { + + final byte[] fileBytes = Files.readAllBytes(Paths.get(BENCHMARK_RESULTS_FILE_PATH)); + return new String(fileBytes, UTF8_CHARSET); + } + + @Nonnull + private static JsonNode addNewResult( + @Nonnull final JsonNode originalRoot, + @Nonnull final String sync, + @Nonnull final String benchmark, + final double newResult) { + + ObjectNode rootNode = (ObjectNode) originalRoot; + ObjectNode branchNode = (ObjectNode) rootNode.get(BRANCH_NAME); + + // If version doesn't exist yet, create a new JSON object for the new version. + if (branchNode == null) { + branchNode = createVersionNode(); + rootNode.set(BRANCH_NAME, branchNode); } - @Nonnull - private static JsonNode addNewResult(@Nonnull final JsonNode originalRoot, - @Nonnull final String sync, - @Nonnull final String benchmark, - final double newResult) { + final ObjectNode syncNode = (ObjectNode) branchNode.get(sync); + final ObjectNode benchmarkNode = (ObjectNode) syncNode.get(benchmark); - ObjectNode rootNode = (ObjectNode) originalRoot; - ObjectNode branchNode = (ObjectNode) rootNode.get(BRANCH_NAME); + // Add new result. + benchmarkNode.set(EXECUTION_TIME, JsonNodeFactory.instance.numberNode(newResult)); + return rootNode; + } - // If version doesn't exist yet, create a new JSON object for the new version. - if (branchNode == null) { - branchNode = createVersionNode(); - rootNode.set(BRANCH_NAME, branchNode); - } + @Nonnull + private static ObjectNode createVersionNode() { - final ObjectNode syncNode = (ObjectNode) branchNode.get(sync); - final ObjectNode benchmarkNode = (ObjectNode) syncNode.get(benchmark); + final ObjectNode newVersionNode = JsonNodeFactory.instance.objectNode(); + newVersionNode.set(PRODUCT_SYNC, createSyncNode()); + newVersionNode.set(INVENTORY_SYNC, createSyncNode()); + newVersionNode.set(CATEGORY_SYNC, createSyncNode()); + newVersionNode.set(PRODUCT_TYPE_SYNC, createSyncNode()); + newVersionNode.set(TYPE_SYNC, createSyncNode()); + newVersionNode.set(CART_DISCOUNT_SYNC, createSyncNode()); + return newVersionNode; + } - // Add new result. - benchmarkNode.set(EXECUTION_TIME, JsonNodeFactory.instance.numberNode(newResult)); - return rootNode; - } + @Nonnull + private static ObjectNode createSyncNode() { - @Nonnull - private static ObjectNode createVersionNode() { - - final ObjectNode newVersionNode = JsonNodeFactory.instance.objectNode(); - newVersionNode.set(PRODUCT_SYNC, createSyncNode()); - newVersionNode.set(INVENTORY_SYNC, createSyncNode()); - newVersionNode.set(CATEGORY_SYNC, createSyncNode()); - newVersionNode.set(PRODUCT_TYPE_SYNC, createSyncNode()); - newVersionNode.set(TYPE_SYNC, createSyncNode()); - newVersionNode.set(CART_DISCOUNT_SYNC, createSyncNode()); - return newVersionNode; - } + final ObjectNode newSyncNode = JsonNodeFactory.instance.objectNode(); + newSyncNode.set(CREATES_ONLY, createBenchmarkNode()); + newSyncNode.set(UPDATES_ONLY, createBenchmarkNode()); + newSyncNode.set(CREATES_AND_UPDATES, createBenchmarkNode()); - @Nonnull - private static ObjectNode createSyncNode() { + return newSyncNode; + } - final ObjectNode newSyncNode = JsonNodeFactory.instance.objectNode(); - newSyncNode.set(CREATES_ONLY, createBenchmarkNode()); - newSyncNode.set(UPDATES_ONLY, createBenchmarkNode()); - newSyncNode.set(CREATES_AND_UPDATES, createBenchmarkNode()); + @Nonnull + private static ObjectNode createBenchmarkNode() { - return newSyncNode; - } + final ObjectNode newBenchmarkNode = JsonNodeFactory.instance.objectNode(); + newBenchmarkNode.set(EXECUTION_TIME, JsonNodeFactory.instance.numberNode(0)); - @Nonnull - private static ObjectNode createBenchmarkNode() { + return newBenchmarkNode; + } - final ObjectNode newBenchmarkNode = JsonNodeFactory.instance.objectNode(); - newBenchmarkNode.set(EXECUTION_TIME, JsonNodeFactory.instance.numberNode(0)); + private static void writeToFile(@Nonnull final String content) throws IOException { + Files.write(Paths.get(BENCHMARK_RESULTS_FILE_PATH), content.getBytes(UTF8_CHARSET)); + } - return newBenchmarkNode; - } - - private static void writeToFile(@Nonnull final String content) throws IOException { - Files.write(Paths.get(BENCHMARK_RESULTS_FILE_PATH), content.getBytes(UTF8_CHARSET)); - } - - private BenchmarkUtils() { - } + private BenchmarkUtils() {} } diff --git a/src/benchmark/java/com/commercetools/sync/benchmark/CartDiscountSyncBenchmark.java b/src/benchmark/java/com/commercetools/sync/benchmark/CartDiscountSyncBenchmark.java index 8a548517e8..2e877dedd7 100644 --- a/src/benchmark/java/com/commercetools/sync/benchmark/CartDiscountSyncBenchmark.java +++ b/src/benchmark/java/com/commercetools/sync/benchmark/CartDiscountSyncBenchmark.java @@ -1,5 +1,27 @@ package com.commercetools.sync.benchmark; +import static com.commercetools.sync.benchmark.BenchmarkUtils.CART_DISCOUNT_SYNC; +import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; +import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; +import static com.commercetools.sync.benchmark.BenchmarkUtils.NUMBER_OF_RESOURCE_UNDER_TEST; +import static com.commercetools.sync.benchmark.BenchmarkUtils.THRESHOLD_EXCEEDED_ERROR; +import static com.commercetools.sync.benchmark.BenchmarkUtils.UPDATES_ONLY; +import static com.commercetools.sync.benchmark.BenchmarkUtils.saveNewResult; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_CART_PREDICATE_1; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_NAME_1; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_TARGET_1; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_VALUE_1; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.JANUARY_FROM; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.JANUARY_UNTIL; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.deleteCartDiscounts; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.getSortOrders; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.lang.String.format; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.cartdiscounts.CartDiscountSync; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; @@ -17,11 +39,6 @@ import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.queries.QueryPredicate; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -30,255 +47,256 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.stream.IntStream; - -import static com.commercetools.sync.benchmark.BenchmarkUtils.CART_DISCOUNT_SYNC; -import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; -import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; -import static com.commercetools.sync.benchmark.BenchmarkUtils.NUMBER_OF_RESOURCE_UNDER_TEST; -import static com.commercetools.sync.benchmark.BenchmarkUtils.THRESHOLD_EXCEEDED_ERROR; -import static com.commercetools.sync.benchmark.BenchmarkUtils.UPDATES_ONLY; -import static com.commercetools.sync.benchmark.BenchmarkUtils.saveNewResult; -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_CART_PREDICATE_1; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_NAME_1; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_TARGET_1; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_VALUE_1; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.JANUARY_FROM; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.JANUARY_UNTIL; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.deleteCartDiscounts; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.getSortOrders; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.lang.String.format; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CartDiscountSyncBenchmark { - private CartDiscountSyncOptions cartDiscountSyncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - @AfterAll - static void tearDown() { - deleteCartDiscounts(CTP_TARGET_CLIENT); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteCartDiscounts(CTP_TARGET_CLIENT); - cartDiscountSyncOptions = buildSyncOptions(); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - } - - private CartDiscountSyncOptions buildSyncOptions() { - final QuadConsumer, Optional, - List>> errorCallback - = (exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }; - - final TriConsumer, Optional> warningCallBack - = (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - - return CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallback) - .warningCallback(warningCallBack) - .build(); - } - - @Test - void sync_NewCartDiscounts_ShouldCreateCartDiscounts() throws IOException { - // preparation - final List cartDiscountDrafts = buildCartDiscountDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - - // benchmark - final long beforeSyncTime = System.currentTimeMillis(); - final CartDiscountSyncStatistics syncStatistics = executeBlocking(cartDiscountSync.sync(cartDiscountDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - - // assert on threshold (based on history of benchmarks; highest was 10496 ms) - final int threshold = 23000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (total number of existing cart discounts) - final CompletableFuture totalNumberOfCartDiscounts = - CTP_TARGET_CLIENT.execute(CartDiscountQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfCartDiscounts); - assertThat(totalNumberOfCartDiscounts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(CART_DISCOUNT_SYNC, CREATES_ONLY, totalTime); - } - - @Test - void sync_ExistingCartDiscounts_ShouldUpdateCartDiscounts() throws IOException { - // preparation - final List cartDiscountDrafts = buildCartDiscountDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Create cart discount drafts to target project - // with different stacking mode (STOP_AFTER_THIS_DISCOUNT) - CompletableFuture.allOf( - cartDiscountDrafts.stream() - .map(CartDiscountDraftBuilder::of) - .map(builder -> builder.stackingMode(StackingMode.STOP_AFTER_THIS_DISCOUNT)) - .map(CartDiscountDraftBuilder::build) - .map(draft -> CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - - - // benchmark - final long beforeSyncTime = System.currentTimeMillis(); - // Update cart discount drafts to target project with different stacking mode (STACKING) - final CartDiscountSyncStatistics syncStatistics = executeBlocking(cartDiscountSync.sync(cartDiscountDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - // assert on threshold (based on history of benchmarks; highest was 11263 ms) - final int threshold = 23000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (number of updated cart discount) - final CompletableFuture totalNumberOfUpdatedCartDiscounts = - CTP_TARGET_CLIENT.execute( - CartDiscountQuery.of().withPredicates(QueryPredicate.of("stackingMode = \"Stacking\""))) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - - executeBlocking(totalNumberOfUpdatedCartDiscounts); - assertThat(totalNumberOfUpdatedCartDiscounts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert actual state of CTP project (total number of existing cart discounts) - final CompletableFuture totalNumberOfCartDiscounts = - CTP_TARGET_CLIENT.execute(CartDiscountQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - executeBlocking(totalNumberOfCartDiscounts); - assertThat(totalNumberOfCartDiscounts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert statistics - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, 0, NUMBER_OF_RESOURCE_UNDER_TEST, 0); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(CART_DISCOUNT_SYNC, UPDATES_ONLY, totalTime); - } - - @Test - void sync_WithSomeExistingCartDiscounts_ShouldSyncCartDiscounts() throws IOException { - // preparation - final List cartDiscountDrafts = buildCartDiscountDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - final int halfNumberOfDrafts = cartDiscountDrafts.size() / 2; - final List firstHalf = cartDiscountDrafts.subList(0, halfNumberOfDrafts); - - // Create first half of cart discount drafts to target project - // with different stacking mode (STOP_AFTER_THIS_DISCOUNT) - CompletableFuture.allOf( - firstHalf.stream() - .map(CartDiscountDraftBuilder::of) - .map(builder -> builder.stackingMode(StackingMode.STOP_AFTER_THIS_DISCOUNT)) - .map(CartDiscountDraftBuilder::build) - .map(draft -> CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - - - // benchmark - final long beforeSyncTime = System.currentTimeMillis(); - // Create half of the cart discount drafts to target project - // and update half of the cart discount drafts to target project with different stacking mode (STACKING) - final CartDiscountSyncStatistics syncStatistics = executeBlocking(cartDiscountSync.sync(cartDiscountDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - - // assert on threshold (based on history of benchmarks; highest was 11277 ms) - final int threshold = 23000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (number of updated cart discount) - final CompletableFuture totalNumberOfUpdatedCartDiscounts = - CTP_TARGET_CLIENT.execute( - CartDiscountQuery.of().withPredicates( - QueryPredicate.of("stackingMode = \"StopAfterThisDiscount\""))) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfUpdatedCartDiscounts); - assertThat(totalNumberOfUpdatedCartDiscounts).isCompletedWithValue(0); - - - // Assert actual state of CTP project (total number of existing cart discounts) - final CompletableFuture totalNumberOfCartDiscounts = - CTP_TARGET_CLIENT.execute(CartDiscountQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - executeBlocking(totalNumberOfCartDiscounts); - assertThat(totalNumberOfCartDiscounts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert statistics - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, halfNumberOfDrafts, halfNumberOfDrafts, 0); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(CART_DISCOUNT_SYNC, CREATES_AND_UPDATES, totalTime); - } - - @Nonnull - private static List buildCartDiscountDrafts(final int numberOfCartDiscounts) { - final List sortOrders = getSortOrders(numberOfCartDiscounts); - return IntStream - .range(0, numberOfCartDiscounts) - .mapToObj(i -> - CartDiscountDraftBuilder.of( - CART_DISCOUNT_NAME_1, - CART_DISCOUNT_CART_PREDICATE_1, - CART_DISCOUNT_VALUE_1, - CART_DISCOUNT_TARGET_1, - sortOrders.get(i), - false) - .key(format("key__%d", i)) - .isActive(false) - .description(LocalizedString.of(Locale.ENGLISH, format("description__%d", i))) - .validFrom(JANUARY_FROM) - .validUntil(JANUARY_UNTIL) - .build()) - .collect(toList()); - } + private CartDiscountSyncOptions cartDiscountSyncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + @AfterAll + static void tearDown() { + deleteCartDiscounts(CTP_TARGET_CLIENT); + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteCartDiscounts(CTP_TARGET_CLIENT); + cartDiscountSyncOptions = buildSyncOptions(); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + } + + private CartDiscountSyncOptions buildSyncOptions() { + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallback = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }; + + final TriConsumer, Optional> + warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + + return CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback(errorCallback) + .warningCallback(warningCallBack) + .build(); + } + + @Test + void sync_NewCartDiscounts_ShouldCreateCartDiscounts() throws IOException { + // preparation + final List cartDiscountDrafts = + buildCartDiscountDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + + // benchmark + final long beforeSyncTime = System.currentTimeMillis(); + final CartDiscountSyncStatistics syncStatistics = + executeBlocking(cartDiscountSync.sync(cartDiscountDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was 10496 ms) + final int threshold = 23000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (total number of existing cart discounts) + final CompletableFuture totalNumberOfCartDiscounts = + CTP_TARGET_CLIENT + .execute(CartDiscountQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfCartDiscounts); + assertThat(totalNumberOfCartDiscounts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(CART_DISCOUNT_SYNC, CREATES_ONLY, totalTime); + } + + @Test + void sync_ExistingCartDiscounts_ShouldUpdateCartDiscounts() throws IOException { + // preparation + final List cartDiscountDrafts = + buildCartDiscountDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Create cart discount drafts to target project + // with different stacking mode (STOP_AFTER_THIS_DISCOUNT) + CompletableFuture.allOf( + cartDiscountDrafts.stream() + .map(CartDiscountDraftBuilder::of) + .map(builder -> builder.stackingMode(StackingMode.STOP_AFTER_THIS_DISCOUNT)) + .map(CartDiscountDraftBuilder::build) + .map(draft -> CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + + // benchmark + final long beforeSyncTime = System.currentTimeMillis(); + // Update cart discount drafts to target project with different stacking mode (STACKING) + final CartDiscountSyncStatistics syncStatistics = + executeBlocking(cartDiscountSync.sync(cartDiscountDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was 11263 ms) + final int threshold = 23000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (number of updated cart discount) + final CompletableFuture totalNumberOfUpdatedCartDiscounts = + CTP_TARGET_CLIENT + .execute( + CartDiscountQuery.of() + .withPredicates(QueryPredicate.of("stackingMode = \"Stacking\""))) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfUpdatedCartDiscounts); + assertThat(totalNumberOfUpdatedCartDiscounts) + .isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert actual state of CTP project (total number of existing cart discounts) + final CompletableFuture totalNumberOfCartDiscounts = + CTP_TARGET_CLIENT + .execute(CartDiscountQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + executeBlocking(totalNumberOfCartDiscounts); + assertThat(totalNumberOfCartDiscounts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert statistics + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, 0, NUMBER_OF_RESOURCE_UNDER_TEST, 0); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(CART_DISCOUNT_SYNC, UPDATES_ONLY, totalTime); + } + + @Test + void sync_WithSomeExistingCartDiscounts_ShouldSyncCartDiscounts() throws IOException { + // preparation + final List cartDiscountDrafts = + buildCartDiscountDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + final int halfNumberOfDrafts = cartDiscountDrafts.size() / 2; + final List firstHalf = cartDiscountDrafts.subList(0, halfNumberOfDrafts); + + // Create first half of cart discount drafts to target project + // with different stacking mode (STOP_AFTER_THIS_DISCOUNT) + CompletableFuture.allOf( + firstHalf.stream() + .map(CartDiscountDraftBuilder::of) + .map(builder -> builder.stackingMode(StackingMode.STOP_AFTER_THIS_DISCOUNT)) + .map(CartDiscountDraftBuilder::build) + .map(draft -> CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + + // benchmark + final long beforeSyncTime = System.currentTimeMillis(); + // Create half of the cart discount drafts to target project + // and update half of the cart discount drafts to target project with different stacking mode + // (STACKING) + final CartDiscountSyncStatistics syncStatistics = + executeBlocking(cartDiscountSync.sync(cartDiscountDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was 11277 ms) + final int threshold = 23000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (number of updated cart discount) + final CompletableFuture totalNumberOfUpdatedCartDiscounts = + CTP_TARGET_CLIENT + .execute( + CartDiscountQuery.of() + .withPredicates(QueryPredicate.of("stackingMode = \"StopAfterThisDiscount\""))) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfUpdatedCartDiscounts); + assertThat(totalNumberOfUpdatedCartDiscounts).isCompletedWithValue(0); + + // Assert actual state of CTP project (total number of existing cart discounts) + final CompletableFuture totalNumberOfCartDiscounts = + CTP_TARGET_CLIENT + .execute(CartDiscountQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + executeBlocking(totalNumberOfCartDiscounts); + assertThat(totalNumberOfCartDiscounts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert statistics + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, halfNumberOfDrafts, halfNumberOfDrafts, 0); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(CART_DISCOUNT_SYNC, CREATES_AND_UPDATES, totalTime); + } + + @Nonnull + private static List buildCartDiscountDrafts(final int numberOfCartDiscounts) { + final List sortOrders = getSortOrders(numberOfCartDiscounts); + return IntStream.range(0, numberOfCartDiscounts) + .mapToObj( + i -> + CartDiscountDraftBuilder.of( + CART_DISCOUNT_NAME_1, + CART_DISCOUNT_CART_PREDICATE_1, + CART_DISCOUNT_VALUE_1, + CART_DISCOUNT_TARGET_1, + sortOrders.get(i), + false) + .key(format("key__%d", i)) + .isActive(false) + .description(LocalizedString.of(Locale.ENGLISH, format("description__%d", i))) + .validFrom(JANUARY_FROM) + .validUntil(JANUARY_UNTIL) + .build()) + .collect(toList()); + } } diff --git a/src/benchmark/java/com/commercetools/sync/benchmark/CategorySyncBenchmark.java b/src/benchmark/java/com/commercetools/sync/benchmark/CategorySyncBenchmark.java index 98d5e647be..369a7e3829 100644 --- a/src/benchmark/java/com/commercetools/sync/benchmark/CategorySyncBenchmark.java +++ b/src/benchmark/java/com/commercetools/sync/benchmark/CategorySyncBenchmark.java @@ -1,35 +1,33 @@ package com.commercetools.sync.benchmark; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Disabled; - -import java.io.IOException; - import static com.commercetools.sync.benchmark.BenchmarkUtils.CATEGORY_SYNC; import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; import static com.commercetools.sync.benchmark.BenchmarkUtils.UPDATES_ONLY; import static com.commercetools.sync.benchmark.BenchmarkUtils.saveNewResult; -@Disabled -class CategorySyncBenchmark { - - @Test - void sync_NewCategories_ShouldCreateCategories() throws IOException { - // TODO: SHOULD BE IMPLEMENTED. - saveNewResult(CATEGORY_SYNC, CREATES_ONLY, 20000); - } - @Test - void sync_ExistingCategories_ShouldUpdateCategories() throws IOException { - // TODO: SHOULD BE IMPLEMENTED. - saveNewResult(CATEGORY_SYNC, UPDATES_ONLY, 10000); - } +import java.io.IOException; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; - @Test - void sync_WithSomeExistingCategories_ShouldSyncCategories() throws IOException { - // TODO: SHOULD BE IMPLEMENTED. - saveNewResult(CATEGORY_SYNC, CREATES_AND_UPDATES, 30000); - } +@Disabled +class CategorySyncBenchmark { + @Test + void sync_NewCategories_ShouldCreateCategories() throws IOException { + // TODO: SHOULD BE IMPLEMENTED. + saveNewResult(CATEGORY_SYNC, CREATES_ONLY, 20000); + } + + @Test + void sync_ExistingCategories_ShouldUpdateCategories() throws IOException { + // TODO: SHOULD BE IMPLEMENTED. + saveNewResult(CATEGORY_SYNC, UPDATES_ONLY, 10000); + } + + @Test + void sync_WithSomeExistingCategories_ShouldSyncCategories() throws IOException { + // TODO: SHOULD BE IMPLEMENTED. + saveNewResult(CATEGORY_SYNC, CREATES_AND_UPDATES, 30000); + } } diff --git a/src/benchmark/java/com/commercetools/sync/benchmark/InventorySyncBenchmark.java b/src/benchmark/java/com/commercetools/sync/benchmark/InventorySyncBenchmark.java index d72744810c..635f8e06f9 100644 --- a/src/benchmark/java/com/commercetools/sync/benchmark/InventorySyncBenchmark.java +++ b/src/benchmark/java/com/commercetools/sync/benchmark/InventorySyncBenchmark.java @@ -1,26 +1,5 @@ package com.commercetools.sync.benchmark; -import com.commercetools.sync.inventories.InventorySync; -import com.commercetools.sync.inventories.InventorySyncOptions; -import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; -import com.commercetools.sync.inventories.helpers.InventorySyncStatistics; -import io.sphere.sdk.inventory.InventoryEntryDraft; -import io.sphere.sdk.inventory.InventoryEntryDraftBuilder; -import io.sphere.sdk.inventory.queries.InventoryEntryQuery; -import io.sphere.sdk.queries.PagedQueryResult; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import java.io.IOException; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; import static com.commercetools.sync.benchmark.BenchmarkUtils.INVENTORY_SYNC; @@ -37,84 +16,104 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import com.commercetools.sync.inventories.InventorySync; +import com.commercetools.sync.inventories.InventorySyncOptions; +import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; +import com.commercetools.sync.inventories.helpers.InventorySyncStatistics; +import io.sphere.sdk.inventory.InventoryEntryDraft; +import io.sphere.sdk.inventory.InventoryEntryDraftBuilder; +import io.sphere.sdk.inventory.queries.InventoryEntryQuery; +import io.sphere.sdk.queries.PagedQueryResult; +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; class InventorySyncBenchmark { - @BeforeEach - void setup() { - deleteInventoryEntries(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - deleteChannels(CTP_TARGET_CLIENT); - } - - @AfterAll - static void tearDown() { - deleteInventoryEntries(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - deleteChannels(CTP_TARGET_CLIENT); - } - - @Test - void sync_NewInventories_ShouldCreateInventories() throws IOException { - // preparation - final List inventoryEntryDrafts = buildInventoryDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - - // benchmark - final long beforeSync = System.currentTimeMillis(); - final InventorySyncStatistics inventorySyncStatistics = - executeBlocking(inventorySync.sync(inventoryEntryDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSync; - - - // assert on threshold (based on history of benchmarks; highest was ~9 seconds) - final int threshold = 18000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (total number of existing inventories) - final CompletableFuture totalNumberOfInventories = - CTP_TARGET_CLIENT.execute(InventoryEntryQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfInventories); - assertThat(totalNumberOfInventories).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - - // Assert on sync statistics - assertThat(inventorySyncStatistics) - .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); - - saveNewResult(INVENTORY_SYNC, CREATES_ONLY, totalTime); - } - - @Disabled - @Test - void sync_ExistingInventories_ShouldUpdateInventories() throws IOException { - // TODO: SHOULD BE IMPLEMENTED. - saveNewResult(INVENTORY_SYNC, UPDATES_ONLY, 50000); - } - - @Disabled - @Test - void sync_WithSomeExistingInventories_ShouldSyncInventories() throws IOException { - // TODO: SHOULD BE IMPLEMENTED. - saveNewResult(INVENTORY_SYNC, CREATES_AND_UPDATES, 30000); - } - - @Nonnull - private List buildInventoryDrafts(final int numberOfDrafts) { - final ZonedDateTime expectedDelivery = ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); - - final List resourceDrafts = new ArrayList<>(); - for (int i = 0; i < numberOfDrafts; i++) { - resourceDrafts.add( - InventoryEntryDraftBuilder.of("sku_" + i, 1L, expectedDelivery, 1, null) - .build()); - } - return resourceDrafts; + @BeforeEach + void setup() { + deleteInventoryEntries(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + deleteChannels(CTP_TARGET_CLIENT); + } + + @AfterAll + static void tearDown() { + deleteInventoryEntries(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + deleteChannels(CTP_TARGET_CLIENT); + } + + @Test + void sync_NewInventories_ShouldCreateInventories() throws IOException { + // preparation + final List inventoryEntryDrafts = + buildInventoryDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + + // benchmark + final long beforeSync = System.currentTimeMillis(); + final InventorySyncStatistics inventorySyncStatistics = + executeBlocking(inventorySync.sync(inventoryEntryDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSync; + + // assert on threshold (based on history of benchmarks; highest was ~9 seconds) + final int threshold = 18000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (total number of existing inventories) + final CompletableFuture totalNumberOfInventories = + CTP_TARGET_CLIENT + .execute(InventoryEntryQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfInventories); + assertThat(totalNumberOfInventories).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert on sync statistics + assertThat(inventorySyncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); + + saveNewResult(INVENTORY_SYNC, CREATES_ONLY, totalTime); + } + + @Disabled + @Test + void sync_ExistingInventories_ShouldUpdateInventories() throws IOException { + // TODO: SHOULD BE IMPLEMENTED. + saveNewResult(INVENTORY_SYNC, UPDATES_ONLY, 50000); + } + + @Disabled + @Test + void sync_WithSomeExistingInventories_ShouldSyncInventories() throws IOException { + // TODO: SHOULD BE IMPLEMENTED. + saveNewResult(INVENTORY_SYNC, CREATES_AND_UPDATES, 30000); + } + + @Nonnull + private List buildInventoryDrafts(final int numberOfDrafts) { + final ZonedDateTime expectedDelivery = + ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); + + final List resourceDrafts = new ArrayList<>(); + for (int i = 0; i < numberOfDrafts; i++) { + resourceDrafts.add( + InventoryEntryDraftBuilder.of("sku_" + i, 1L, expectedDelivery, 1, null).build()); } + return resourceDrafts; + } } diff --git a/src/benchmark/java/com/commercetools/sync/benchmark/ProductSyncBenchmark.java b/src/benchmark/java/com/commercetools/sync/benchmark/ProductSyncBenchmark.java index fab1dc2c67..e205179208 100644 --- a/src/benchmark/java/com/commercetools/sync/benchmark/ProductSyncBenchmark.java +++ b/src/benchmark/java/com/commercetools/sync/benchmark/ProductSyncBenchmark.java @@ -1,5 +1,26 @@ package com.commercetools.sync.benchmark; +import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; +import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; +import static com.commercetools.sync.benchmark.BenchmarkUtils.NUMBER_OF_RESOURCE_UNDER_TEST; +import static com.commercetools.sync.benchmark.BenchmarkUtils.PRODUCT_SYNC; +import static com.commercetools.sync.benchmark.BenchmarkUtils.THRESHOLD_EXCEEDED_ERROR; +import static com.commercetools.sync.benchmark.BenchmarkUtils.UPDATES_ONLY; +import static com.commercetools.sync.benchmark.BenchmarkUtils.saveNewResult; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static java.util.Locale.ENGLISH; +import static org.assertj.core.api.Assertions.assertThat; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; @@ -20,262 +41,261 @@ import io.sphere.sdk.products.queries.ProductQuery; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.queries.PagedQueryResult; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; -import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; -import static com.commercetools.sync.benchmark.BenchmarkUtils.NUMBER_OF_RESOURCE_UNDER_TEST; -import static com.commercetools.sync.benchmark.BenchmarkUtils.PRODUCT_SYNC; -import static com.commercetools.sync.benchmark.BenchmarkUtils.THRESHOLD_EXCEEDED_ERROR; -import static com.commercetools.sync.benchmark.BenchmarkUtils.UPDATES_ONLY; -import static com.commercetools.sync.benchmark.BenchmarkUtils.saveNewResult; -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static java.util.Locale.ENGLISH; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncBenchmark { - private static ProductType productType; - private ProductSyncOptions syncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_TARGET_CLIENT); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final QuadConsumer, Optional, - List>> - errorCallback = (exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); + private static ProductType productType; + private ProductSyncOptions syncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, ENGLISH, OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_TARGET_CLIENT); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallback = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }; - final TriConsumer, Optional> warningCallback = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallback) - .warningCallback(warningCallback) - .build(); - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_NewProducts_ShouldCreateProducts() throws IOException { - final List productDrafts = buildProductDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Sync drafts - final ProductSync productSync = new ProductSync(syncOptions); - - final long beforeSyncTime = System.currentTimeMillis(); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(productDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - // assert on threshold (based on history of benchmarks, highest was ~16 seconds) - final int threshold = 32000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (total number of existing products) - final CompletableFuture totalNumberOfProducts = - CTP_TARGET_CLIENT.execute(ProductProjectionQuery.ofStaged()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - executeBlocking(totalNumberOfProducts); - assertThat(totalNumberOfProducts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(PRODUCT_SYNC, CREATES_ONLY, totalTime); - } - - @Test - void sync_ExistingProducts_ShouldUpdateProducts() throws IOException { - final List productDrafts = buildProductDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - // Create drafts to target project with different descriptions - CompletableFuture.allOf(productDrafts.stream() - .map(ProductDraftBuilder::of) - .map(builder -> builder.description( - ofEnglish("oldDescription"))) - .map(builder -> builder.productType(productType.toReference())) - .map(ProductDraftBuilder::build) - .map(draft -> CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(draft))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - // Sync new drafts - final ProductSync productSync = new ProductSync(syncOptions); - - final long beforeSyncTime = System.currentTimeMillis(); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(productDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - - // assert on threshold (based on history of benchmarks; highest was ~19 seconds) - final int threshold = 38000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (number of updated products) - final CompletableFuture totalNumberOfUpdatedProducts = - CTP_TARGET_CLIENT.execute(ProductQuery.of() - .withPredicates(p -> p.masterData().staged() - .description().locale(ENGLISH) - .is("newDescription"))) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfUpdatedProducts); - assertThat(totalNumberOfUpdatedProducts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert actual state of CTP project (total number of existing products) - final CompletableFuture totalNumberOfProducts = - CTP_TARGET_CLIENT.execute(ProductProjectionQuery.ofStaged()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - executeBlocking(totalNumberOfProducts); - assertThat(totalNumberOfProducts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert statistics - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, 0, NUMBER_OF_RESOURCE_UNDER_TEST, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(PRODUCT_SYNC, UPDATES_ONLY, totalTime); - } - - @Test - void sync_WithSomeExistingProducts_ShouldSyncProducts() throws IOException { - final List productDrafts = buildProductDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - final int halfNumberOfDrafts = productDrafts.size() / 2; - final List firstHalf = productDrafts.subList(0, halfNumberOfDrafts); - - // Create first half of drafts to target project with different description - CompletableFuture.allOf(firstHalf.stream() - .map(ProductDraftBuilder::of) - .map(builder -> builder.description( - ofEnglish("oldDescription"))) - .map(builder -> builder.productType(productType.toReference())) - .map(ProductDraftBuilder::build) - .map(draft -> CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(draft))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - // Sync new drafts - final ProductSync productSync = new ProductSync(syncOptions); - - final long beforeSyncTime = System.currentTimeMillis(); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(productDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - - // assert on threshold (based on history of benchmarks; highest was ~19 seconds) - final int threshold = 38000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (number of updated products) - final CompletableFuture totalNumberOfUpdatedProducts = - CTP_TARGET_CLIENT.execute(ProductQuery.of() - .withPredicates(p -> p.masterData().staged() - .description().locale(ENGLISH) - .is("oldDescription"))) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfUpdatedProducts); - assertThat(totalNumberOfUpdatedProducts).isCompletedWithValue(0); - - // Assert actual state of CTP project (total number of existing products) - final CompletableFuture totalNumberOfProducts = - CTP_TARGET_CLIENT.execute(ProductQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - executeBlocking(totalNumberOfProducts); - assertThat(totalNumberOfProducts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - - // Assert statistics - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, halfNumberOfDrafts, halfNumberOfDrafts, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(PRODUCT_SYNC, CREATES_AND_UPDATES, totalTime); - } - - @Nonnull - private List buildProductDrafts(final int numberOfProducts) { - final List productDrafts = new ArrayList<>(); - final ResourceIdentifier draftsProductType = ResourceIdentifier.ofKey(productType.getKey()); - for (int i = 0; i < numberOfProducts; i++) { - final ProductVariantDraft masterVariantDraft = ProductVariantDraftBuilder.of() - .key("masterVariantKey_" + i) - .sku("sku_" + i) - .build(); - final ProductDraft productDraft = ProductDraftBuilder - .of(draftsProductType, ofEnglish("name_" + i), ofEnglish("slug_" + i), masterVariantDraft) - .description(ofEnglish("newDescription")) - .key("productKey_" + i) - .build(); - productDrafts.add(productDraft); - } - return productDrafts; + final TriConsumer, Optional> warningCallback = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback(errorCallback) + .warningCallback(warningCallback) + .build(); + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_NewProducts_ShouldCreateProducts() throws IOException { + final List productDrafts = buildProductDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Sync drafts + final ProductSync productSync = new ProductSync(syncOptions); + + final long beforeSyncTime = System.currentTimeMillis(); + final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(productDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks, highest was ~16 seconds) + final int threshold = 32000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (total number of existing products) + final CompletableFuture totalNumberOfProducts = + CTP_TARGET_CLIENT + .execute(ProductProjectionQuery.ofStaged()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + executeBlocking(totalNumberOfProducts); + assertThat(totalNumberOfProducts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(PRODUCT_SYNC, CREATES_ONLY, totalTime); + } + + @Test + void sync_ExistingProducts_ShouldUpdateProducts() throws IOException { + final List productDrafts = buildProductDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + // Create drafts to target project with different descriptions + CompletableFuture.allOf( + productDrafts.stream() + .map(ProductDraftBuilder::of) + .map(builder -> builder.description(ofEnglish("oldDescription"))) + .map(builder -> builder.productType(productType.toReference())) + .map(ProductDraftBuilder::build) + .map(draft -> CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(draft))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + + // Sync new drafts + final ProductSync productSync = new ProductSync(syncOptions); + + final long beforeSyncTime = System.currentTimeMillis(); + final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(productDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was ~19 seconds) + final int threshold = 38000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (number of updated products) + final CompletableFuture totalNumberOfUpdatedProducts = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of() + .withPredicates( + p -> + p.masterData() + .staged() + .description() + .locale(ENGLISH) + .is("newDescription"))) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfUpdatedProducts); + assertThat(totalNumberOfUpdatedProducts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert actual state of CTP project (total number of existing products) + final CompletableFuture totalNumberOfProducts = + CTP_TARGET_CLIENT + .execute(ProductProjectionQuery.ofStaged()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + executeBlocking(totalNumberOfProducts); + assertThat(totalNumberOfProducts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert statistics + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, 0, NUMBER_OF_RESOURCE_UNDER_TEST, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(PRODUCT_SYNC, UPDATES_ONLY, totalTime); + } + + @Test + void sync_WithSomeExistingProducts_ShouldSyncProducts() throws IOException { + final List productDrafts = buildProductDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + final int halfNumberOfDrafts = productDrafts.size() / 2; + final List firstHalf = productDrafts.subList(0, halfNumberOfDrafts); + + // Create first half of drafts to target project with different description + CompletableFuture.allOf( + firstHalf.stream() + .map(ProductDraftBuilder::of) + .map(builder -> builder.description(ofEnglish("oldDescription"))) + .map(builder -> builder.productType(productType.toReference())) + .map(ProductDraftBuilder::build) + .map(draft -> CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(draft))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + + // Sync new drafts + final ProductSync productSync = new ProductSync(syncOptions); + + final long beforeSyncTime = System.currentTimeMillis(); + final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(productDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was ~19 seconds) + final int threshold = 38000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (number of updated products) + final CompletableFuture totalNumberOfUpdatedProducts = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of() + .withPredicates( + p -> + p.masterData() + .staged() + .description() + .locale(ENGLISH) + .is("oldDescription"))) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfUpdatedProducts); + assertThat(totalNumberOfUpdatedProducts).isCompletedWithValue(0); + + // Assert actual state of CTP project (total number of existing products) + final CompletableFuture totalNumberOfProducts = + CTP_TARGET_CLIENT + .execute(ProductQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + executeBlocking(totalNumberOfProducts); + assertThat(totalNumberOfProducts).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert statistics + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, halfNumberOfDrafts, halfNumberOfDrafts, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(PRODUCT_SYNC, CREATES_AND_UPDATES, totalTime); + } + + @Nonnull + private List buildProductDrafts(final int numberOfProducts) { + final List productDrafts = new ArrayList<>(); + final ResourceIdentifier draftsProductType = + ResourceIdentifier.ofKey(productType.getKey()); + for (int i = 0; i < numberOfProducts; i++) { + final ProductVariantDraft masterVariantDraft = + ProductVariantDraftBuilder.of().key("masterVariantKey_" + i).sku("sku_" + i).build(); + final ProductDraft productDraft = + ProductDraftBuilder.of( + draftsProductType, + ofEnglish("name_" + i), + ofEnglish("slug_" + i), + masterVariantDraft) + .description(ofEnglish("newDescription")) + .key("productKey_" + i) + .build(); + productDrafts.add(productDraft); } + return productDrafts; + } } diff --git a/src/benchmark/java/com/commercetools/sync/benchmark/ProductTypeSyncBenchmark.java b/src/benchmark/java/com/commercetools/sync/benchmark/ProductTypeSyncBenchmark.java index 88f59fdc5c..66f7645d87 100644 --- a/src/benchmark/java/com/commercetools/sync/benchmark/ProductTypeSyncBenchmark.java +++ b/src/benchmark/java/com/commercetools/sync/benchmark/ProductTypeSyncBenchmark.java @@ -1,5 +1,21 @@ package com.commercetools.sync.benchmark; +import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; +import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; +import static com.commercetools.sync.benchmark.BenchmarkUtils.NUMBER_OF_RESOURCE_UNDER_TEST; +import static com.commercetools.sync.benchmark.BenchmarkUtils.PRODUCT_TYPE_SYNC; +import static com.commercetools.sync.benchmark.BenchmarkUtils.THRESHOLD_EXCEEDED_ERROR; +import static com.commercetools.sync.benchmark.BenchmarkUtils.UPDATES_ONLY; +import static com.commercetools.sync.benchmark.BenchmarkUtils.saveNewResult; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_1; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -16,11 +32,6 @@ import io.sphere.sdk.producttypes.commands.ProductTypeCreateCommand; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; import io.sphere.sdk.queries.PagedQueryResult; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -29,245 +40,256 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; -import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; -import static com.commercetools.sync.benchmark.BenchmarkUtils.NUMBER_OF_RESOURCE_UNDER_TEST; -import static com.commercetools.sync.benchmark.BenchmarkUtils.PRODUCT_TYPE_SYNC; -import static com.commercetools.sync.benchmark.BenchmarkUtils.THRESHOLD_EXCEEDED_ERROR; -import static com.commercetools.sync.benchmark.BenchmarkUtils.UPDATES_ONLY; -import static com.commercetools.sync.benchmark.BenchmarkUtils.saveNewResult; -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_1; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductTypeSyncBenchmark { - private ProductTypeSyncOptions productTypeSyncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - @AfterAll - static void tearDown() { - deleteProductTypes(CTP_TARGET_CLIENT); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteProductTypes(CTP_TARGET_CLIENT); - productTypeSyncOptions = buildSyncOptions(); - } - - @Nonnull - private ProductTypeSyncOptions buildSyncOptions() { - final QuadConsumer, Optional, - List>> errorCallBack = - (exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }; - final TriConsumer, Optional> warningCallBack = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - return ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallBack) - .warningCallback(warningCallBack) - .build(); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - } - - @Test - void sync_NewProductTypes_ShouldCreateProductTypes() throws IOException { - // preparation - final List productTypeDrafts = buildProductTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // benchmark - final long beforeSyncTime = System.currentTimeMillis(); - final ProductTypeSyncStatistics syncStatistics = executeBlocking(productTypeSync.sync(productTypeDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - // assert on threshold (based on history of benchmarks; highest was ~12 seconds) - final int threshold = 24000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (total number of existing product types) - final CompletableFuture totalNumberOfProductTypes = - CTP_TARGET_CLIENT.execute(ProductTypeQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfProductTypes); - assertThat(totalNumberOfProductTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(PRODUCT_TYPE_SYNC, CREATES_ONLY, totalTime); - } - - @Test - void sync_ExistingProductTypes_ShouldUpdateProductTypes() throws IOException { - // preparation - final List productTypeDrafts = buildProductTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - // Create drafts to target project with different attribute definition name - CompletableFuture.allOf( - productTypeDrafts.stream() - .map(ProductTypeDraftBuilder::of) - .map(ProductTypeSyncBenchmark::applyAttributeDefinitionNameChange) - .map(ProductTypeDraftBuilder::build) - .map(draft -> CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(draft))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // benchmark - final long beforeSyncTime = System.currentTimeMillis(); - final ProductTypeSyncStatistics syncStatistics = executeBlocking(productTypeSync.sync(productTypeDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - // assert on threshold (based on history of benchmarks; highest was ~13 seconds) - final int threshold = 26000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (number of updated product types) - final CompletableFuture totalNumberOfUpdatedProductTypes = - CTP_TARGET_CLIENT.execute(ProductTypeQuery.of() - .withPredicates(p -> p.attributes().name().is("attr_name_1"))) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfUpdatedProductTypes); - assertThat(totalNumberOfUpdatedProductTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert actual state of CTP project (total number of existing product types) - final CompletableFuture totalNumberOfProductTypes = - CTP_TARGET_CLIENT.execute(ProductTypeQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfProductTypes); - assertThat(totalNumberOfProductTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert statistics - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, 0, NUMBER_OF_RESOURCE_UNDER_TEST, 0); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(PRODUCT_TYPE_SYNC, UPDATES_ONLY, totalTime); - } - - @Test - void sync_WithSomeExistingProductTypes_ShouldSyncProductTypes() throws IOException { - // preparation - final List productTypeDrafts = buildProductTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - final int halfNumberOfDrafts = productTypeDrafts.size() / 2; - final List firstHalf = productTypeDrafts.subList(0, halfNumberOfDrafts); - - // Create first half of drafts to target project with different attribute definition name - CompletableFuture.allOf(firstHalf.stream() - .map(ProductTypeDraftBuilder::of) - .map(ProductTypeSyncBenchmark::applyAttributeDefinitionNameChange) - .map(ProductTypeDraftBuilder::build) - .map(draft -> CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(draft))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // benchmark - final long beforeSyncTime = System.currentTimeMillis(); - final ProductTypeSyncStatistics syncStatistics = executeBlocking(productTypeSync.sync(productTypeDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - // assert on threshold (based on history of benchmarks; highest was ~13 seconds) - final int threshold = 26000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (number of updated product types) - final CompletableFuture totalNumberOfProductTypesWithOldName = - CTP_TARGET_CLIENT.execute(ProductTypeQuery.of() - .withPredicates( - p -> p.attributes().name().is("attr_name_1_old"))) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfProductTypesWithOldName); - assertThat(totalNumberOfProductTypesWithOldName).isCompletedWithValue(0); - - // Assert actual state of CTP project (total number of existing product types) - final CompletableFuture totalNumberOfProductTypes = - CTP_TARGET_CLIENT.execute(ProductTypeQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - executeBlocking(totalNumberOfProductTypes); - assertThat(totalNumberOfProductTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert statistics - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, halfNumberOfDrafts, halfNumberOfDrafts, 0); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(PRODUCT_TYPE_SYNC, CREATES_AND_UPDATES, totalTime); - } - - - @Nonnull - private static List buildProductTypeDrafts(final int numberOfTypes) { - return IntStream - .range(0, numberOfTypes) - .mapToObj(i -> ProductTypeDraftBuilder.of( + private ProductTypeSyncOptions productTypeSyncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + @AfterAll + static void tearDown() { + deleteProductTypes(CTP_TARGET_CLIENT); + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteProductTypes(CTP_TARGET_CLIENT); + productTypeSyncOptions = buildSyncOptions(); + } + + @Nonnull + private ProductTypeSyncOptions buildSyncOptions() { + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallBack = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }; + final TriConsumer, Optional> + warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + return ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback(errorCallBack) + .warningCallback(warningCallBack) + .build(); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + } + + @Test + void sync_NewProductTypes_ShouldCreateProductTypes() throws IOException { + // preparation + final List productTypeDrafts = + buildProductTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + + // benchmark + final long beforeSyncTime = System.currentTimeMillis(); + final ProductTypeSyncStatistics syncStatistics = + executeBlocking(productTypeSync.sync(productTypeDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was ~12 seconds) + final int threshold = 24000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (total number of existing product types) + final CompletableFuture totalNumberOfProductTypes = + CTP_TARGET_CLIENT + .execute(ProductTypeQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfProductTypes); + assertThat(totalNumberOfProductTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(PRODUCT_TYPE_SYNC, CREATES_ONLY, totalTime); + } + + @Test + void sync_ExistingProductTypes_ShouldUpdateProductTypes() throws IOException { + // preparation + final List productTypeDrafts = + buildProductTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + // Create drafts to target project with different attribute definition name + CompletableFuture.allOf( + productTypeDrafts.stream() + .map(ProductTypeDraftBuilder::of) + .map(ProductTypeSyncBenchmark::applyAttributeDefinitionNameChange) + .map(ProductTypeDraftBuilder::build) + .map(draft -> CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(draft))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + + // benchmark + final long beforeSyncTime = System.currentTimeMillis(); + final ProductTypeSyncStatistics syncStatistics = + executeBlocking(productTypeSync.sync(productTypeDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was ~13 seconds) + final int threshold = 26000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (number of updated product types) + final CompletableFuture totalNumberOfUpdatedProductTypes = + CTP_TARGET_CLIENT + .execute( + ProductTypeQuery.of().withPredicates(p -> p.attributes().name().is("attr_name_1"))) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfUpdatedProductTypes); + assertThat(totalNumberOfUpdatedProductTypes) + .isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert actual state of CTP project (total number of existing product types) + final CompletableFuture totalNumberOfProductTypes = + CTP_TARGET_CLIENT + .execute(ProductTypeQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfProductTypes); + assertThat(totalNumberOfProductTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert statistics + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, 0, NUMBER_OF_RESOURCE_UNDER_TEST, 0); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(PRODUCT_TYPE_SYNC, UPDATES_ONLY, totalTime); + } + + @Test + void sync_WithSomeExistingProductTypes_ShouldSyncProductTypes() throws IOException { + // preparation + final List productTypeDrafts = + buildProductTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + final int halfNumberOfDrafts = productTypeDrafts.size() / 2; + final List firstHalf = productTypeDrafts.subList(0, halfNumberOfDrafts); + + // Create first half of drafts to target project with different attribute definition name + CompletableFuture.allOf( + firstHalf.stream() + .map(ProductTypeDraftBuilder::of) + .map(ProductTypeSyncBenchmark::applyAttributeDefinitionNameChange) + .map(ProductTypeDraftBuilder::build) + .map(draft -> CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(draft))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + + // benchmark + final long beforeSyncTime = System.currentTimeMillis(); + final ProductTypeSyncStatistics syncStatistics = + executeBlocking(productTypeSync.sync(productTypeDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was ~13 seconds) + final int threshold = 26000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (number of updated product types) + final CompletableFuture totalNumberOfProductTypesWithOldName = + CTP_TARGET_CLIENT + .execute( + ProductTypeQuery.of() + .withPredicates(p -> p.attributes().name().is("attr_name_1_old"))) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfProductTypesWithOldName); + assertThat(totalNumberOfProductTypesWithOldName).isCompletedWithValue(0); + + // Assert actual state of CTP project (total number of existing product types) + final CompletableFuture totalNumberOfProductTypes = + CTP_TARGET_CLIENT + .execute(ProductTypeQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + executeBlocking(totalNumberOfProductTypes); + assertThat(totalNumberOfProductTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert statistics + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, halfNumberOfDrafts, halfNumberOfDrafts, 0); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(PRODUCT_TYPE_SYNC, CREATES_AND_UPDATES, totalTime); + } + + @Nonnull + private static List buildProductTypeDrafts(final int numberOfTypes) { + return IntStream.range(0, numberOfTypes) + .mapToObj( + i -> + ProductTypeDraftBuilder.of( format("key__%d", i), format("name__%d", i), format("description__%d", i), - singletonList(ATTRIBUTE_DEFINITION_DRAFT_1) - ).build()) - .collect(Collectors.toList()); - } - - @Nonnull - private static ProductTypeDraftBuilder applyAttributeDefinitionNameChange( - @Nonnull final ProductTypeDraftBuilder builder) { - - final List list = - builder.getAttributes() - .stream() - .map(attributeDefinitionDraft -> AttributeDefinitionDraftBuilder - .of(attributeDefinitionDraft) - .name(attributeDefinitionDraft.getName() + "_old") - .build()) - .collect(Collectors.toList()); - - return builder.attributes(list); - } - + singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)) + .build()) + .collect(Collectors.toList()); + } + + @Nonnull + private static ProductTypeDraftBuilder applyAttributeDefinitionNameChange( + @Nonnull final ProductTypeDraftBuilder builder) { + + final List list = + builder.getAttributes().stream() + .map( + attributeDefinitionDraft -> + AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) + .name(attributeDefinitionDraft.getName() + "_old") + .build()) + .collect(Collectors.toList()); + + return builder.attributes(list); + } } diff --git a/src/benchmark/java/com/commercetools/sync/benchmark/TypeSyncBenchmark.java b/src/benchmark/java/com/commercetools/sync/benchmark/TypeSyncBenchmark.java index 39c109c998..2ef0e9c0e6 100644 --- a/src/benchmark/java/com/commercetools/sync/benchmark/TypeSyncBenchmark.java +++ b/src/benchmark/java/com/commercetools/sync/benchmark/TypeSyncBenchmark.java @@ -1,5 +1,22 @@ package com.commercetools.sync.benchmark; +import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; +import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; +import static com.commercetools.sync.benchmark.BenchmarkUtils.NUMBER_OF_RESOURCE_UNDER_TEST; +import static com.commercetools.sync.benchmark.BenchmarkUtils.THRESHOLD_EXCEEDED_ERROR; +import static com.commercetools.sync.benchmark.BenchmarkUtils.TYPE_SYNC; +import static com.commercetools.sync.benchmark.BenchmarkUtils.UPDATES_ONLY; +import static com.commercetools.sync.benchmark.BenchmarkUtils.saveNewResult; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_NAME_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.deleteTypes; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -17,11 +34,6 @@ import io.sphere.sdk.types.TypeDraftBuilder; import io.sphere.sdk.types.commands.TypeCreateCommand; import io.sphere.sdk.types.queries.TypeQuery; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -30,241 +42,248 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_AND_UPDATES; -import static com.commercetools.sync.benchmark.BenchmarkUtils.CREATES_ONLY; -import static com.commercetools.sync.benchmark.BenchmarkUtils.NUMBER_OF_RESOURCE_UNDER_TEST; -import static com.commercetools.sync.benchmark.BenchmarkUtils.THRESHOLD_EXCEEDED_ERROR; -import static com.commercetools.sync.benchmark.BenchmarkUtils.TYPE_SYNC; -import static com.commercetools.sync.benchmark.BenchmarkUtils.UPDATES_ONLY; -import static com.commercetools.sync.benchmark.BenchmarkUtils.saveNewResult; -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_NAME_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.deleteTypes; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class TypeSyncBenchmark { - private TypeSyncOptions typeSyncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - @AfterEach - void tearDown() { - deleteTypes(CTP_TARGET_CLIENT); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteTypes(CTP_TARGET_CLIENT); - typeSyncOptions = buildSyncOptions(); - } - - @Nonnull - private TypeSyncOptions buildSyncOptions() { - final QuadConsumer, Optional, List>> - errorCallBack = (exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); + private TypeSyncOptions typeSyncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + @AfterEach + void tearDown() { + deleteTypes(CTP_TARGET_CLIENT); + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteTypes(CTP_TARGET_CLIENT); + typeSyncOptions = buildSyncOptions(); + } + + @Nonnull + private TypeSyncOptions buildSyncOptions() { + final QuadConsumer, Optional, List>> + errorCallBack = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }; - final TriConsumer, Optional> warningCallBack = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - return TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallBack) - .warningCallback(warningCallBack) - .build(); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - } - - @Test - void sync_NewTypes_ShouldCreateTypes() throws IOException { - // preparation - final List typeDrafts = buildTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - // benchmark - final long beforeSyncTime = System.currentTimeMillis(); - final TypeSyncStatistics syncStatistics = executeBlocking(typeSync.sync(typeDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - // assert on threshold (based on history of benchmarks; highest was ~13 seconds) - final int threshold = 26000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (total number of existing types) - final CompletableFuture totalNumberOfTypes = - CTP_TARGET_CLIENT.execute(TypeQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfTypes); - assertThat(totalNumberOfTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(TYPE_SYNC, CREATES_ONLY, totalTime); - } - - @Test - void sync_ExistingTypes_ShouldUpdateTypes() throws IOException { - // preparation - final List typeDrafts = buildTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - // Create drafts to target project with different field type name - CompletableFuture.allOf(typeDrafts.stream() - .map(TypeDraftBuilder::of) - .map(TypeSyncBenchmark::applyFieldDefinitionNameChange) - .map(TypeDraftBuilder::build) - .map(draft -> CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(draft))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - // benchmark - final long beforeSyncTime = System.currentTimeMillis(); - final TypeSyncStatistics syncStatistics = executeBlocking(typeSync.sync(typeDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - // assert on threshold (based on history of benchmarks; highest was ~13 seconds) - final int threshold = 26000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (number of updated types) - final CompletableFuture totalNumberOfUpdatedTypes = - CTP_TARGET_CLIENT.execute(TypeQuery.of() - .withPredicates(p -> p.fieldDefinitions().name().is( - FIELD_DEFINITION_NAME_1))) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfUpdatedTypes); - assertThat(totalNumberOfUpdatedTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert actual state of CTP project (total number of existing types) - final CompletableFuture totalNumberOfTypes = - CTP_TARGET_CLIENT.execute(TypeQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - executeBlocking(totalNumberOfTypes); - assertThat(totalNumberOfTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert statistics - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, 0, NUMBER_OF_RESOURCE_UNDER_TEST, 0); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(TYPE_SYNC, UPDATES_ONLY, totalTime); - } - - @Test - void sync_WithSomeExistingTypes_ShouldSyncTypes() throws IOException { - // preparation - final List typeDrafts = buildTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); - final int halfNumberOfDrafts = typeDrafts.size() / 2; - final List firstHalf = typeDrafts.subList(0, halfNumberOfDrafts); - - // Create first half of drafts to target project with different field definition name - CompletableFuture.allOf(firstHalf.stream() - .map(TypeDraftBuilder::of) - .map(TypeSyncBenchmark::applyFieldDefinitionNameChange) - .map(TypeDraftBuilder::build) - .map(draft -> CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(draft))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - // benchmark - final long beforeSyncTime = System.currentTimeMillis(); - final TypeSyncStatistics syncStatistics = executeBlocking(typeSync.sync(typeDrafts)); - final long totalTime = System.currentTimeMillis() - beforeSyncTime; - - // assert on threshold (based on history of benchmarks; highest was ~12 seconds) - final int threshold = 24000; // double of the highest benchmark - assertThat(totalTime).withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) - .isLessThan(threshold); - - // Assert actual state of CTP project (number of updated types) - final CompletableFuture totalNumberOfUpdatedTypesWithOldFieldDefinitionName = - CTP_TARGET_CLIENT.execute(TypeQuery.of() - .withPredicates(p -> p.fieldDefinitions().name().is( - FIELD_DEFINITION_NAME_1 + "_old"))) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - - executeBlocking(totalNumberOfUpdatedTypesWithOldFieldDefinitionName); - assertThat(totalNumberOfUpdatedTypesWithOldFieldDefinitionName).isCompletedWithValue(0); - - // Assert actual state of CTP project (total number of existing types) - final CompletableFuture totalNumberOfTypes = - CTP_TARGET_CLIENT.execute(TypeQuery.of()) - .thenApply(PagedQueryResult::getTotal) - .thenApply(Long::intValue) - .toCompletableFuture(); - executeBlocking(totalNumberOfTypes); - assertThat(totalNumberOfTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); - - // Assert statistics - assertThat(syncStatistics).hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, halfNumberOfDrafts, halfNumberOfDrafts, 0); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - saveNewResult(TYPE_SYNC, CREATES_AND_UPDATES, totalTime); - } - - @Nonnull - private static List buildTypeDrafts(final int numberOfTypes) { - return IntStream - .range(0, numberOfTypes) - .mapToObj(i -> TypeDraftBuilder.of( + final TriConsumer, Optional> warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + return TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback(errorCallBack) + .warningCallback(warningCallBack) + .build(); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + } + + @Test + void sync_NewTypes_ShouldCreateTypes() throws IOException { + // preparation + final List typeDrafts = buildTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // benchmark + final long beforeSyncTime = System.currentTimeMillis(); + final TypeSyncStatistics syncStatistics = executeBlocking(typeSync.sync(typeDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was ~13 seconds) + final int threshold = 26000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (total number of existing types) + final CompletableFuture totalNumberOfTypes = + CTP_TARGET_CLIENT + .execute(TypeQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfTypes); + assertThat(totalNumberOfTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, NUMBER_OF_RESOURCE_UNDER_TEST, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(TYPE_SYNC, CREATES_ONLY, totalTime); + } + + @Test + void sync_ExistingTypes_ShouldUpdateTypes() throws IOException { + // preparation + final List typeDrafts = buildTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + // Create drafts to target project with different field type name + CompletableFuture.allOf( + typeDrafts.stream() + .map(TypeDraftBuilder::of) + .map(TypeSyncBenchmark::applyFieldDefinitionNameChange) + .map(TypeDraftBuilder::build) + .map(draft -> CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(draft))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // benchmark + final long beforeSyncTime = System.currentTimeMillis(); + final TypeSyncStatistics syncStatistics = executeBlocking(typeSync.sync(typeDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was ~13 seconds) + final int threshold = 26000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (number of updated types) + final CompletableFuture totalNumberOfUpdatedTypes = + CTP_TARGET_CLIENT + .execute( + TypeQuery.of() + .withPredicates(p -> p.fieldDefinitions().name().is(FIELD_DEFINITION_NAME_1))) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfUpdatedTypes); + assertThat(totalNumberOfUpdatedTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert actual state of CTP project (total number of existing types) + final CompletableFuture totalNumberOfTypes = + CTP_TARGET_CLIENT + .execute(TypeQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + executeBlocking(totalNumberOfTypes); + assertThat(totalNumberOfTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert statistics + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, 0, NUMBER_OF_RESOURCE_UNDER_TEST, 0); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(TYPE_SYNC, UPDATES_ONLY, totalTime); + } + + @Test + void sync_WithSomeExistingTypes_ShouldSyncTypes() throws IOException { + // preparation + final List typeDrafts = buildTypeDrafts(NUMBER_OF_RESOURCE_UNDER_TEST); + final int halfNumberOfDrafts = typeDrafts.size() / 2; + final List firstHalf = typeDrafts.subList(0, halfNumberOfDrafts); + + // Create first half of drafts to target project with different field definition name + CompletableFuture.allOf( + firstHalf.stream() + .map(TypeDraftBuilder::of) + .map(TypeSyncBenchmark::applyFieldDefinitionNameChange) + .map(TypeDraftBuilder::build) + .map(draft -> CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(draft))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // benchmark + final long beforeSyncTime = System.currentTimeMillis(); + final TypeSyncStatistics syncStatistics = executeBlocking(typeSync.sync(typeDrafts)); + final long totalTime = System.currentTimeMillis() - beforeSyncTime; + + // assert on threshold (based on history of benchmarks; highest was ~12 seconds) + final int threshold = 24000; // double of the highest benchmark + assertThat(totalTime) + .withFailMessage(format(THRESHOLD_EXCEEDED_ERROR, totalTime, threshold)) + .isLessThan(threshold); + + // Assert actual state of CTP project (number of updated types) + final CompletableFuture totalNumberOfUpdatedTypesWithOldFieldDefinitionName = + CTP_TARGET_CLIENT + .execute( + TypeQuery.of() + .withPredicates( + p -> p.fieldDefinitions().name().is(FIELD_DEFINITION_NAME_1 + "_old"))) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + + executeBlocking(totalNumberOfUpdatedTypesWithOldFieldDefinitionName); + assertThat(totalNumberOfUpdatedTypesWithOldFieldDefinitionName).isCompletedWithValue(0); + + // Assert actual state of CTP project (total number of existing types) + final CompletableFuture totalNumberOfTypes = + CTP_TARGET_CLIENT + .execute(TypeQuery.of()) + .thenApply(PagedQueryResult::getTotal) + .thenApply(Long::intValue) + .toCompletableFuture(); + executeBlocking(totalNumberOfTypes); + assertThat(totalNumberOfTypes).isCompletedWithValue(NUMBER_OF_RESOURCE_UNDER_TEST); + + // Assert statistics + assertThat(syncStatistics) + .hasValues(NUMBER_OF_RESOURCE_UNDER_TEST, halfNumberOfDrafts, halfNumberOfDrafts, 0); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + saveNewResult(TYPE_SYNC, CREATES_AND_UPDATES, totalTime); + } + + @Nonnull + private static List buildTypeDrafts(final int numberOfTypes) { + return IntStream.range(0, numberOfTypes) + .mapToObj( + i -> + TypeDraftBuilder.of( format("key__%d", i), LocalizedString.ofEnglish(format("name__%d", i)), ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(LocalizedString.ofEnglish(format("description__%d", i))) - .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) - .build()) - .collect(Collectors.toList()); - } - - @Nonnull - private static TypeDraftBuilder applyFieldDefinitionNameChange(@Nonnull final TypeDraftBuilder builder) { - final List list = builder.getFieldDefinitions() - .stream() - .map(fieldDefinition -> FieldDefinition.of(fieldDefinition.getType(), - fieldDefinition.getName() + "_old", - fieldDefinition.getLabel(), - fieldDefinition.isRequired(), - fieldDefinition.getInputHint())) - .collect(Collectors.toList()); - - return builder.fieldDefinitions(list); - } + .description(LocalizedString.ofEnglish(format("description__%d", i))) + .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) + .build()) + .collect(Collectors.toList()); + } + + @Nonnull + private static TypeDraftBuilder applyFieldDefinitionNameChange( + @Nonnull final TypeDraftBuilder builder) { + final List list = + builder.getFieldDefinitions().stream() + .map( + fieldDefinition -> + FieldDefinition.of( + fieldDefinition.getType(), + fieldDefinition.getName() + "_old", + fieldDefinition.getLabel(), + fieldDefinition.isRequired(), + fieldDefinition.getInputHint())) + .collect(Collectors.toList()); + + return builder.fieldDefinitions(list); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/CleanupUnresolvedReferenceCustomObjectsIT.java b/src/integration-test/java/com/commercetools/sync/integration/commons/CleanupUnresolvedReferenceCustomObjectsIT.java index bc737a9ee4..9d1a65b38c 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/CleanupUnresolvedReferenceCustomObjectsIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/CleanupUnresolvedReferenceCustomObjectsIT.java @@ -1,5 +1,14 @@ package com.commercetools.sync.integration.commons; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedCustomObjects; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedTransitionsCustomObjects; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.services.impl.UnresolvedTransitionsServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.CleanupUnresolvedReferenceCustomObjects; import com.commercetools.sync.commons.models.WaitingToBeResolved; import com.commercetools.sync.commons.models.WaitingToBeResolvedTransitions; @@ -16,9 +25,6 @@ import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraftBuilder; import io.sphere.sdk.states.StateType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -26,83 +32,78 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedCustomObjects; -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedTransitionsCustomObjects; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.services.impl.UnresolvedTransitionsServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY; -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CleanupUnresolvedReferenceCustomObjectsIT { - private UnresolvedTransitionsService unresolvedTransitionsService; - private UnresolvedReferencesService unresolvedReferencesService; + private UnresolvedTransitionsService unresolvedTransitionsService; + private UnresolvedReferencesService unresolvedReferencesService; - @BeforeEach - void setupTest() { - deleteWaitingToBeResolvedTransitionsCustomObjects(CTP_TARGET_CLIENT, CUSTOM_OBJECT_CONTAINER_KEY); - deleteWaitingToBeResolvedCustomObjects(CTP_TARGET_CLIENT); + @BeforeEach + void setupTest() { + deleteWaitingToBeResolvedTransitionsCustomObjects( + CTP_TARGET_CLIENT, CUSTOM_OBJECT_CONTAINER_KEY); + deleteWaitingToBeResolvedCustomObjects(CTP_TARGET_CLIENT); - unresolvedReferencesService = new UnresolvedReferencesServiceImpl( + unresolvedReferencesService = + new UnresolvedReferencesServiceImpl( ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build()); - unresolvedTransitionsService = new UnresolvedTransitionsServiceImpl( - StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build()); - } - - @Test - void cleanup_withDeleteDaysAfterLastModification_ShouldDeleteAndReturnCleanupStatistics() { - createSampleUnresolvedReferences(); - - final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = - CleanupUnresolvedReferenceCustomObjects.of(CTP_TARGET_CLIENT) - .pageSize(3) // for testing purpose, ensures the pagination works. - .cleanup(-1) // to be able to test it. - .join(); - - assertThat(statistics.getTotalDeleted()).isEqualTo(10); - assertThat(statistics.getTotalFailed()).isEqualTo(0); - assertThat(statistics.getReportMessage()) - .isEqualTo("Summary: 10 custom objects were deleted in total (0 failed to delete)."); + unresolvedTransitionsService = + new UnresolvedTransitionsServiceImpl(StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build()); + } + + @Test + void cleanup_withDeleteDaysAfterLastModification_ShouldDeleteAndReturnCleanupStatistics() { + createSampleUnresolvedReferences(); + + final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = + CleanupUnresolvedReferenceCustomObjects.of(CTP_TARGET_CLIENT) + .pageSize(3) // for testing purpose, ensures the pagination works. + .cleanup(-1) // to be able to test it. + .join(); + + assertThat(statistics.getTotalDeleted()).isEqualTo(10); + assertThat(statistics.getTotalFailed()).isEqualTo(0); + assertThat(statistics.getReportMessage()) + .isEqualTo("Summary: 10 custom objects were deleted in total (0 failed to delete)."); + } + + void createSampleUnresolvedReferences() { + final ProductDraft sampleProductDraft = + SphereJsonUtils.readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, ProductDraft.class); + + final Set> sampleTransitions = + new HashSet<>(Arrays.asList(State.referenceOfId("id1"), State.referenceOfId("id2"))); + + final List productUnresolvedReferences = new ArrayList<>(); + final List transitionUnresolvedReferences = new ArrayList<>(); + + for (int i = 1; i <= 5; i++) { + productUnresolvedReferences.add( + new WaitingToBeResolved( + ProductDraftBuilder.of(sampleProductDraft).key(format("productKey%s", i)).build(), + asSet("foo", "bar"))); + + transitionUnresolvedReferences.add( + new WaitingToBeResolvedTransitions( + StateDraftBuilder.of(format("stateKeys%s", i), StateType.LINE_ITEM_STATE) + .transitions(sampleTransitions) + .build(), + asSet("foo", "bar"))); } - void createSampleUnresolvedReferences() { - final ProductDraft sampleProductDraft = - SphereJsonUtils.readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, ProductDraft.class); - - final Set> sampleTransitions = new HashSet<>( - Arrays.asList(State.referenceOfId("id1"), State.referenceOfId("id2"))); - - final List productUnresolvedReferences = new ArrayList<>(); - final List transitionUnresolvedReferences = new ArrayList<>(); - - for (int i = 1; i <= 5; i++) { - productUnresolvedReferences.add(new WaitingToBeResolved( - ProductDraftBuilder.of(sampleProductDraft).key(format("productKey%s", i)).build(), - asSet("foo", "bar"))); - - transitionUnresolvedReferences.add(new WaitingToBeResolvedTransitions( - StateDraftBuilder.of(format("stateKeys%s", i), StateType.LINE_ITEM_STATE) - .transitions(sampleTransitions) - .build(), - asSet("foo", "bar"))); - } - - CompletableFuture.allOf( + CompletableFuture.allOf( CompletableFuture.allOf( - productUnresolvedReferences - .stream() + productUnresolvedReferences.stream() .map(unresolvedReferencesService::save) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)), CompletableFuture.allOf( - transitionUnresolvedReferences - .stream() + transitionUnresolvedReferences.stream() .map(unresolvedTransitionsService::save) .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - ).join(); - } + .toArray(CompletableFuture[]::new))) + .join(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/CtpQueryUtilsIT.java b/src/integration-test/java/com/commercetools/sync/integration/commons/CtpQueryUtilsIT.java index 6fb2f31807..277ff41c0c 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/CtpQueryUtilsIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/CtpQueryUtilsIT.java @@ -1,5 +1,15 @@ package com.commercetools.sync.integration.commons; +import static com.commercetools.sync.commons.utils.CtpQueryUtils.queryAll; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategories; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCategoryDraftsWithPrefix; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.GraphQlQueryResources; @@ -7,11 +17,6 @@ import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.CategoryDraft; import io.sphere.sdk.categories.queries.CategoryQuery; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -19,195 +24,210 @@ import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.CtpQueryUtils.queryAll; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategories; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCategoryDraftsWithPrefix; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static java.util.Collections.singleton; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CtpQueryUtilsIT { - /** - * Delete all categories and types from target project. Then create custom types for target CTP project categories. - */ - @BeforeAll - static void setup() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, "anyName", CTP_TARGET_CLIENT); - } - - /** - * Deletes Categories and Types from target CTP projects, then it populates target CTP project with category test - * data. - */ - @BeforeEach - void setupTest() { - deleteAllCategories(CTP_TARGET_CLIENT); - } - - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - } - - @Test - void queryAll_WithKeyCollectorConsumerOn100CategoriesWithCustomPageSize_ShouldCollectKeys() { - final int numberOfCategories = 100; - createCategories(CTP_TARGET_CLIENT, getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", - null, numberOfCategories)); - final List categoryKeys = new ArrayList<>(); - - final Consumer> categoryPageConsumer = categoryPageResults -> + /** + * Delete all categories and types from target project. Then create custom types for target CTP + * project categories. + */ + @BeforeAll + static void setup() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, "anyName", CTP_TARGET_CLIENT); + } + + /** + * Deletes Categories and Types from target CTP projects, then it populates target CTP project + * with category test data. + */ + @BeforeEach + void setupTest() { + deleteAllCategories(CTP_TARGET_CLIENT); + } + + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + } + + @Test + void queryAll_WithKeyCollectorConsumerOn100CategoriesWithCustomPageSize_ShouldCollectKeys() { + final int numberOfCategories = 100; + createCategories( + CTP_TARGET_CLIENT, + getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, numberOfCategories)); + final List categoryKeys = new ArrayList<>(); + + final Consumer> categoryPageConsumer = + categoryPageResults -> categoryPageResults.forEach(category -> categoryKeys.add(category.getKey())); - queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), categoryPageConsumer, 10).toCompletableFuture().join(); - assertThat(categoryKeys).hasSize(numberOfCategories); - } + queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), categoryPageConsumer, 10) + .toCompletableFuture() + .join(); + assertThat(categoryKeys).hasSize(numberOfCategories); + } - @Test - void queryAll_WithKeyCollectorConsumerOn600Categories_ShouldCollectKeys() { - final int numberOfCategories = 600; - final List categoryDrafts = getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", - null, numberOfCategories); - createCategories(CTP_TARGET_CLIENT, categoryDrafts); + @Test + void queryAll_WithKeyCollectorConsumerOn600Categories_ShouldCollectKeys() { + final int numberOfCategories = 600; + final List categoryDrafts = + getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, numberOfCategories); + createCategories(CTP_TARGET_CLIENT, categoryDrafts); - final List categoryKeys = new ArrayList<>(); + final List categoryKeys = new ArrayList<>(); - final Consumer> categoryPageConsumer = categoryPageResults -> + final Consumer> categoryPageConsumer = + categoryPageResults -> categoryPageResults.forEach(category -> categoryKeys.add(category.getKey())); - queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), categoryPageConsumer).toCompletableFuture().join(); - assertThat(categoryKeys).hasSize(numberOfCategories); - } - - @Test - void queryAll_WithCategoryCollectorCallbackWithUniformPageSplitting_ShouldFetchAllCategories() { - final int numberOfCategories = 10; - createCategories(CTP_TARGET_CLIENT, getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", - null, numberOfCategories)); - - final List> categoryPages = - queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), (categories -> categories), 2) - .toCompletableFuture().join(); - - assertThat(categoryPages).hasSize(5); - - categoryPages.forEach(page -> assertThat(page.size()).isEqualTo(2)); - - // Assert that all categories created are fetched. - final Set pageCategoryKeys = new HashSet<>(); - categoryPages.forEach(page -> { - assertThat(page.size()).isEqualTo(2); - page.forEach(category -> pageCategoryKeys.add(category.getKey())); + queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), categoryPageConsumer) + .toCompletableFuture() + .join(); + assertThat(categoryKeys).hasSize(numberOfCategories); + } + + @Test + void queryAll_WithCategoryCollectorCallbackWithUniformPageSplitting_ShouldFetchAllCategories() { + final int numberOfCategories = 10; + createCategories( + CTP_TARGET_CLIENT, + getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, numberOfCategories)); + + final List> categoryPages = + queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), (categories -> categories), 2) + .toCompletableFuture() + .join(); + + assertThat(categoryPages).hasSize(5); + + categoryPages.forEach(page -> assertThat(page.size()).isEqualTo(2)); + + // Assert that all categories created are fetched. + final Set pageCategoryKeys = new HashSet<>(); + categoryPages.forEach( + page -> { + assertThat(page.size()).isEqualTo(2); + page.forEach(category -> pageCategoryKeys.add(category.getKey())); }); - assertThat(pageCategoryKeys).hasSize(numberOfCategories); - } - - @Test - void queryAll_WithCategoryCollectorCallbackWithNonUniformPageSplitting_ShouldFetchAllCategories() { - final int numberOfCategories = 7; - createCategories(CTP_TARGET_CLIENT, getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", - null, numberOfCategories)); - - final List> categoryPages = - queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), (categories -> categories), 2) - .toCompletableFuture().join(); - - assertThat(categoryPages).hasSize(4); - - // Assert that all categories created are fetched. - final Set pageCategoryKeys = new HashSet<>(); - categoryPages.forEach(page -> page.forEach(category -> pageCategoryKeys.add(category.getKey()))); - assertThat(pageCategoryKeys).hasSize(numberOfCategories); - } - - @Test - void queryAll_WithCategoryCollectorCallbackOn600Categories_ShouldFetchAllCategories() { - final int numberOfCategories = 600; - createCategories(CTP_TARGET_CLIENT, getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", - null, numberOfCategories)); - - final List> categoryPages = - queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), (categories -> categories)).toCompletableFuture().join(); - assertThat(categoryPages).hasSize(2); - assertThat(categoryPages.get(0)).hasSize(500); - assertThat(categoryPages.get(1)).hasSize(100); - } - - - @Test - void queryAll_WithIdCollectorConsumerWithSinglePage_ShouldCollectIds() { - final int numberOfCategories = 100; - List categoryDrafts = - getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, numberOfCategories); - List categories = createCategories(CTP_TARGET_CLIENT, categoryDrafts); - - Set allCategoryKeys = categories.stream() - .map(category -> category.getKey()) - .collect(Collectors.toSet()); - final List categoryIds = new ArrayList<>(); - - final Consumer> categoryPageConsumer = categoryPageResults -> + assertThat(pageCategoryKeys).hasSize(numberOfCategories); + } + + @Test + void + queryAll_WithCategoryCollectorCallbackWithNonUniformPageSplitting_ShouldFetchAllCategories() { + final int numberOfCategories = 7; + createCategories( + CTP_TARGET_CLIENT, + getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, numberOfCategories)); + + final List> categoryPages = + queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), (categories -> categories), 2) + .toCompletableFuture() + .join(); + + assertThat(categoryPages).hasSize(4); + + // Assert that all categories created are fetched. + final Set pageCategoryKeys = new HashSet<>(); + categoryPages.forEach( + page -> page.forEach(category -> pageCategoryKeys.add(category.getKey()))); + assertThat(pageCategoryKeys).hasSize(numberOfCategories); + } + + @Test + void queryAll_WithCategoryCollectorCallbackOn600Categories_ShouldFetchAllCategories() { + final int numberOfCategories = 600; + createCategories( + CTP_TARGET_CLIENT, + getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, numberOfCategories)); + + final List> categoryPages = + queryAll(CTP_TARGET_CLIENT, CategoryQuery.of(), (categories -> categories)) + .toCompletableFuture() + .join(); + assertThat(categoryPages).hasSize(2); + assertThat(categoryPages.get(0)).hasSize(500); + assertThat(categoryPages.get(1)).hasSize(100); + } + + @Test + void queryAll_WithIdCollectorConsumerWithSinglePage_ShouldCollectIds() { + final int numberOfCategories = 100; + List categoryDrafts = + getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, numberOfCategories); + List categories = createCategories(CTP_TARGET_CLIENT, categoryDrafts); + + Set allCategoryKeys = + categories.stream().map(category -> category.getKey()).collect(Collectors.toSet()); + final List categoryIds = new ArrayList<>(); + + final Consumer> categoryPageConsumer = + categoryPageResults -> categoryPageResults.forEach(category -> categoryIds.add(category.getId())); - ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = new ResourceKeyIdGraphQlRequest(allCategoryKeys, - GraphQlQueryResources.CATEGORIES); + ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(allCategoryKeys, GraphQlQueryResources.CATEGORIES); - queryAll(CTP_TARGET_CLIENT, resourceKeyIdGraphQlRequest, categoryPageConsumer).toCompletableFuture().join(); - assertThat(categoryIds).hasSize(numberOfCategories); - } + queryAll(CTP_TARGET_CLIENT, resourceKeyIdGraphQlRequest, categoryPageConsumer) + .toCompletableFuture() + .join(); + assertThat(categoryIds).hasSize(numberOfCategories); + } - @Test - void queryAll_WithIdCollectorConsumerWithMultiplePages_ShouldCollectIds() { - final int numberOfCategories = 600; - final List categoryDrafts = getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", - null, numberOfCategories); - List categories = createCategories(CTP_TARGET_CLIENT, categoryDrafts); - Set allCategoryKeys = categories.stream() - .map(category -> category.getKey()) - .collect(Collectors.toSet()); + @Test + void queryAll_WithIdCollectorConsumerWithMultiplePages_ShouldCollectIds() { + final int numberOfCategories = 600; + final List categoryDrafts = + getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, numberOfCategories); + List categories = createCategories(CTP_TARGET_CLIENT, categoryDrafts); + Set allCategoryKeys = + categories.stream().map(category -> category.getKey()).collect(Collectors.toSet()); - final List categoryIds = new ArrayList<>(); + final List categoryIds = new ArrayList<>(); - final Consumer> categoryPageConsumer = categoryPageResults -> + final Consumer> categoryPageConsumer = + categoryPageResults -> categoryPageResults.forEach(category -> categoryIds.add(category.getId())); - ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(allCategoryKeys, GraphQlQueryResources.CATEGORIES); + ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(allCategoryKeys, GraphQlQueryResources.CATEGORIES); - queryAll(CTP_TARGET_CLIENT, resourceKeyIdGraphQlRequest, categoryPageConsumer).toCompletableFuture().join(); - assertThat(categoryIds).hasSize(numberOfCategories); - } + queryAll(CTP_TARGET_CLIENT, resourceKeyIdGraphQlRequest, categoryPageConsumer) + .toCompletableFuture() + .join(); + assertThat(categoryIds).hasSize(numberOfCategories); + } - @Test - void queryAll_WithIdCollectorConsumerWithRequestedKeySubset_ShouldCollectIds() { - final List categoryDrafts = getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", - null, 5); - List categories = createCategories(CTP_TARGET_CLIENT, categoryDrafts); + @Test + void queryAll_WithIdCollectorConsumerWithRequestedKeySubset_ShouldCollectIds() { + final List categoryDrafts = + getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, 5); + List categories = createCategories(CTP_TARGET_CLIENT, categoryDrafts); - final List categoryIds = new ArrayList<>(); + final List categoryIds = new ArrayList<>(); - final Consumer> categoryPageConsumer = categoryPageResults -> + final Consumer> categoryPageConsumer = + categoryPageResults -> categoryPageResults.forEach(category -> categoryIds.add(category.getId())); - ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(singleton(categories.get(0).getKey()), - GraphQlQueryResources.CATEGORIES); - - queryAll(CTP_TARGET_CLIENT, resourceKeyIdGraphQlRequest, categoryPageConsumer).toCompletableFuture().join(); - assertThat(categoryIds).hasSize(1); - assertThat(categoryIds).containsExactly(categories.get(0).getId()); - } + ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest( + singleton(categories.get(0).getKey()), GraphQlQueryResources.CATEGORIES); + queryAll(CTP_TARGET_CLIENT, resourceKeyIdGraphQlRequest, categoryPageConsumer) + .toCompletableFuture() + .join(); + assertThat(categoryIds).hasSize(1); + assertThat(categoryIds).containsExactly(categories.get(0).getId()); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CartDiscountITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CartDiscountITUtils.java index b0afbed858..fec132b896 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CartDiscountITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CartDiscountITUtils.java @@ -1,5 +1,14 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createTypeIfNotAlreadyExisting; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; +import static java.lang.String.format; +import static java.util.stream.Collectors.toList; + import io.sphere.sdk.cartdiscounts.CartDiscount; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.cartdiscounts.CartDiscountDraftBuilder; @@ -17,196 +26,187 @@ import io.sphere.sdk.types.ResourceTypeIdsSetBuilder; import io.sphere.sdk.types.Type; import io.sphere.sdk.utils.MoneyImpl; - -import javax.annotation.Nonnull; import java.time.ZonedDateTime; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.IntStream; - -import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createTypeIfNotAlreadyExisting; -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; -import static java.lang.String.format; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; public final class CartDiscountITUtils { - public static final String CART_DISCOUNT_KEY_1 = "key_1"; - public static final String CART_DISCOUNT_KEY_2 = "key_2"; - - public static final LocalizedString CART_DISCOUNT_NAME_1 = LocalizedString.of(Locale.ENGLISH, "name_1"); - public static final LocalizedString CART_DISCOUNT_NAME_2 = LocalizedString.of(Locale.ENGLISH, "name_2"); - - public static final LocalizedString CART_DISCOUNT_DESC_1 = - LocalizedString.of(Locale.ENGLISH, "discount- get 10 percent"); - public static final LocalizedString CART_DISCOUNT_DESC_2 = - LocalizedString.of(Locale.ENGLISH, "discount- get 20 EUR for special items"); - - public static final String PREDICATE_1 = "1 = 1"; - public static final String PREDICATE_2 = "lineItemExists(sku = \"0123456789\" or sku = \"0246891213\") = true"; - - public static final CartPredicate CART_DISCOUNT_CART_PREDICATE_1 = CartPredicate.of(PREDICATE_1); - public static final CartPredicate CART_DISCOUNT_CART_PREDICATE_2 = CartPredicate.of(PREDICATE_2); - - public static final CartDiscountValue CART_DISCOUNT_VALUE_1 = CartDiscountValue.ofRelative(1000); - public static final CartDiscountValue CART_DISCOUNT_VALUE_2 = CartDiscountValue.ofAbsolute(MoneyImpl.of(20, EUR)); - - public static final CartDiscountTarget CART_DISCOUNT_TARGET_1 = LineItemsTarget.ofAll(); - public static final CartDiscountTarget CART_DISCOUNT_TARGET_2 = - LineItemsTarget.of("sku = \"0123456789\" or sku = \"0246891213\""); - - public static final ZonedDateTime JANUARY_FROM = ZonedDateTime.parse("2019-01-01T00:00:00.000Z"); - public static final ZonedDateTime JANUARY_UNTIL = ZonedDateTime.parse("2019-01-31T00:00:00.000Z"); - public static final ZonedDateTime FEBRUARY_FROM = ZonedDateTime.parse("2019-02-01T00:00:00.000Z"); - public static final ZonedDateTime FEBRUARY_UNTIL = ZonedDateTime.parse("2019-02-28T00:00:00.000Z"); - - public static final String SORT_ORDER_1 = "0.1"; - public static final String SORT_ORDER_2 = "0.2"; - - public static final String OLD_CART_DISCOUNT_TYPE_KEY = "oldCartDiscountCustomTypeKey"; - public static final String OLD_CART_DISCOUNT_TYPE_NAME = "oldCartDiscountCustomTypeName"; - - public static final CartDiscountDraft CART_DISCOUNT_DRAFT_1 = - CartDiscountDraftBuilder.of(CART_DISCOUNT_NAME_1, - CART_DISCOUNT_CART_PREDICATE_1, - CART_DISCOUNT_VALUE_1, - CART_DISCOUNT_TARGET_1, - SORT_ORDER_1, - false) - .key(CART_DISCOUNT_KEY_1) - .active(false) - .description(CART_DISCOUNT_DESC_1) - .validFrom(JANUARY_FROM) - .validUntil(JANUARY_UNTIL) - .custom(getCustomFieldsDraft()) - .build(); - - public static final CartDiscountDraft CART_DISCOUNT_DRAFT_2 = - CartDiscountDraftBuilder.of(CART_DISCOUNT_NAME_2, - CART_DISCOUNT_CART_PREDICATE_2, - CART_DISCOUNT_VALUE_2, - CART_DISCOUNT_TARGET_2, - SORT_ORDER_2, - false) - .key(CART_DISCOUNT_KEY_2) - .active(false) - .description(CART_DISCOUNT_DESC_2) - .validFrom(FEBRUARY_FROM) - .validUntil(FEBRUARY_UNTIL) - .custom(getCustomFieldsDraft()) - .build(); - - - public static CustomFieldsDraft getCustomFieldsDraft() { - return CustomFieldsDraft.ofTypeKeyAndJson(OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap()); - } - - /** - * Deletes all cart discounts from CTP project, represented by provided {@code ctpClient}. - * - * @param ctpClient represents the CTP project the cart discounts will be deleted from. - */ - public static void deleteCartDiscounts(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, CartDiscountQuery.of(), CartDiscountDeleteCommand::of); - } - - /** - * Deletes all cart discounts from CTP projects defined by {@code CTP_SOURCE_CLIENT} and {@code CTP_TARGET_CLIENT}. - */ - public static void deleteCartDiscountsFromTargetAndSource() { - deleteCartDiscounts(CTP_SOURCE_CLIENT); - deleteCartDiscounts(CTP_TARGET_CLIENT); - } - - - public static void populateSourceProject() { - createCartDiscountCustomType(OLD_CART_DISCOUNT_TYPE_KEY, - Locale.ENGLISH, - OLD_CART_DISCOUNT_TYPE_NAME, - CTP_SOURCE_CLIENT); - - final CartDiscountDraft draft1 = CartDiscountDraftBuilder - .of(CART_DISCOUNT_DRAFT_1) - .custom(CustomFieldsDraft - .ofTypeKeyAndJson(OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) + public static final String CART_DISCOUNT_KEY_1 = "key_1"; + public static final String CART_DISCOUNT_KEY_2 = "key_2"; + + public static final LocalizedString CART_DISCOUNT_NAME_1 = + LocalizedString.of(Locale.ENGLISH, "name_1"); + public static final LocalizedString CART_DISCOUNT_NAME_2 = + LocalizedString.of(Locale.ENGLISH, "name_2"); + + public static final LocalizedString CART_DISCOUNT_DESC_1 = + LocalizedString.of(Locale.ENGLISH, "discount- get 10 percent"); + public static final LocalizedString CART_DISCOUNT_DESC_2 = + LocalizedString.of(Locale.ENGLISH, "discount- get 20 EUR for special items"); + + public static final String PREDICATE_1 = "1 = 1"; + public static final String PREDICATE_2 = + "lineItemExists(sku = \"0123456789\" or sku = \"0246891213\") = true"; + + public static final CartPredicate CART_DISCOUNT_CART_PREDICATE_1 = CartPredicate.of(PREDICATE_1); + public static final CartPredicate CART_DISCOUNT_CART_PREDICATE_2 = CartPredicate.of(PREDICATE_2); + + public static final CartDiscountValue CART_DISCOUNT_VALUE_1 = CartDiscountValue.ofRelative(1000); + public static final CartDiscountValue CART_DISCOUNT_VALUE_2 = + CartDiscountValue.ofAbsolute(MoneyImpl.of(20, EUR)); + + public static final CartDiscountTarget CART_DISCOUNT_TARGET_1 = LineItemsTarget.ofAll(); + public static final CartDiscountTarget CART_DISCOUNT_TARGET_2 = + LineItemsTarget.of("sku = \"0123456789\" or sku = \"0246891213\""); + + public static final ZonedDateTime JANUARY_FROM = ZonedDateTime.parse("2019-01-01T00:00:00.000Z"); + public static final ZonedDateTime JANUARY_UNTIL = ZonedDateTime.parse("2019-01-31T00:00:00.000Z"); + public static final ZonedDateTime FEBRUARY_FROM = ZonedDateTime.parse("2019-02-01T00:00:00.000Z"); + public static final ZonedDateTime FEBRUARY_UNTIL = + ZonedDateTime.parse("2019-02-28T00:00:00.000Z"); + + public static final String SORT_ORDER_1 = "0.1"; + public static final String SORT_ORDER_2 = "0.2"; + + public static final String OLD_CART_DISCOUNT_TYPE_KEY = "oldCartDiscountCustomTypeKey"; + public static final String OLD_CART_DISCOUNT_TYPE_NAME = "oldCartDiscountCustomTypeName"; + + public static final CartDiscountDraft CART_DISCOUNT_DRAFT_1 = + CartDiscountDraftBuilder.of( + CART_DISCOUNT_NAME_1, + CART_DISCOUNT_CART_PREDICATE_1, + CART_DISCOUNT_VALUE_1, + CART_DISCOUNT_TARGET_1, + SORT_ORDER_1, + false) + .key(CART_DISCOUNT_KEY_1) + .active(false) + .description(CART_DISCOUNT_DESC_1) + .validFrom(JANUARY_FROM) + .validUntil(JANUARY_UNTIL) + .custom(getCustomFieldsDraft()) + .build(); + + public static final CartDiscountDraft CART_DISCOUNT_DRAFT_2 = + CartDiscountDraftBuilder.of( + CART_DISCOUNT_NAME_2, + CART_DISCOUNT_CART_PREDICATE_2, + CART_DISCOUNT_VALUE_2, + CART_DISCOUNT_TARGET_2, + SORT_ORDER_2, + false) + .key(CART_DISCOUNT_KEY_2) + .active(false) + .description(CART_DISCOUNT_DESC_2) + .validFrom(FEBRUARY_FROM) + .validUntil(FEBRUARY_UNTIL) + .custom(getCustomFieldsDraft()) + .build(); + + public static CustomFieldsDraft getCustomFieldsDraft() { + return CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap()); + } + + /** + * Deletes all cart discounts from CTP project, represented by provided {@code ctpClient}. + * + * @param ctpClient represents the CTP project the cart discounts will be deleted from. + */ + public static void deleteCartDiscounts(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, CartDiscountQuery.of(), CartDiscountDeleteCommand::of); + } + + /** + * Deletes all cart discounts from CTP projects defined by {@code CTP_SOURCE_CLIENT} and {@code + * CTP_TARGET_CLIENT}. + */ + public static void deleteCartDiscountsFromTargetAndSource() { + deleteCartDiscounts(CTP_SOURCE_CLIENT); + deleteCartDiscounts(CTP_TARGET_CLIENT); + } + + public static void populateSourceProject() { + createCartDiscountCustomType( + OLD_CART_DISCOUNT_TYPE_KEY, Locale.ENGLISH, OLD_CART_DISCOUNT_TYPE_NAME, CTP_SOURCE_CLIENT); + + final CartDiscountDraft draft1 = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CartDiscountDraft draft2 = CartDiscountDraftBuilder - .of(CART_DISCOUNT_DRAFT_2) - .custom(CustomFieldsDraft - .ofTypeKeyAndJson(OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) + final CartDiscountDraft draft2 = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - CompletableFuture.allOf( + CompletableFuture.allOf( CTP_SOURCE_CLIENT.execute(CartDiscountCreateCommand.of(draft1)).toCompletableFuture(), CTP_SOURCE_CLIENT.execute(CartDiscountCreateCommand.of(draft2)).toCompletableFuture()) - .join(); - } - - public static Type createCartDiscountCustomType(@Nonnull final String typeKey, - @Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final SphereClient ctpClient) { - - return createTypeIfNotAlreadyExisting( - typeKey, - locale, - name, - ResourceTypeIdsSetBuilder.of().add("cart-discount"), - ctpClient); - } - - public static void populateTargetProject() { - createCartDiscountCustomType(OLD_CART_DISCOUNT_TYPE_KEY, - Locale.ENGLISH, - OLD_CART_DISCOUNT_TYPE_NAME, - CTP_TARGET_CLIENT); - - final CartDiscountDraft draft1 = CartDiscountDraftBuilder - .of(CART_DISCOUNT_DRAFT_1) - .custom(CustomFieldsDraft - .ofTypeKeyAndJson(OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) + .join(); + } + + public static Type createCartDiscountCustomType( + @Nonnull final String typeKey, + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final SphereClient ctpClient) { + + return createTypeIfNotAlreadyExisting( + typeKey, locale, name, ResourceTypeIdsSetBuilder.of().add("cart-discount"), ctpClient); + } + + public static void populateTargetProject() { + createCartDiscountCustomType( + OLD_CART_DISCOUNT_TYPE_KEY, Locale.ENGLISH, OLD_CART_DISCOUNT_TYPE_NAME, CTP_TARGET_CLIENT); + + final CartDiscountDraft draft1 = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft1)).toCompletableFuture().join(); - } - - /** - * Tries to fetch cart discount of {@code key} using {@code sphereClient}. - * - * @param sphereClient sphere client used to execute requests. - * @param key key of requested cart discount. - * @return {@link Optional} which may contain type of {@code key}. - */ - public static Optional getCartDiscountByKey( - @Nonnull final SphereClient sphereClient, - @Nonnull final String key) { - - final CartDiscountQuery query = CartDiscountQueryBuilder - .of() + CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft1)).toCompletableFuture().join(); + } + + /** + * Tries to fetch cart discount of {@code key} using {@code sphereClient}. + * + * @param sphereClient sphere client used to execute requests. + * @param key key of requested cart discount. + * @return {@link Optional} which may contain type of {@code key}. + */ + public static Optional getCartDiscountByKey( + @Nonnull final SphereClient sphereClient, @Nonnull final String key) { + + final CartDiscountQuery query = + CartDiscountQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().is(key)) .build(); - return sphereClient.execute(query).toCompletableFuture().join().head(); - } - - /** - * Builds a list of odd sortOrder strings that start with `0.01`. They are odd because - * because sortOrder in CTP is not allowed to end with a zero and should be decimal value between 0 and 1. - * - * @param capacity the number of sort orders to build. - */ - public static List getSortOrders(final int capacity) { - return IntStream.range(0, capacity * 2) - .filter(index -> index % 2 != 0) - .mapToObj(oddNumber -> format("0.0%s", oddNumber)) - .collect(toList()); - } - - private CartDiscountITUtils() { - } + return sphereClient.execute(query).toCompletableFuture().join().head(); + } + + /** + * Builds a list of odd sortOrder strings that start with `0.01`. They are odd because because + * sortOrder in CTP is not allowed to end with a zero and should be decimal value between 0 and 1. + * + * @param capacity the number of sort orders to build. + */ + public static List getSortOrders(final int capacity) { + return IntStream.range(0, capacity * 2) + .filter(index -> index % 2 != 0) + .mapToObj(oddNumber -> format("0.0%s", oddNumber)) + .collect(toList()); + } + + private CartDiscountITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CategoryITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CategoryITUtils.java index 9692fe4dc2..c172d9bb41 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CategoryITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CategoryITUtils.java @@ -1,5 +1,11 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createTypeIfNotAlreadyExisting; +import static io.sphere.sdk.models.ResourceIdentifier.ofKey; +import static io.sphere.sdk.utils.CompletableFutureUtils.listOfFuturesToFutureOfList; +import static java.lang.String.format; + import com.commercetools.sync.categories.CategorySync; import com.commercetools.sync.categories.helpers.CategorySyncStatistics; import io.sphere.sdk.categories.Category; @@ -18,9 +24,6 @@ import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.ResourceTypeIdsSetBuilder; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -33,301 +36,323 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; - -import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createTypeIfNotAlreadyExisting; -import static io.sphere.sdk.models.ResourceIdentifier.ofKey; -import static io.sphere.sdk.utils.CompletableFutureUtils.listOfFuturesToFutureOfList; -import static java.lang.String.format; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class CategoryITUtils { - public static final String OLD_CATEGORY_CUSTOM_TYPE_KEY = "oldCategoryCustomTypeKey"; - public static final String OLD_CATEGORY_CUSTOM_TYPE_NAME = "old_type_name"; - - /** - * Builds a list of the supplied number ({@code numberOfCategories}) of CategoryDraft objects that can be used for - * integration tests to mimic existing categories in a target CTP project for example. All the newly created - * category drafts will have {@code parentCategory} as a parent. - * - * @param numberOfCategories the number of category drafts to create. - * @param parentCategory the parent of the drafts. - * @return a list of CategoryDrafts. - */ - public static List getCategoryDrafts(@Nullable final Category parentCategory, - final int numberOfCategories) { - List categoryDrafts = new ArrayList<>(); - for (int i = 0; i < numberOfCategories; i++) { - final LocalizedString name = LocalizedString.of(Locale.ENGLISH, format("draft%s", i + 1)); - final LocalizedString slug = LocalizedString.of(Locale.ENGLISH, format("slug%s", i + 1)); - final LocalizedString description = LocalizedString.of(Locale.ENGLISH, format("desc%s", i + 1)); - final String key = format("key%s", i + 1); - final String orderHint = format("0.%s", i + 1); - final CategoryDraft categoryDraft = CategoryDraftBuilder.of(name, slug) - .parent(parentCategory != null - ? ofKey(parentCategory.getKey()) : null) - .description(description) - .key(key) - .orderHint(orderHint) - .custom(getCustomFieldsDraft()) - .build(); - categoryDrafts.add(categoryDraft); - } - return categoryDrafts; - } + public static final String OLD_CATEGORY_CUSTOM_TYPE_KEY = "oldCategoryCustomTypeKey"; + public static final String OLD_CATEGORY_CUSTOM_TYPE_NAME = "old_type_name"; - /** - * Builds a list of the supplied number ({@code numberOfCategories}) of CategoryDraft objects (with a customized - * prefix string for the name, slug and description) that can be used for integration tests to mimic existing - * categories in a target CTP project for example. All the newly created category drafts will have - * {@code parentCategory} as a parent. - * - * @param numberOfCategories the number of category drafts to create. - * @param parentCategory the parent of the drafts. - * @return a list of CategoryDrafts. - */ - public static List getCategoryDraftsWithPrefix(@Nonnull final Locale locale, - @Nonnull final String prefix, - @Nullable final Category parentCategory, - final int numberOfCategories) { - final List categoryDraftsWithPrefix = new ArrayList<>(); - final List categoryDrafts = getCategoryDrafts(parentCategory, numberOfCategories); - for (CategoryDraft categoryDraft : categoryDrafts) { - final LocalizedString newCategoryName = LocalizedString.of(locale, - format("%s%s", prefix, categoryDraft.getName().get(locale))); - final LocalizedString newCategorySlug = LocalizedString.of(locale, - format("%s%s", prefix, categoryDraft.getSlug().get(locale))); - final LocalizedString newCategoryDescription = LocalizedString.of(locale, - format("%s%s", prefix, categoryDraft.getDescription().get(locale))); - final CategoryDraftBuilder categoryDraftBuilder = CategoryDraftBuilder.of(categoryDraft) - .name(newCategoryName) - .slug(newCategorySlug) - .description(newCategoryDescription); - categoryDraftsWithPrefix.add(categoryDraftBuilder.build()); - } - return categoryDraftsWithPrefix; + /** + * Builds a list of the supplied number ({@code numberOfCategories}) of CategoryDraft objects that + * can be used for integration tests to mimic existing categories in a target CTP project for + * example. All the newly created category drafts will have {@code parentCategory} as a parent. + * + * @param numberOfCategories the number of category drafts to create. + * @param parentCategory the parent of the drafts. + * @return a list of CategoryDrafts. + */ + public static List getCategoryDrafts( + @Nullable final Category parentCategory, final int numberOfCategories) { + List categoryDrafts = new ArrayList<>(); + for (int i = 0; i < numberOfCategories; i++) { + final LocalizedString name = LocalizedString.of(Locale.ENGLISH, format("draft%s", i + 1)); + final LocalizedString slug = LocalizedString.of(Locale.ENGLISH, format("slug%s", i + 1)); + final LocalizedString description = + LocalizedString.of(Locale.ENGLISH, format("desc%s", i + 1)); + final String key = format("key%s", i + 1); + final String orderHint = format("0.%s", i + 1); + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of(name, slug) + .parent(parentCategory != null ? ofKey(parentCategory.getKey()) : null) + .description(description) + .key(key) + .orderHint(orderHint) + .custom(getCustomFieldsDraft()) + .build(); + categoryDrafts.add(categoryDraft); } + return categoryDrafts; + } - /** - * This method creates {@code numberOfChildren} categories as children to the supplied {@code parent} category in - * the supplied {@link SphereClient} project in a blocking fashion. It assigns them a key, and an - * {@code Locale.ENGLISH} name and slug of the value of the supplied {@code prefix} appended to the - * (index of the child + 1). For example, if the prefix supplied is {@code "cat"}, the key and the english locales - * of the name and the slug would be {@code "cat1"} for the first child. - * - * @param numberOfChildren the number of children categories to create. - * @param parent the parent category to assign these children to. - * @param prefix a prefix to string to prepend to index of the children, to assign it as a key, name and - * slug to each of the created categories. - * @param ctpClient the ctpClient that defines the CTP project to create the categories on. - * @return the list of Categories created. - */ - public static List createChildren(final int numberOfChildren, - @Nullable final Category parent, - @Nonnull final String prefix, - @Nonnull final SphereClient ctpClient) { - final List children = new ArrayList<>(); - final List> futures = new ArrayList<>(); - for (int i = 0; i < numberOfChildren; i++) { - final String categoryName = prefix + (i + 1); - CategoryDraftBuilder childBuilder = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, categoryName), - LocalizedString.of(Locale.ENGLISH, categoryName)) - .key(categoryName) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) - .orderHint("sameOrderHint"); - if (parent != null) { - childBuilder = childBuilder.parent(ofKey(parent.getKey())); - } - CategoryDraft child = childBuilder.build(); - final CompletableFuture future = ctpClient - .execute(CategoryCreateCommand.of(child)).toCompletableFuture(); - futures.add(future); - } - CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])) - .thenAccept(voidResult -> { - for (CompletableFuture creationFuture : futures) { - children.add(creationFuture.join()); - } - }).join(); - return children; + /** + * Builds a list of the supplied number ({@code numberOfCategories}) of CategoryDraft objects + * (with a customized prefix string for the name, slug and description) that can be used for + * integration tests to mimic existing categories in a target CTP project for example. All the + * newly created category drafts will have {@code parentCategory} as a parent. + * + * @param numberOfCategories the number of category drafts to create. + * @param parentCategory the parent of the drafts. + * @return a list of CategoryDrafts. + */ + public static List getCategoryDraftsWithPrefix( + @Nonnull final Locale locale, + @Nonnull final String prefix, + @Nullable final Category parentCategory, + final int numberOfCategories) { + final List categoryDraftsWithPrefix = new ArrayList<>(); + final List categoryDrafts = + getCategoryDrafts(parentCategory, numberOfCategories); + for (CategoryDraft categoryDraft : categoryDrafts) { + final LocalizedString newCategoryName = + LocalizedString.of(locale, format("%s%s", prefix, categoryDraft.getName().get(locale))); + final LocalizedString newCategorySlug = + LocalizedString.of(locale, format("%s%s", prefix, categoryDraft.getSlug().get(locale))); + final LocalizedString newCategoryDescription = + LocalizedString.of( + locale, format("%s%s", prefix, categoryDraft.getDescription().get(locale))); + final CategoryDraftBuilder categoryDraftBuilder = + CategoryDraftBuilder.of(categoryDraft) + .name(newCategoryName) + .slug(newCategorySlug) + .description(newCategoryDescription); + categoryDraftsWithPrefix.add(categoryDraftBuilder.build()); } + return categoryDraftsWithPrefix; + } - /** - * Creates a dummy instance of {@link CustomFieldsDraft} with the key defined by - * {@code OLD_CATEGORY_CUSTOM_TYPE_KEY} and two custom fields 'invisibleInShop' & 'backgroundColor'. - * - *

The 'invisibleInShop' field is of type {@code boolean} and has value {@code false}. The 'backgroundColor' - * field is of type {@code localisedString} and has the values {"de": "rot", "en": "red"}. - * - * @return a dummy instance of {@link CustomFieldsDraft} with some hardcoded custom fields and key. - */ - public static CustomFieldsDraft getCustomFieldsDraft() { - return CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap()); + /** + * This method creates {@code numberOfChildren} categories as children to the supplied {@code + * parent} category in the supplied {@link SphereClient} project in a blocking fashion. It assigns + * them a key, and an {@code Locale.ENGLISH} name and slug of the value of the supplied {@code + * prefix} appended to the (index of the child + 1). For example, if the prefix supplied is {@code + * "cat"}, the key and the english locales of the name and the slug would be {@code "cat1"} for + * the first child. + * + * @param numberOfChildren the number of children categories to create. + * @param parent the parent category to assign these children to. + * @param prefix a prefix to string to prepend to index of the children, to assign it as a key, + * name and slug to each of the created categories. + * @param ctpClient the ctpClient that defines the CTP project to create the categories on. + * @return the list of Categories created. + */ + public static List createChildren( + final int numberOfChildren, + @Nullable final Category parent, + @Nonnull final String prefix, + @Nonnull final SphereClient ctpClient) { + final List children = new ArrayList<>(); + final List> futures = new ArrayList<>(); + for (int i = 0; i < numberOfChildren; i++) { + final String categoryName = prefix + (i + 1); + CategoryDraftBuilder childBuilder = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, categoryName), + LocalizedString.of(Locale.ENGLISH, categoryName)) + .key(categoryName) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .orderHint("sameOrderHint"); + if (parent != null) { + childBuilder = childBuilder.parent(ofKey(parent.getKey())); + } + CategoryDraft child = childBuilder.build(); + final CompletableFuture future = + ctpClient.execute(CategoryCreateCommand.of(child)).toCompletableFuture(); + futures.add(future); } + CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])) + .thenAccept( + voidResult -> { + for (CompletableFuture creationFuture : futures) { + children.add(creationFuture.join()); + } + }) + .join(); + return children; + } - /** - * This method blocks to create the supplied {@code categoryDrafts} in the CTP project defined by the supplied - * {@code ctpClient}, - * - *

Note: the method creates the given categories in parallel. So it expects them all to be in the same hierarchy - * level. - * - * @param ctpClient defines the CTP project to create the categories on. - * @param categoryDrafts the drafts to build the categories from. - */ - public static List createCategories(@Nonnull final SphereClient ctpClient, - @Nonnull final List categoryDrafts) { - final List> futures = new ArrayList<>(); - for (CategoryDraft categoryDraft : categoryDrafts) { - final CategoryCreateCommand categoryCreateCommand = CategoryCreateCommand.of(categoryDraft); - final CompletableFuture categoryCompletableFuture = - ctpClient.execute(categoryCreateCommand).toCompletableFuture(); - futures.add(categoryCompletableFuture); + /** + * Creates a dummy instance of {@link CustomFieldsDraft} with the key defined by {@code + * OLD_CATEGORY_CUSTOM_TYPE_KEY} and two custom fields 'invisibleInShop' & 'backgroundColor'. + * + *

The 'invisibleInShop' field is of type {@code boolean} and has value {@code false}. The + * 'backgroundColor' field is of type {@code localisedString} and has the values {"de": "rot", + * "en": "red"}. + * + * @return a dummy instance of {@link CustomFieldsDraft} with some hardcoded custom fields and + * key. + */ + public static CustomFieldsDraft getCustomFieldsDraft() { + return CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap()); + } - } - - return listOfFuturesToFutureOfList(futures).join(); + /** + * This method blocks to create the supplied {@code categoryDrafts} in the CTP project defined by + * the supplied {@code ctpClient}, + * + *

Note: the method creates the given categories in parallel. So it expects them all to be in + * the same hierarchy level. + * + * @param ctpClient defines the CTP project to create the categories on. + * @param categoryDrafts the drafts to build the categories from. + */ + public static List createCategories( + @Nonnull final SphereClient ctpClient, @Nonnull final List categoryDrafts) { + final List> futures = new ArrayList<>(); + for (CategoryDraft categoryDraft : categoryDrafts) { + final CategoryCreateCommand categoryCreateCommand = CategoryCreateCommand.of(categoryDraft); + final CompletableFuture categoryCompletableFuture = + ctpClient.execute(categoryCreateCommand).toCompletableFuture(); + futures.add(categoryCompletableFuture); } - /** - * This method blocks to create a category custom Type on the CTP project defined by the supplied - * {@code ctpClient}, with the supplied data. - * - * @param typeKey the type key - * @param locale the locale to be used for specifying the type name and field definitions names. - * @param name the name of the custom type. - * @param ctpClient defines the CTP project to create the type on. - */ - public static Type createCategoriesCustomType(@Nonnull final String typeKey, - @Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final SphereClient ctpClient) { + return listOfFuturesToFutureOfList(futures).join(); + } - return createTypeIfNotAlreadyExisting(typeKey, locale, name, ResourceTypeIdsSetBuilder.of().addCategories(), - ctpClient); - } + /** + * This method blocks to create a category custom Type on the CTP project defined by the supplied + * {@code ctpClient}, with the supplied data. + * + * @param typeKey the type key + * @param locale the locale to be used for specifying the type name and field definitions names. + * @param name the name of the custom type. + * @param ctpClient defines the CTP project to create the type on. + */ + public static Type createCategoriesCustomType( + @Nonnull final String typeKey, + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final SphereClient ctpClient) { + return createTypeIfNotAlreadyExisting( + typeKey, locale, name, ResourceTypeIdsSetBuilder.of().addCategories(), ctpClient); + } - /** - * Deletes all categories from CTP projects defined by the {@code ctpClient}. Only issues delete request action to a - * category in the case that none of its ancestors was already deleted or not to avoid trying to delete a category - * which would have been already deleted, due to deletion of an ancestor of it. As a performance improvement, this - * method sorts categories by least ancestors for faster deletion (due to deletion of ancestors always first, which - * in turn deletes all the children and grand children. - * - * @param ctpClient defines the CTP project to delete the categories from. - */ - public static void deleteAllCategories(@Nonnull final SphereClient ctpClient) { - final Set keys = new HashSet<>(); - final List categories = QueryExecutionUtils.queryAll(ctpClient, - CategoryQuery.of().withExpansionPaths(CategoryExpansionModel::ancestors)) - .thenApply(CategoryITUtils::sortCategoriesByLeastAncestors) - .toCompletableFuture().join(); - categories.forEach(category -> { - final String categoryKey = category.getKey(); - if (!hasADeletedAncestor(category, keys)) { - ctpClient.execute(CategoryDeleteCommand.of(category)) - .thenAccept(deletedCategory -> keys.add(categoryKey)) - .toCompletableFuture().join(); - } + /** + * Deletes all categories from CTP projects defined by the {@code ctpClient}. Only issues delete + * request action to a category in the case that none of its ancestors was already deleted or not + * to avoid trying to delete a category which would have been already deleted, due to deletion of + * an ancestor of it. As a performance improvement, this method sorts categories by least + * ancestors for faster deletion (due to deletion of ancestors always first, which in turn deletes + * all the children and grand children. + * + * @param ctpClient defines the CTP project to delete the categories from. + */ + public static void deleteAllCategories(@Nonnull final SphereClient ctpClient) { + final Set keys = new HashSet<>(); + final List categories = + QueryExecutionUtils.queryAll( + ctpClient, CategoryQuery.of().withExpansionPaths(CategoryExpansionModel::ancestors)) + .thenApply(CategoryITUtils::sortCategoriesByLeastAncestors) + .toCompletableFuture() + .join(); + categories.forEach( + category -> { + final String categoryKey = category.getKey(); + if (!hasADeletedAncestor(category, keys)) { + ctpClient + .execute(CategoryDeleteCommand.of(category)) + .thenAccept(deletedCategory -> keys.add(categoryKey)) + .toCompletableFuture() + .join(); + } }); - } + } - private static List sortCategoriesByLeastAncestors(@Nonnull final List categories) { - categories.sort(Comparator.comparingInt(category -> category.getAncestors().size())); - return categories; - } - - private static boolean hasADeletedAncestor(@Nonnull final Category category, - @Nonnull final Set keysOfDeletedAncestors) { - final List> categoryAncestors = category.getAncestors(); - return categoryAncestors.stream().anyMatch(ancestor -> - keysOfDeletedAncestors.contains(ancestor.getObj().getKey())); - } + private static List sortCategoriesByLeastAncestors( + @Nonnull final List categories) { + categories.sort(Comparator.comparingInt(category -> category.getAncestors().size())); + return categories; + } + private static boolean hasADeletedAncestor( + @Nonnull final Category category, @Nonnull final Set keysOfDeletedAncestors) { + final List> categoryAncestors = category.getAncestors(); + return categoryAncestors.stream() + .anyMatch(ancestor -> keysOfDeletedAncestors.contains(ancestor.getObj().getKey())); + } - /** - * Given a list of {@link CategoryDraft} batches represented by a - * {@link List}<{@link List}<{@link CategoryDraft}>> and an instance of {@link CategorySync}, this - * method recursively calls sync by the instance of {@link CategorySync} on each batch, then removes it, until - * there are no more batches, in other words, all batches have been synced. - * - * @param categorySync the categorySync instance to sync with each batch of {@link CategoryDraft} - * @param batches the batches of {@link CategoryDraft} to sync. - * @param result in the first call of this recursive method, this result is normally a completed future, it - * used from within the method to recursively sync each batch once the previous batch has - * finished syncing. - * @return an instance of {@link CompletionStage} which contains as a result an instance of - * {@link CategorySyncStatistics} representing the {@code statistics} of the sync process executed on the - * given list of batches. - */ - public static CompletionStage syncBatches(@Nonnull final CategorySync categorySync, - @Nonnull final List> batches, - @Nonnull final - CompletionStage result) { - if (batches.isEmpty()) { - return result; - } - final List firstBatch = batches.remove(0); - return syncBatches(categorySync, batches, result - .thenCompose(subResult -> categorySync.sync(firstBatch))); + /** + * Given a list of {@link CategoryDraft} batches represented by a {@link List}<{@link + * List}<{@link CategoryDraft}>> and an instance of {@link CategorySync}, this method + * recursively calls sync by the instance of {@link CategorySync} on each batch, then removes it, + * until there are no more batches, in other words, all batches have been synced. + * + * @param categorySync the categorySync instance to sync with each batch of {@link CategoryDraft} + * @param batches the batches of {@link CategoryDraft} to sync. + * @param result in the first call of this recursive method, this result is normally a completed + * future, it used from within the method to recursively sync each batch once the previous + * batch has finished syncing. + * @return an instance of {@link CompletionStage} which contains as a result an instance of {@link + * CategorySyncStatistics} representing the {@code statistics} of the sync process executed on + * the given list of batches. + */ + public static CompletionStage syncBatches( + @Nonnull final CategorySync categorySync, + @Nonnull final List> batches, + @Nonnull final CompletionStage result) { + if (batches.isEmpty()) { + return result; } + final List firstBatch = batches.remove(0); + return syncBatches( + categorySync, batches, result.thenCompose(subResult -> categorySync.sync(firstBatch))); + } - /** - * Builds a {@link Set} of {@link ResourceIdentifier} with keys in place of ids from the supplied {@link List} of - * {@link Category}. - * - * @param categories a {@link List} of {@link Category} from which the {@link Set} of {@link ResourceIdentifier} - * will be built. - * @return a {@link Set} of {@link ResourceIdentifier} with keys in place of ids from the supplied {@link List} of - * {@link Category}. - */ - @Nonnull - public static Set> geResourceIdentifiersWithKeys( - @Nonnull final List categories) { - return categories.stream() - .map(category -> ResourceIdentifier.ofKey(category.getKey())) - .collect(Collectors.toSet()); - } + /** + * Builds a {@link Set} of {@link ResourceIdentifier} with keys in place of ids from the supplied + * {@link List} of {@link Category}. + * + * @param categories a {@link List} of {@link Category} from which the {@link Set} of {@link + * ResourceIdentifier} will be built. + * @return a {@link Set} of {@link ResourceIdentifier} with keys in place of ids from the supplied + * {@link List} of {@link Category}. + */ + @Nonnull + public static Set> geResourceIdentifiersWithKeys( + @Nonnull final List categories) { + return categories.stream() + .map(category -> ResourceIdentifier.ofKey(category.getKey())) + .collect(Collectors.toSet()); + } - /** - * Builds a {@link List} of {@link Reference} built from the supplied {@link List} of {@link Category}. - * - * @param categories a {@link List} of {@link Category} from which the {@link List} of {@link Reference} will be - * built. - * @return a {@link List} of {@link Reference} built from the supplied {@link List} of {@link Category}. - */ - @Nonnull - public static List> getReferencesWithIds(@Nonnull final List categories) { - return categories.stream() - .map(category -> Category.referenceOfId(category.getId())) - .collect(Collectors.toList()); - } + /** + * Builds a {@link List} of {@link Reference} built from the supplied {@link List} of {@link + * Category}. + * + * @param categories a {@link List} of {@link Category} from which the {@link List} of {@link + * Reference} will be built. + * @return a {@link List} of {@link Reference} built from the supplied {@link List} of {@link + * Category}. + */ + @Nonnull + public static List> getReferencesWithIds( + @Nonnull final List categories) { + return categories.stream() + .map(category -> Category.referenceOfId(category.getId())) + .collect(Collectors.toList()); + } - /** - * Given a {@link CategoryOrderHints} instance and a {@link List} of {@link Category}, this method replaces all the - * categoryOrderHint ids with the {@link Category} keys. - * - * @param categoryOrderHints the categoryOrderHints that should have its keys replaced with ids. - * @param categories the categories that the keys would be taken from to replace on the newly created - * {@link CategoryOrderHints}. - * @return a new {@link CategoryOrderHints} instance with keys replacing the category ids. - */ - @Nonnull - public static CategoryOrderHints replaceCategoryOrderHintCategoryIdsWithKeys( - @Nonnull final CategoryOrderHints categoryOrderHints, - @Nonnull final List categories) { - final Map categoryOrderHintKeyMap = new HashMap<>(); - categoryOrderHints.getAsMap() - .forEach((categoryId, categoryOrderHintValue) -> - categories.stream() - .filter(category -> Objects.equals(category.getId(), categoryId)) - .findFirst() - .ifPresent(category -> - categoryOrderHintKeyMap.put(category.getKey(), categoryOrderHintValue))); - return CategoryOrderHints.of(categoryOrderHintKeyMap); - } + /** + * Given a {@link CategoryOrderHints} instance and a {@link List} of {@link Category}, this method + * replaces all the categoryOrderHint ids with the {@link Category} keys. + * + * @param categoryOrderHints the categoryOrderHints that should have its keys replaced with ids. + * @param categories the categories that the keys would be taken from to replace on the newly + * created {@link CategoryOrderHints}. + * @return a new {@link CategoryOrderHints} instance with keys replacing the category ids. + */ + @Nonnull + public static CategoryOrderHints replaceCategoryOrderHintCategoryIdsWithKeys( + @Nonnull final CategoryOrderHints categoryOrderHints, + @Nonnull final List categories) { + final Map categoryOrderHintKeyMap = new HashMap<>(); + categoryOrderHints + .getAsMap() + .forEach( + (categoryId, categoryOrderHintValue) -> + categories.stream() + .filter(category -> Objects.equals(category.getId(), categoryId)) + .findFirst() + .ifPresent( + category -> + categoryOrderHintKeyMap.put( + category.getKey(), categoryOrderHintValue))); + return CategoryOrderHints.of(categoryOrderHintKeyMap); + } - private CategoryITUtils() { - } + private CategoryITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ChannelITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ChannelITUtils.java index 2d4be54275..39cfa6bf99 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ChannelITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ChannelITUtils.java @@ -1,5 +1,10 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; + import io.sphere.sdk.channels.Channel; import io.sphere.sdk.channels.ChannelDraft; import io.sphere.sdk.channels.ChannelDraftBuilder; @@ -7,50 +12,44 @@ import io.sphere.sdk.channels.commands.ChannelDeleteCommand; import io.sphere.sdk.channels.queries.ChannelQuery; import io.sphere.sdk.client.SphereClient; - import javax.annotation.Nonnull; -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; - public final class ChannelITUtils { - /** - * Deletes all Channels from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and - * {@code CTP_TARGET_CLIENT}. - */ - public static void deleteChannelsFromTargetAndSource() { - deleteChannels(CTP_TARGET_CLIENT); - deleteChannels(CTP_SOURCE_CLIENT); - } - - /** - * Deletes all Channels from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the Channels from. - */ - public static void deleteChannels(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, ChannelQuery.of(), ChannelDeleteCommand::of); - } - - /** - * Creates a {@link Channel} in the CTP project defined by the {@code ctpClient} in a blocking fashion. - * - * @param ctpClient defines the CTP project to create the Channels in. - * @param name the name of the channel to create. - * @param key the key of the channel to create. - * @return the created Channel. - */ - public static Channel createChannel(@Nonnull final SphereClient ctpClient, @Nonnull final String name, - @Nonnull final String key) { - final ChannelDraft channelDraft = ChannelDraftBuilder.of(name) - .key(key) - .build(); - - return executeBlocking(ctpClient.execute(ChannelCreateCommand.of(channelDraft))); - } - - private ChannelITUtils() { - } + /** + * Deletes all Channels from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and {@code + * CTP_TARGET_CLIENT}. + */ + public static void deleteChannelsFromTargetAndSource() { + deleteChannels(CTP_TARGET_CLIENT); + deleteChannels(CTP_SOURCE_CLIENT); + } + + /** + * Deletes all Channels from the CTP project defined by the {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the Channels from. + */ + public static void deleteChannels(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, ChannelQuery.of(), ChannelDeleteCommand::of); + } + + /** + * Creates a {@link Channel} in the CTP project defined by the {@code ctpClient} in a blocking + * fashion. + * + * @param ctpClient defines the CTP project to create the Channels in. + * @param name the name of the channel to create. + * @param key the key of the channel to create. + * @return the created Channel. + */ + public static Channel createChannel( + @Nonnull final SphereClient ctpClient, + @Nonnull final String name, + @Nonnull final String key) { + final ChannelDraft channelDraft = ChannelDraftBuilder.of(name).key(key).build(); + + return executeBlocking(ctpClient.execute(ChannelCreateCommand.of(channelDraft))); + } + + private ChannelITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomObjectITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomObjectITUtils.java index a7927a5362..ac94975dca 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomObjectITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomObjectITUtils.java @@ -11,144 +11,145 @@ import io.sphere.sdk.customobjects.queries.CustomObjectQuery; import io.sphere.sdk.customobjects.queries.CustomObjectQueryBuilder; import io.sphere.sdk.queries.PagedQueryResult; - - -import javax.annotation.Nonnull; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - +import javax.annotation.Nonnull; public final class CustomObjectITUtils { - private static final String PRODUCT_CUSTOM_OBJECT_CONTAINER_KEY = - "commercetools-sync-java.UnresolvedReferencesService.productDrafts"; - - - - /** - * Deletes customObjects from CTP project which have key/container used in integration test, - * represented by provided {@code ctpClient}. - * - * @param ctpClient represents the CTP project the custom objects will be deleted from. - */ - public static void deleteCustomObject( - @Nonnull final SphereClient ctpClient, - @Nonnull final String key, - @Nonnull final String container) { - - final CustomObjectQuery customObjectQuery = - CustomObjectQueryBuilder.ofJsonNode() - .plusPredicates(queryModel -> queryModel.container().is(container)) - .plusPredicates(queryModel -> queryModel.key().is(key)) - .build(); - - ctpClient - .execute(customObjectQuery) - .thenApply(PagedQueryResult::getResults) - .thenCompose(customObjects -> deleteCustomObject(ctpClient, customObjects)) - .toCompletableFuture() - .join(); - } - - @Nonnull - private static CompletableFuture deleteCustomObject( - @Nonnull final SphereClient ctpClient, - @Nonnull final List> customObjects) { - - return CompletableFuture.allOf( - customObjects - .stream() - .map(customObject -> ctpClient - .execute(CustomObjectDeleteCommand.of(customObject, JsonNode.class))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)); - } - - /** - * Create a custom object in target CTP project, represented by provided {@code ctpClient}. - * - * @param ctpClient represents the CTP project the custom object will be created. - * @param key represents the key field required in custom object. - * @param container represents the container field required in custom object. - * @param value represents the value field required in custom object. - */ - public static CustomObject createCustomObject( - @Nonnull final SphereClient ctpClient, - @Nonnull final String key, - @Nonnull final String container, - @Nonnull final JsonNode value) { - - CustomObjectDraft customObjectDraft = CustomObjectDraft.ofUnversionedUpsert(container,key, value); - return ctpClient.execute(CustomObjectUpsertCommand.of(customObjectDraft)).toCompletableFuture().join(); - } - - /** - * This method is expected to be used only by tests, it only works on projects with less than or equal to 20 custom - * objects. Otherwise, it won't delete all the custom objects in the project of the client. - * - * @param ctpClient the client to delete the custom objects from. - */ - public static void deleteWaitingToBeResolvedCustomObjects(@Nonnull final SphereClient ctpClient) { - - final CustomObjectQuery customObjectQuery = - CustomObjectQuery - .of(WaitingToBeResolved.class) - .byContainer(PRODUCT_CUSTOM_OBJECT_CONTAINER_KEY); - - ctpClient - .execute(customObjectQuery) - .thenApply(PagedQueryResult::getResults) - .thenCompose(customObjects -> deleteWaitingToBeResolvedCustomObjects(ctpClient, customObjects)) - .toCompletableFuture() - .join(); - } - - @Nonnull - private static CompletableFuture deleteWaitingToBeResolvedCustomObjects( - @Nonnull final SphereClient ctpClient, - @Nonnull final List> customObjects) { - - return CompletableFuture.allOf( - customObjects - .stream() - .map(customObject -> ctpClient - .execute(CustomObjectDeleteCommand.of(customObject, WaitingToBeResolved.class))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)); - } - - public static void deleteWaitingToBeResolvedTransitionsCustomObjects( - @Nonnull final SphereClient ctpClient, - @Nonnull final String customObjectKey) { - - final CustomObjectQuery customObjectQuery = - CustomObjectQuery - .of(WaitingToBeResolvedTransitions.class) - .byContainer(customObjectKey); - - ctpClient - .execute(customObjectQuery) - .thenApply(PagedQueryResult::getResults) - .thenCompose(customObjects -> deleteWaitingToBeResolvedTransitionsCustomObjects(ctpClient, customObjects)) - .toCompletableFuture() - .join(); - } - - @Nonnull - private static CompletableFuture deleteWaitingToBeResolvedTransitionsCustomObjects( - @Nonnull final SphereClient ctpClient, - @Nonnull final List> customObjects) { - - return CompletableFuture.allOf( - customObjects - .stream() - .map(customObject -> ctpClient - .execute(CustomObjectDeleteCommand.of(customObject, WaitingToBeResolvedTransitions.class))) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)); - } - - private CustomObjectITUtils() { - } + private static final String PRODUCT_CUSTOM_OBJECT_CONTAINER_KEY = + "commercetools-sync-java.UnresolvedReferencesService.productDrafts"; + + /** + * Deletes customObjects from CTP project which have key/container used in integration test, + * represented by provided {@code ctpClient}. + * + * @param ctpClient represents the CTP project the custom objects will be deleted from. + */ + public static void deleteCustomObject( + @Nonnull final SphereClient ctpClient, + @Nonnull final String key, + @Nonnull final String container) { + + final CustomObjectQuery customObjectQuery = + CustomObjectQueryBuilder.ofJsonNode() + .plusPredicates(queryModel -> queryModel.container().is(container)) + .plusPredicates(queryModel -> queryModel.key().is(key)) + .build(); + + ctpClient + .execute(customObjectQuery) + .thenApply(PagedQueryResult::getResults) + .thenCompose(customObjects -> deleteCustomObject(ctpClient, customObjects)) + .toCompletableFuture() + .join(); + } + + @Nonnull + private static CompletableFuture deleteCustomObject( + @Nonnull final SphereClient ctpClient, + @Nonnull final List> customObjects) { + + return CompletableFuture.allOf( + customObjects.stream() + .map( + customObject -> + ctpClient.execute(CustomObjectDeleteCommand.of(customObject, JsonNode.class))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)); + } + + /** + * Create a custom object in target CTP project, represented by provided {@code ctpClient}. + * + * @param ctpClient represents the CTP project the custom object will be created. + * @param key represents the key field required in custom object. + * @param container represents the container field required in custom object. + * @param value represents the value field required in custom object. + */ + public static CustomObject createCustomObject( + @Nonnull final SphereClient ctpClient, + @Nonnull final String key, + @Nonnull final String container, + @Nonnull final JsonNode value) { + + CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert(container, key, value); + return ctpClient + .execute(CustomObjectUpsertCommand.of(customObjectDraft)) + .toCompletableFuture() + .join(); + } + + /** + * This method is expected to be used only by tests, it only works on projects with less than or + * equal to 20 custom objects. Otherwise, it won't delete all the custom objects in the project of + * the client. + * + * @param ctpClient the client to delete the custom objects from. + */ + public static void deleteWaitingToBeResolvedCustomObjects(@Nonnull final SphereClient ctpClient) { + + final CustomObjectQuery customObjectQuery = + CustomObjectQuery.of(WaitingToBeResolved.class) + .byContainer(PRODUCT_CUSTOM_OBJECT_CONTAINER_KEY); + + ctpClient + .execute(customObjectQuery) + .thenApply(PagedQueryResult::getResults) + .thenCompose( + customObjects -> deleteWaitingToBeResolvedCustomObjects(ctpClient, customObjects)) + .toCompletableFuture() + .join(); + } + + @Nonnull + private static CompletableFuture deleteWaitingToBeResolvedCustomObjects( + @Nonnull final SphereClient ctpClient, + @Nonnull final List> customObjects) { + + return CompletableFuture.allOf( + customObjects.stream() + .map( + customObject -> + ctpClient.execute( + CustomObjectDeleteCommand.of(customObject, WaitingToBeResolved.class))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)); + } + + public static void deleteWaitingToBeResolvedTransitionsCustomObjects( + @Nonnull final SphereClient ctpClient, @Nonnull final String customObjectKey) { + + final CustomObjectQuery customObjectQuery = + CustomObjectQuery.of(WaitingToBeResolvedTransitions.class).byContainer(customObjectKey); + + ctpClient + .execute(customObjectQuery) + .thenApply(PagedQueryResult::getResults) + .thenCompose( + customObjects -> + deleteWaitingToBeResolvedTransitionsCustomObjects(ctpClient, customObjects)) + .toCompletableFuture() + .join(); + } + + @Nonnull + private static CompletableFuture deleteWaitingToBeResolvedTransitionsCustomObjects( + @Nonnull final SphereClient ctpClient, + @Nonnull final List> customObjects) { + + return CompletableFuture.allOf( + customObjects.stream() + .map( + customObject -> + ctpClient.execute( + CustomObjectDeleteCommand.of( + customObject, WaitingToBeResolvedTransitions.class))) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)); + } + + private CustomObjectITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomerGroupITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomerGroupITUtils.java index 1efff64bab..3a1c2b13cc 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomerGroupITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomerGroupITUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; + import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customergroups.CustomerGroup; import io.sphere.sdk.customergroups.CustomerGroupDraft; @@ -7,40 +10,37 @@ import io.sphere.sdk.customergroups.commands.CustomerGroupCreateCommand; import io.sphere.sdk.customergroups.commands.CustomerGroupDeleteCommand; import io.sphere.sdk.customergroups.queries.CustomerGroupQuery; - import javax.annotation.Nonnull; -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; - public final class CustomerGroupITUtils { - /** - * Deletes all CustomerGroups from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the CustomerGroups from. - */ - public static void deleteCustomerGroups(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, CustomerGroupQuery.of(), CustomerGroupDeleteCommand::of); - } - - /** - * Creates a {@link CustomerGroup} in the CTP project defined by the {@code ctpClient} in a blocking fashion. - * - * @param ctpClient defines the CTP project to create the CustomerGroup in. - * @param name the name of the CustomerGroup to create. - * @param key the key of the CustomerGroup to create. - * @return the created CustomerGroup. - */ - public static CustomerGroup createCustomerGroup(@Nonnull final SphereClient ctpClient, @Nonnull final String name, - @Nonnull final String key) { - final CustomerGroupDraft customerGroupDraft = CustomerGroupDraftBuilder.of(name) - .key(key) - .build(); - - return executeBlocking(ctpClient.execute(CustomerGroupCreateCommand.of(customerGroupDraft))); - } - - private CustomerGroupITUtils() { - } + /** + * Deletes all CustomerGroups from the CTP project defined by the {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the CustomerGroups from. + */ + public static void deleteCustomerGroups(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, CustomerGroupQuery.of(), CustomerGroupDeleteCommand::of); + } + + /** + * Creates a {@link CustomerGroup} in the CTP project defined by the {@code ctpClient} in a + * blocking fashion. + * + * @param ctpClient defines the CTP project to create the CustomerGroup in. + * @param name the name of the CustomerGroup to create. + * @param key the key of the CustomerGroup to create. + * @return the created CustomerGroup. + */ + public static CustomerGroup createCustomerGroup( + @Nonnull final SphereClient ctpClient, + @Nonnull final String name, + @Nonnull final String key) { + final CustomerGroupDraft customerGroupDraft = + CustomerGroupDraftBuilder.of(name).key(key).build(); + + return executeBlocking(ctpClient.execute(CustomerGroupCreateCommand.of(customerGroupDraft))); + } + + private CustomerGroupITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomerITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomerITUtils.java index 3242f9ce03..0732b2983e 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomerITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/CustomerITUtils.java @@ -1,5 +1,17 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.createCustomerGroup; +import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.deleteCustomerGroups; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createTypeIfNotAlreadyExisting; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.sync.integration.commons.utils.StoreITUtils.createStore; +import static com.commercetools.sync.integration.commons.utils.StoreITUtils.deleteStores; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; + import com.neovisionaries.i18n.CountryCode; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customergroups.CustomerGroup; @@ -16,67 +28,57 @@ import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.ResourceTypeIdsSetBuilder; import io.sphere.sdk.types.Type; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; import java.time.LocalDate; import java.util.Locale; - -import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.createCustomerGroup; -import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.deleteCustomerGroups; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createTypeIfNotAlreadyExisting; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.sync.integration.commons.utils.StoreITUtils.createStore; -import static com.commercetools.sync.integration.commons.utils.StoreITUtils.deleteStores; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; public final class CustomerITUtils { - /** - * Deletes all customers, types, stores and customer groups from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the customer sync test data. - */ - public static void deleteCustomerSyncTestData(@Nonnull final SphereClient ctpClient) { - deleteCustomers(ctpClient); - deleteTypes(ctpClient); - deleteStores(ctpClient); - deleteCustomerGroups(ctpClient); - } - - /** - * Deletes all customers from CTP project, represented by provided {@code ctpClient}. - * - * @param ctpClient represents the CTP project the customers will be deleted from. - */ - public static void deleteCustomers(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, CustomerQuery.of(), CustomerDeleteCommand::of); - } - - public static ImmutablePair createSampleCustomerJohnDoe( - @Nonnull final SphereClient ctpClient) { - - final Store storeBerlin = createStore(ctpClient, "store-berlin"); - final Store storeHamburg = createStore(ctpClient, "store-hamburg"); - final Store storeMunich = createStore(ctpClient, "store-munich"); - - final Type customTypeGoldMember = createCustomerCustomType("customer-type-gold", Locale.ENGLISH, - "gold customers", ctpClient); - - final CustomerGroup customerGroupGoldMembers = createCustomerGroup(ctpClient, "gold members", "gold"); - - final CustomerDraft customerDraftJohnDoe = CustomerDraftBuilder - .of("john@example.com", "12345") + /** + * Deletes all customers, types, stores and customer groups from the CTP project defined by the + * {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the customer sync test data. + */ + public static void deleteCustomerSyncTestData(@Nonnull final SphereClient ctpClient) { + deleteCustomers(ctpClient); + deleteTypes(ctpClient); + deleteStores(ctpClient); + deleteCustomerGroups(ctpClient); + } + + /** + * Deletes all customers from CTP project, represented by provided {@code ctpClient}. + * + * @param ctpClient represents the CTP project the customers will be deleted from. + */ + public static void deleteCustomers(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, CustomerQuery.of(), CustomerDeleteCommand::of); + } + + public static ImmutablePair createSampleCustomerJohnDoe( + @Nonnull final SphereClient ctpClient) { + + final Store storeBerlin = createStore(ctpClient, "store-berlin"); + final Store storeHamburg = createStore(ctpClient, "store-hamburg"); + final Store storeMunich = createStore(ctpClient, "store-munich"); + + final Type customTypeGoldMember = + createCustomerCustomType("customer-type-gold", Locale.ENGLISH, "gold customers", ctpClient); + + final CustomerGroup customerGroupGoldMembers = + createCustomerGroup(ctpClient, "gold members", "gold"); + + final CustomerDraft customerDraftJohnDoe = + CustomerDraftBuilder.of("john@example.com", "12345") .customerNumber("gold-1") .key("customer-key-john-doe") - .stores(asList( - ResourceIdentifier.ofKey(storeBerlin.getKey()), - ResourceIdentifier.ofKey(storeHamburg.getKey()), - ResourceIdentifier.ofKey(storeMunich.getKey()))) + .stores( + asList( + ResourceIdentifier.ofKey(storeBerlin.getKey()), + ResourceIdentifier.ofKey(storeHamburg.getKey()), + ResourceIdentifier.ofKey(storeMunich.getKey()))) .firstName("John") .lastName("Doe") .middleName("Jr") @@ -87,10 +89,11 @@ public static ImmutablePair createSampleCustomerJohnDoe .vatId("DE999999999") .emailVerified(true) .customerGroup(ResourceIdentifier.ofKey(customerGroupGoldMembers.getKey())) - .addresses(asList( - Address.of(CountryCode.DE).withCity("berlin").withKey("address1"), - Address.of(CountryCode.DE).withCity("hamburg").withKey("address2"), - Address.of(CountryCode.DE).withCity("munich").withKey("address3"))) + .addresses( + asList( + Address.of(CountryCode.DE).withCity("berlin").withKey("address1"), + Address.of(CountryCode.DE).withCity("hamburg").withKey("address2"), + Address.of(CountryCode.DE).withCity("munich").withKey("address3"))) .defaultBillingAddress(0) .billingAddresses(asList(0, 1)) .defaultShippingAddress(2) @@ -99,14 +102,14 @@ public static ImmutablePair createSampleCustomerJohnDoe .locale(Locale.ENGLISH) .build(); - final Customer customer = createCustomer(ctpClient, customerDraftJohnDoe); - return ImmutablePair.of(customer, customerDraftJohnDoe); - } + final Customer customer = createCustomer(ctpClient, customerDraftJohnDoe); + return ImmutablePair.of(customer, customerDraftJohnDoe); + } - @Nonnull - public static Customer createSampleCustomerJaneDoe(@Nonnull final SphereClient ctpClient) { - final CustomerDraft customerDraftJaneDoe = CustomerDraftBuilder - .of("jane@example.com", "12345") + @Nonnull + public static Customer createSampleCustomerJaneDoe(@Nonnull final SphereClient ctpClient) { + final CustomerDraft customerDraftJaneDoe = + CustomerDraftBuilder.of("jane@example.com", "12345") .customerNumber("random-1") .key("customer-key-jane-doe") .firstName("Jane") @@ -118,9 +121,10 @@ public static Customer createSampleCustomerJaneDoe(@Nonnull final SphereClient c .companyName("Acme Corporation") .vatId("FR000000000") .emailVerified(false) - .addresses(asList( - Address.of(CountryCode.DE).withCity("cologne").withKey("address1"), - Address.of(CountryCode.DE).withCity("berlin").withKey("address2"))) + .addresses( + asList( + Address.of(CountryCode.DE).withCity("cologne").withKey("address1"), + Address.of(CountryCode.DE).withCity("berlin").withKey("address2"))) .defaultBillingAddress(0) .billingAddresses(singletonList(0)) .defaultShippingAddress(1) @@ -128,48 +132,43 @@ public static Customer createSampleCustomerJaneDoe(@Nonnull final SphereClient c .locale(Locale.ENGLISH) .build(); - return createCustomer(ctpClient, customerDraftJaneDoe); - } - - /** - * Creates a {@link Customer} in the CTP project defined by the {@code ctpClient} in a blocking fashion. - * - * @param ctpClient defines the CTP project to create the CustomerGroup in. - * @param customerDraft the draft of the customer to create. - * @return the created customer. - */ - public static Customer createCustomer( - @Nonnull final SphereClient ctpClient, - @Nonnull final CustomerDraft customerDraft) { - - final CustomerSignInResult customerSignInResult = executeBlocking( - ctpClient.execute(CustomerCreateCommand.of(customerDraft))); - return customerSignInResult.getCustomer(); - } - - /** - * This method blocks to create a customer custom type on the CTP project defined by the supplied - * {@code ctpClient}, with the supplied data. - * - * @param typeKey the type key - * @param locale the locale to be used for specifying the type name and field definitions names. - * @param name the name of the custom type. - * @param ctpClient defines the CTP project to create the type on. - */ - public static Type createCustomerCustomType( - @Nonnull final String typeKey, - @Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final SphereClient ctpClient) { - - return createTypeIfNotAlreadyExisting( - typeKey, - locale, - name, - ResourceTypeIdsSetBuilder.of().addCustomers(), - ctpClient); - } - - private CustomerITUtils() { - } + return createCustomer(ctpClient, customerDraftJaneDoe); + } + + /** + * Creates a {@link Customer} in the CTP project defined by the {@code ctpClient} in a blocking + * fashion. + * + * @param ctpClient defines the CTP project to create the CustomerGroup in. + * @param customerDraft the draft of the customer to create. + * @return the created customer. + */ + public static Customer createCustomer( + @Nonnull final SphereClient ctpClient, @Nonnull final CustomerDraft customerDraft) { + + final CustomerSignInResult customerSignInResult = + executeBlocking(ctpClient.execute(CustomerCreateCommand.of(customerDraft))); + return customerSignInResult.getCustomer(); + } + + /** + * This method blocks to create a customer custom type on the CTP project defined by the supplied + * {@code ctpClient}, with the supplied data. + * + * @param typeKey the type key + * @param locale the locale to be used for specifying the type name and field definitions names. + * @param name the name of the custom type. + * @param ctpClient defines the CTP project to create the type on. + */ + public static Type createCustomerCustomType( + @Nonnull final String typeKey, + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final SphereClient ctpClient) { + + return createTypeIfNotAlreadyExisting( + typeKey, locale, name, ResourceTypeIdsSetBuilder.of().addCustomers(), ctpClient); + } + + private CustomerITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ITUtils.java index 6b87750353..9cb2565651 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ITUtils.java @@ -1,5 +1,15 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.Optional.ofNullable; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.utils.CtpQueryUtils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; @@ -31,9 +41,6 @@ import io.sphere.sdk.types.commands.TypeDeleteCommand; import io.sphere.sdk.types.queries.TypeQuery; import io.sphere.sdk.types.queries.TypeQueryBuilder; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -44,390 +51,389 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.IntStream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.Optional.ofNullable; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * This class is meant only meant for internal use of the library's integration tests. - */ +/** This class is meant only meant for internal use of the library's integration tests. */ public final class ITUtils { - public static final String LOCALISED_STRING_CUSTOM_FIELD_NAME = "backgroundColor"; - public static final String BOOLEAN_CUSTOM_FIELD_NAME = "invisibleInShop"; - public static final String EMPTY_SET_CUSTOM_FIELD_NAME = "emptySet"; - public static final String NON_EMPTY_SEY_CUSTOM_FIELD_NAME = "nonEmptySet"; - public static final String NULL_NODE_SET_CUSTOM_FIELD_NAME = "nullNode"; - public static final String NULL_SET_CUSTOM_FIELD_NAME = "null"; - - - /** - * This method blocks to create an asset custom Type on the CTP project defined by the supplied - * {@code ctpClient}, with the supplied data. - * @param typeKey the type key - * @param locale the locale to be used for specifying the type name and field definitions names. - * @param name the name of the custom type. - * @param ctpClient defines the CTP project to create the type on. - */ - public static Type createAssetsCustomType( - @Nonnull final String typeKey, - @Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final SphereClient ctpClient) { - - return createTypeIfNotAlreadyExisting( - typeKey, locale, name, ResourceTypeIdsSetBuilder.of().addAssets(), ctpClient); - } - - /** - * This method blocks to create a custom Type on the CTP project defined by the supplied - * {@code ctpClient}, with the supplied data. - * - * @param typeKey the type key - * @param locale the locale to be used for specifying the type name and field definitions names. - * @param name the name of the custom type. - * @param resourceTypeIdsSetBuilder builds the resource type ids for the created type. - * @param ctpClient defines the CTP project to create the type on. - */ - public static Type createTypeIfNotAlreadyExisting( - @Nonnull final String typeKey, - @Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final ResourceTypeIdsSetBuilder resourceTypeIdsSetBuilder, - @Nonnull final SphereClient ctpClient) { - - return typeExists(typeKey, ctpClient) - .thenCompose(type -> + public static final String LOCALISED_STRING_CUSTOM_FIELD_NAME = "backgroundColor"; + public static final String BOOLEAN_CUSTOM_FIELD_NAME = "invisibleInShop"; + public static final String EMPTY_SET_CUSTOM_FIELD_NAME = "emptySet"; + public static final String NON_EMPTY_SEY_CUSTOM_FIELD_NAME = "nonEmptySet"; + public static final String NULL_NODE_SET_CUSTOM_FIELD_NAME = "nullNode"; + public static final String NULL_SET_CUSTOM_FIELD_NAME = "null"; + + /** + * This method blocks to create an asset custom Type on the CTP project defined by the supplied + * {@code ctpClient}, with the supplied data. + * + * @param typeKey the type key + * @param locale the locale to be used for specifying the type name and field definitions names. + * @param name the name of the custom type. + * @param ctpClient defines the CTP project to create the type on. + */ + public static Type createAssetsCustomType( + @Nonnull final String typeKey, + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final SphereClient ctpClient) { + + return createTypeIfNotAlreadyExisting( + typeKey, locale, name, ResourceTypeIdsSetBuilder.of().addAssets(), ctpClient); + } + + /** + * This method blocks to create a custom Type on the CTP project defined by the supplied {@code + * ctpClient}, with the supplied data. + * + * @param typeKey the type key + * @param locale the locale to be used for specifying the type name and field definitions names. + * @param name the name of the custom type. + * @param resourceTypeIdsSetBuilder builds the resource type ids for the created type. + * @param ctpClient defines the CTP project to create the type on. + */ + public static Type createTypeIfNotAlreadyExisting( + @Nonnull final String typeKey, + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final ResourceTypeIdsSetBuilder resourceTypeIdsSetBuilder, + @Nonnull final SphereClient ctpClient) { + + return typeExists(typeKey, ctpClient) + .thenCompose( + type -> type.map(CompletableFuture::completedFuture) - .orElseGet(() -> { - final TypeDraft typeDraft = TypeDraftBuilder - .of(typeKey, LocalizedString.of(locale, name), resourceTypeIdsSetBuilder) - .fieldDefinitions(createCustomTypeFieldDefinitions(locale)) - .build(); - return ctpClient.execute(TypeCreateCommand.of(typeDraft)).toCompletableFuture(); - })) - .toCompletableFuture().join(); - } - - private static CompletionStage> typeExists(@Nonnull final String typeKey, - @Nonnull final SphereClient ctpClient) { - return ctpClient - .execute(TypeQueryBuilder.of().predicates(QueryPredicate.of(format("key=\"%s\"", typeKey))).build()) - .thenApply(PagedResult::head); - } - - - /** - * Builds a list of two field definitions; one for a {@link LocalizedStringFieldType} and one for a - * {@link BooleanFieldType}. The JSON of the created field definition list looks as follows: - * - *

"fieldDefinitions": [ - * { - * "name": "backgroundColor", - * "label": { - * "en": "backgroundColor" - * }, - * "required": false, - * "type": { - * "name": "LocalizedString" - * }, - * "inputHint": "SingleLine" - * }, - * { - * "name": "invisibleInShop", - * "label": { - * "en": "invisibleInShop" - * }, - * "required": false, - * "type": { - * "name": "Boolean" - * }, - * "inputHint": "SingleLine" - * } - * ] - * - * @param locale defines the locale for which the field definition names are going to be bound to. - * @return the list of field definitions. - */ - private static List createCustomTypeFieldDefinitions(@Nonnull final Locale locale) { - return asList( - FieldDefinition - .of(LocalizedStringFieldType.of(), LOCALISED_STRING_CUSTOM_FIELD_NAME, - LocalizedString.of(locale, LOCALISED_STRING_CUSTOM_FIELD_NAME), false), - FieldDefinition - .of(BooleanFieldType.of(), BOOLEAN_CUSTOM_FIELD_NAME, - LocalizedString.of(locale, BOOLEAN_CUSTOM_FIELD_NAME), false), - FieldDefinition - .of(SetFieldType.of(StringFieldType.of()), EMPTY_SET_CUSTOM_FIELD_NAME, - LocalizedString.of(locale, EMPTY_SET_CUSTOM_FIELD_NAME), false), - FieldDefinition - .of(SetFieldType.of(StringFieldType.of()), NON_EMPTY_SEY_CUSTOM_FIELD_NAME, - LocalizedString.of(locale, NON_EMPTY_SEY_CUSTOM_FIELD_NAME), false), - FieldDefinition - .of(SetFieldType.of(StringFieldType.of()), NULL_NODE_SET_CUSTOM_FIELD_NAME, - LocalizedString.of(locale, NULL_NODE_SET_CUSTOM_FIELD_NAME), false), - FieldDefinition - .of(SetFieldType.of(StringFieldType.of()), NULL_SET_CUSTOM_FIELD_NAME, - LocalizedString.of(locale, NULL_SET_CUSTOM_FIELD_NAME), false)); - - } - - /** - * Builds a {@link Map} for the custom fields to their {@link JsonNode} values that looks as follows in JSON - * format: - * - *

"fields": {"invisibleInShop": false, "backgroundColor": { "en": "red", "de": "rot"}} - * - * @return a Map of the custom fields to their JSON values with dummy data. - */ - public static Map createCustomFieldsJsonMap() { - final Map customFieldsJsons = new HashMap<>(); - customFieldsJsons.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(false)); - customFieldsJsons.put(LOCALISED_STRING_CUSTOM_FIELD_NAME, - JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); - return customFieldsJsons; - } - - /** - * Deletes all Types from CTP projects defined by the {@code sphereClient} - * - * @param ctpClient defines the CTP project to delete the Types from. - */ - public static void deleteTypes(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, TypeQuery.of(), TypeDeleteCommand::of); - } - - /** - * Deletes all Types from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and {@code CTP_TARGET_CLIENT}. - */ - public static void deleteTypesFromTargetAndSource() { - deleteTypes(CTP_TARGET_CLIENT); - deleteTypes(CTP_SOURCE_CLIENT); - } - - /** - * Applies the {@code resourceToRequestMapper} function on each page, resulting from the {@code query} executed by - * the {@code ctpClient}, to map each resource to a {@link SphereRequest} and then executes these requests in - * parallel within each page. - * - * @param ctpClient defines the CTP project to apply the query on. - * @param query query that should be made on the CTP project. - * @param resourceToRequestMapper defines a mapper function that should be applied on each resource, in the fetched - * page from the query on the specified CTP project, to map it to a - * {@link SphereRequest}. - */ - public static > void queryAndExecute( - @Nonnull final SphereClient ctpClient, - @Nonnull final QueryDsl query, - @Nonnull final Function> resourceToRequestMapper) { - - queryAndCompose(ctpClient, query, resource -> ctpClient.execute(resourceToRequestMapper.apply(resource))); - } - - /** - * Applies the {@code resourceToStageMapper} function on each page, resulting from the {@code query} executed by - * the {@code ctpClient}, to map each resource to a {@link CompletionStage} and then executes these stages in - * parallel within each page. - * - * - * @param ctpClient defines the CTP project to apply the query on. - * @param query query that should be made on the CTP project. - * @param resourceToStageMapper defines a mapper function that should be applied on each resource, in the fetched - * page from the query on the specified CTP project, to map it to a - * {@link CompletionStage} which will be executed (in a blocking fashion) after - * every page fetch. - * - */ - public static , S> void queryAndCompose( - @Nonnull final SphereClient ctpClient, - @Nonnull final QueryDsl query, - @Nonnull final Function> resourceToStageMapper) { - - // TODO: GITHUB ISSUE #248 - final Consumer> pageConsumer = - pageElements -> CompletableFuture.allOf(pageElements.stream() - .map(resourceToStageMapper) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - CtpQueryUtils.queryAll(ctpClient, query, pageConsumer) - .toCompletableFuture() - .join(); - } - - /** - * Creates an {@link AssetDraft} with the with the given key and name. - * - * @param assetKey asset draft key. - * @param assetName asset draft name. - * @return an {@link AssetDraft} with the with the given key and name. - */ - public static AssetDraft createAssetDraft(@Nonnull final String assetKey, - @Nonnull final LocalizedString assetName) { - return createAssetDraftBuilder(assetKey, assetName).build(); - } - - /** - * Creates an {@link AssetDraft} with the with the given key and name. The asset draft created will have custom - * field with the type id supplied ({@code assetCustomTypeId} and the fields built from the method - * {@link ITUtils#createCustomFieldsJsonMap()}. - * - * @param assetKey asset draft key. - * @param assetName asset draft name. - * @param assetCustomTypeId the asset custom type id. - * @return an {@link AssetDraft} with the with the given key and name. The asset draft created will have custom - * field with the type id supplied ({@code assetCustomTypeId} and the fields built from the method - * {@link ITUtils#createCustomFieldsJsonMap()}. - */ - public static AssetDraft createAssetDraft(@Nonnull final String assetKey, - @Nonnull final LocalizedString assetName, - @Nonnull final String assetCustomTypeId) { - return createAssetDraft(assetKey, assetName, assetCustomTypeId, createCustomFieldsJsonMap()); - } - - /** - * Creates an {@link AssetDraft} with the with the given key and name. The asset draft created will have custom - * field with the type id supplied ({@code assetCustomTypeId} and the custom fields will be defined by the - * {@code customFieldsJsonMap} supplied. - * - * @param assetKey asset draft key. - * @param assetName asset draft name. - * @param assetCustomTypeId the asset custom type id. - * @param customFieldsJsonMap the custom fields of the asset custom type. - * @return an {@link AssetDraft} with the with the given key and name. The asset draft created will have custom - * field with the type id supplied ({@code assetCustomTypeId} and the custom fields will be defined by the - * {@code customFieldsJsonMap} supplied. - */ - public static AssetDraft createAssetDraft(@Nonnull final String assetKey, - @Nonnull final LocalizedString assetName, - @Nonnull final String assetCustomTypeId, - @Nonnull final Map customFieldsJsonMap) { - return createAssetDraftBuilder(assetKey, assetName) - .custom(CustomFieldsDraft.ofTypeIdAndJson(assetCustomTypeId, customFieldsJsonMap)) - .build(); - } - - /** - * Creates an {@link AssetDraft} with the with the given key and name. The asset draft created will have custom - * field with the type key supplied ({@code assetCustomTypeKey} and the fields built from the method - * {@link ITUtils#createCustomFieldsJsonMap()}. - * - * @param assetKey asset draft key. - * @param assetName asset draft name. - * @param assetCustomTypeKey the asset custom type key. - * @return an {@link AssetDraft} with the with the given key and name. The asset draft created will have custom - * field with the type key supplied ({@code assetCustomTypeKey} and the fields built from the method - * {@link ITUtils#createCustomFieldsJsonMap()}. - */ - public static AssetDraft createAssetDraftWithKey(@Nonnull final String assetKey, - @Nonnull final LocalizedString assetName, - @Nonnull final String assetCustomTypeKey) { - return createAssetDraftWithKey(assetKey, assetName, assetCustomTypeKey, createCustomFieldsJsonMap()); - } - - /** - * Creates an {@link AssetDraft} with the with the given key and name. The asset draft created will have custom - * field with the type key supplied ({@code assetCustomTypeKey} and the custom fields will be defined by the - * {@code customFieldsJsonMap} supplied. - * - * @param assetKey asset draft key. - * @param assetName asset draft name. - * @param assetCustomTypeKey the asset custom type key. - * @param customFieldsJsonMap the custom fields of the asset custom type. - * @return an {@link AssetDraft} with the with the given key and name. The asset draft created will have custom - * field with the type id supplied ({@code assetCustomTypeId} and the custom fields will be defined by the - * {@code customFieldsJsonMap} supplied. - */ - public static AssetDraft createAssetDraftWithKey(@Nonnull final String assetKey, - @Nonnull final LocalizedString assetName, - @Nonnull final String assetCustomTypeKey, - @Nonnull final Map customFieldsJsonMap) { - return createAssetDraftBuilder(assetKey, assetName) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(assetCustomTypeKey, customFieldsJsonMap)) - .build(); - } - - /** - * Creates an {@link AssetDraftBuilder} with the with the given key and name. The builder created will contain one - * tag with the same value as the key and will contain one {@link io.sphere.sdk.models.AssetSource} - * with the uri {@code sourceUri}. - * - * @param assetKey asset draft key. - * @param assetName asset draft name. - * @return an {@link AssetDraftBuilder} with the with the given key and name. The builder created will contain one - * tag with the same value as the key and will contain one {@link io.sphere.sdk.models.AssetSource} with the - * uri {@code sourceUri}. - */ - private static AssetDraftBuilder createAssetDraftBuilder(@Nonnull final String assetKey, - @Nonnull final LocalizedString assetName) { - return AssetDraftBuilder.of(emptyList(), assetName) - .key(assetKey) - .tags(singleton(assetKey)) - .sources(singletonList(AssetSourceBuilder.ofUri("sourceUri").build())); - } - - /** - * Creates a {@link ProductVariantDraft} draft key and sku of the value supplied {@code variantKeyAndSku} and with - * the supplied {@code assetDrafts} and {@code priceDrafts} - * - * @param variantKeyAndSku the value of the key and sku of the created draft. - * @param assetDrafts the assets to assign to the created draft. - * @param priceDrafts the prices to assign to the created draft. - * @return a {@link ProductVariantDraft} draft key and sku of the value supplied {@code variantKeyAndSku} and with - * the supplied {@code assetDrafts} and {@code priceDrafts}. - */ - public static ProductVariantDraft createVariantDraft(@Nonnull final String variantKeyAndSku, - @Nullable final List assetDrafts, - @Nullable final List priceDrafts) { - - return ProductVariantDraftBuilder.of() - .key(variantKeyAndSku) - .sku(variantKeyAndSku) - .assets(assetDrafts) - .prices(priceDrafts) - .build(); - } - - /** - * Asserts that a list of {@link Asset} and a list of {@link AssetDraft} have the same ordering of assets (assets - * are matched by key). It asserts that the matching assets have the same name, description, custom fields, tags, - * and asset sources. - * - *

TODO: This should be refactored into Asset asserts helpers. GITHUB ISSUE#261 - * - * @param assets the list of assets to compare to the list of asset drafts. - * @param assetDrafts the list of asset drafts to compare to the list of assets. - */ - public static void assertAssetsAreEqual(@Nonnull final List assets, - @Nonnull final List assetDrafts) { - IntStream.range(0, assetDrafts.size()) - .forEach(index -> { - final Asset createdAsset = assets.get(index); - final AssetDraft assetDraft = assetDrafts.get(index); - - assertThat(createdAsset.getName()).isEqualTo(assetDraft.getName()); - assertThat(createdAsset.getDescription()).isEqualTo(assetDraft.getDescription()); - assertThat(createdAsset.getKey()).isEqualTo(assetDraft.getKey()); - - ofNullable(assetDraft.getCustom()) - .ifPresent(customFields -> { - assertThat(createdAsset.getCustom()).isNotNull(); - assertThat(createdAsset.getCustom().getFieldsJsonMap()) - .isEqualTo(assetDraft.getCustom().getFields()); - }); - - assertThat(createdAsset.getTags()).isEqualTo(assetDraft.getTags()); - assertThat(createdAsset.getSources()).isEqualTo(assetDraft.getSources()); - }); - } - - private ITUtils() { - } + .orElseGet( + () -> { + final TypeDraft typeDraft = + TypeDraftBuilder.of( + typeKey, + LocalizedString.of(locale, name), + resourceTypeIdsSetBuilder) + .fieldDefinitions(createCustomTypeFieldDefinitions(locale)) + .build(); + return ctpClient + .execute(TypeCreateCommand.of(typeDraft)) + .toCompletableFuture(); + })) + .toCompletableFuture() + .join(); + } + + private static CompletionStage> typeExists( + @Nonnull final String typeKey, @Nonnull final SphereClient ctpClient) { + return ctpClient + .execute( + TypeQueryBuilder.of() + .predicates(QueryPredicate.of(format("key=\"%s\"", typeKey))) + .build()) + .thenApply(PagedResult::head); + } + + /** + * Builds a list of two field definitions; one for a {@link LocalizedStringFieldType} and one for + * a {@link BooleanFieldType}. The JSON of the created field definition list looks as follows: + * + *

"fieldDefinitions": [ { "name": "backgroundColor", "label": { "en": "backgroundColor" }, + * "required": false, "type": { "name": "LocalizedString" }, "inputHint": "SingleLine" }, { + * "name": "invisibleInShop", "label": { "en": "invisibleInShop" }, "required": false, "type": { + * "name": "Boolean" }, "inputHint": "SingleLine" } ] + * + * @param locale defines the locale for which the field definition names are going to be bound to. + * @return the list of field definitions. + */ + private static List createCustomTypeFieldDefinitions( + @Nonnull final Locale locale) { + return asList( + FieldDefinition.of( + LocalizedStringFieldType.of(), + LOCALISED_STRING_CUSTOM_FIELD_NAME, + LocalizedString.of(locale, LOCALISED_STRING_CUSTOM_FIELD_NAME), + false), + FieldDefinition.of( + BooleanFieldType.of(), + BOOLEAN_CUSTOM_FIELD_NAME, + LocalizedString.of(locale, BOOLEAN_CUSTOM_FIELD_NAME), + false), + FieldDefinition.of( + SetFieldType.of(StringFieldType.of()), + EMPTY_SET_CUSTOM_FIELD_NAME, + LocalizedString.of(locale, EMPTY_SET_CUSTOM_FIELD_NAME), + false), + FieldDefinition.of( + SetFieldType.of(StringFieldType.of()), + NON_EMPTY_SEY_CUSTOM_FIELD_NAME, + LocalizedString.of(locale, NON_EMPTY_SEY_CUSTOM_FIELD_NAME), + false), + FieldDefinition.of( + SetFieldType.of(StringFieldType.of()), + NULL_NODE_SET_CUSTOM_FIELD_NAME, + LocalizedString.of(locale, NULL_NODE_SET_CUSTOM_FIELD_NAME), + false), + FieldDefinition.of( + SetFieldType.of(StringFieldType.of()), + NULL_SET_CUSTOM_FIELD_NAME, + LocalizedString.of(locale, NULL_SET_CUSTOM_FIELD_NAME), + false)); + } + + /** + * Builds a {@link Map} for the custom fields to their {@link JsonNode} values that looks as + * follows in JSON format: + * + *

"fields": {"invisibleInShop": false, "backgroundColor": { "en": "red", "de": "rot"}} + * + * @return a Map of the custom fields to their JSON values with dummy data. + */ + public static Map createCustomFieldsJsonMap() { + final Map customFieldsJsons = new HashMap<>(); + customFieldsJsons.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(false)); + customFieldsJsons.put( + LOCALISED_STRING_CUSTOM_FIELD_NAME, + JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); + return customFieldsJsons; + } + + /** + * Deletes all Types from CTP projects defined by the {@code sphereClient} + * + * @param ctpClient defines the CTP project to delete the Types from. + */ + public static void deleteTypes(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, TypeQuery.of(), TypeDeleteCommand::of); + } + + /** + * Deletes all Types from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and {@code + * CTP_TARGET_CLIENT}. + */ + public static void deleteTypesFromTargetAndSource() { + deleteTypes(CTP_TARGET_CLIENT); + deleteTypes(CTP_SOURCE_CLIENT); + } + + /** + * Applies the {@code resourceToRequestMapper} function on each page, resulting from the {@code + * query} executed by the {@code ctpClient}, to map each resource to a {@link SphereRequest} and + * then executes these requests in parallel within each page. + * + * @param ctpClient defines the CTP project to apply the query on. + * @param query query that should be made on the CTP project. + * @param resourceToRequestMapper defines a mapper function that should be applied on each + * resource, in the fetched page from the query on the specified CTP project, to map it to a + * {@link SphereRequest}. + */ + public static > void queryAndExecute( + @Nonnull final SphereClient ctpClient, + @Nonnull final QueryDsl query, + @Nonnull final Function> resourceToRequestMapper) { + + queryAndCompose( + ctpClient, query, resource -> ctpClient.execute(resourceToRequestMapper.apply(resource))); + } + + /** + * Applies the {@code resourceToStageMapper} function on each page, resulting from the {@code + * query} executed by the {@code ctpClient}, to map each resource to a {@link CompletionStage} and + * then executes these stages in parallel within each page. + * + * @param ctpClient defines the CTP project to apply the query on. + * @param query query that should be made on the CTP project. + * @param resourceToStageMapper defines a mapper function that should be applied on each resource, + * in the fetched page from the query on the specified CTP project, to map it to a {@link + * CompletionStage} which will be executed (in a blocking fashion) after every page fetch. + */ + public static , S> void queryAndCompose( + @Nonnull final SphereClient ctpClient, + @Nonnull final QueryDsl query, + @Nonnull final Function> resourceToStageMapper) { + + // TODO: GITHUB ISSUE #248 + final Consumer> pageConsumer = + pageElements -> + CompletableFuture.allOf( + pageElements.stream() + .map(resourceToStageMapper) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + + CtpQueryUtils.queryAll(ctpClient, query, pageConsumer).toCompletableFuture().join(); + } + + /** + * Creates an {@link AssetDraft} with the with the given key and name. + * + * @param assetKey asset draft key. + * @param assetName asset draft name. + * @return an {@link AssetDraft} with the with the given key and name. + */ + public static AssetDraft createAssetDraft( + @Nonnull final String assetKey, @Nonnull final LocalizedString assetName) { + return createAssetDraftBuilder(assetKey, assetName).build(); + } + + /** + * Creates an {@link AssetDraft} with the with the given key and name. The asset draft created + * will have custom field with the type id supplied ({@code assetCustomTypeId} and the fields + * built from the method {@link ITUtils#createCustomFieldsJsonMap()}. + * + * @param assetKey asset draft key. + * @param assetName asset draft name. + * @param assetCustomTypeId the asset custom type id. + * @return an {@link AssetDraft} with the with the given key and name. The asset draft created + * will have custom field with the type id supplied ({@code assetCustomTypeId} and the fields + * built from the method {@link ITUtils#createCustomFieldsJsonMap()}. + */ + public static AssetDraft createAssetDraft( + @Nonnull final String assetKey, + @Nonnull final LocalizedString assetName, + @Nonnull final String assetCustomTypeId) { + return createAssetDraft(assetKey, assetName, assetCustomTypeId, createCustomFieldsJsonMap()); + } + + /** + * Creates an {@link AssetDraft} with the with the given key and name. The asset draft created + * will have custom field with the type id supplied ({@code assetCustomTypeId} and the custom + * fields will be defined by the {@code customFieldsJsonMap} supplied. + * + * @param assetKey asset draft key. + * @param assetName asset draft name. + * @param assetCustomTypeId the asset custom type id. + * @param customFieldsJsonMap the custom fields of the asset custom type. + * @return an {@link AssetDraft} with the with the given key and name. The asset draft created + * will have custom field with the type id supplied ({@code assetCustomTypeId} and the custom + * fields will be defined by the {@code customFieldsJsonMap} supplied. + */ + public static AssetDraft createAssetDraft( + @Nonnull final String assetKey, + @Nonnull final LocalizedString assetName, + @Nonnull final String assetCustomTypeId, + @Nonnull final Map customFieldsJsonMap) { + return createAssetDraftBuilder(assetKey, assetName) + .custom(CustomFieldsDraft.ofTypeIdAndJson(assetCustomTypeId, customFieldsJsonMap)) + .build(); + } + + /** + * Creates an {@link AssetDraft} with the with the given key and name. The asset draft created + * will have custom field with the type key supplied ({@code assetCustomTypeKey} and the fields + * built from the method {@link ITUtils#createCustomFieldsJsonMap()}. + * + * @param assetKey asset draft key. + * @param assetName asset draft name. + * @param assetCustomTypeKey the asset custom type key. + * @return an {@link AssetDraft} with the with the given key and name. The asset draft created + * will have custom field with the type key supplied ({@code assetCustomTypeKey} and the + * fields built from the method {@link ITUtils#createCustomFieldsJsonMap()}. + */ + public static AssetDraft createAssetDraftWithKey( + @Nonnull final String assetKey, + @Nonnull final LocalizedString assetName, + @Nonnull final String assetCustomTypeKey) { + return createAssetDraftWithKey( + assetKey, assetName, assetCustomTypeKey, createCustomFieldsJsonMap()); + } + + /** + * Creates an {@link AssetDraft} with the with the given key and name. The asset draft created + * will have custom field with the type key supplied ({@code assetCustomTypeKey} and the custom + * fields will be defined by the {@code customFieldsJsonMap} supplied. + * + * @param assetKey asset draft key. + * @param assetName asset draft name. + * @param assetCustomTypeKey the asset custom type key. + * @param customFieldsJsonMap the custom fields of the asset custom type. + * @return an {@link AssetDraft} with the with the given key and name. The asset draft created + * will have custom field with the type id supplied ({@code assetCustomTypeId} and the custom + * fields will be defined by the {@code customFieldsJsonMap} supplied. + */ + public static AssetDraft createAssetDraftWithKey( + @Nonnull final String assetKey, + @Nonnull final LocalizedString assetName, + @Nonnull final String assetCustomTypeKey, + @Nonnull final Map customFieldsJsonMap) { + return createAssetDraftBuilder(assetKey, assetName) + .custom(CustomFieldsDraft.ofTypeKeyAndJson(assetCustomTypeKey, customFieldsJsonMap)) + .build(); + } + + /** + * Creates an {@link AssetDraftBuilder} with the with the given key and name. The builder created + * will contain one tag with the same value as the key and will contain one {@link + * io.sphere.sdk.models.AssetSource} with the uri {@code sourceUri}. + * + * @param assetKey asset draft key. + * @param assetName asset draft name. + * @return an {@link AssetDraftBuilder} with the with the given key and name. The builder created + * will contain one tag with the same value as the key and will contain one {@link + * io.sphere.sdk.models.AssetSource} with the uri {@code sourceUri}. + */ + private static AssetDraftBuilder createAssetDraftBuilder( + @Nonnull final String assetKey, @Nonnull final LocalizedString assetName) { + return AssetDraftBuilder.of(emptyList(), assetName) + .key(assetKey) + .tags(singleton(assetKey)) + .sources(singletonList(AssetSourceBuilder.ofUri("sourceUri").build())); + } + + /** + * Creates a {@link ProductVariantDraft} draft key and sku of the value supplied {@code + * variantKeyAndSku} and with the supplied {@code assetDrafts} and {@code priceDrafts} + * + * @param variantKeyAndSku the value of the key and sku of the created draft. + * @param assetDrafts the assets to assign to the created draft. + * @param priceDrafts the prices to assign to the created draft. + * @return a {@link ProductVariantDraft} draft key and sku of the value supplied {@code + * variantKeyAndSku} and with the supplied {@code assetDrafts} and {@code priceDrafts}. + */ + public static ProductVariantDraft createVariantDraft( + @Nonnull final String variantKeyAndSku, + @Nullable final List assetDrafts, + @Nullable final List priceDrafts) { + + return ProductVariantDraftBuilder.of() + .key(variantKeyAndSku) + .sku(variantKeyAndSku) + .assets(assetDrafts) + .prices(priceDrafts) + .build(); + } + + /** + * Asserts that a list of {@link Asset} and a list of {@link AssetDraft} have the same ordering of + * assets (assets are matched by key). It asserts that the matching assets have the same name, + * description, custom fields, tags, and asset sources. + * + *

TODO: This should be refactored into Asset asserts helpers. GITHUB ISSUE#261 + * + * @param assets the list of assets to compare to the list of asset drafts. + * @param assetDrafts the list of asset drafts to compare to the list of assets. + */ + public static void assertAssetsAreEqual( + @Nonnull final List assets, @Nonnull final List assetDrafts) { + IntStream.range(0, assetDrafts.size()) + .forEach( + index -> { + final Asset createdAsset = assets.get(index); + final AssetDraft assetDraft = assetDrafts.get(index); + + assertThat(createdAsset.getName()).isEqualTo(assetDraft.getName()); + assertThat(createdAsset.getDescription()).isEqualTo(assetDraft.getDescription()); + assertThat(createdAsset.getKey()).isEqualTo(assetDraft.getKey()); + + ofNullable(assetDraft.getCustom()) + .ifPresent( + customFields -> { + assertThat(createdAsset.getCustom()).isNotNull(); + assertThat(createdAsset.getCustom().getFieldsJsonMap()) + .isEqualTo(assetDraft.getCustom().getFields()); + }); + + assertThat(createdAsset.getTags()).isEqualTo(assetDraft.getTags()); + assertThat(createdAsset.getSources()).isEqualTo(assetDraft.getSources()); + }); + } + + private ITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ProductITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ProductITUtils.java index c9e96e565f..4416e7ced5 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ProductITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ProductITUtils.java @@ -1,5 +1,19 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; +import static com.commercetools.sync.integration.commons.utils.ChannelITUtils.deleteChannels; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedCustomObjects; +import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.deleteCustomerGroups; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createTypeIfNotAlreadyExisting; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndCompose; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.deleteStates; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.deleteTaxCategories; +import static java.util.Collections.singletonList; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; + import io.sphere.sdk.channels.Channel; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.client.SphereRequest; @@ -19,197 +33,199 @@ import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.ResourceTypeIdsSetBuilder; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Locale; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; -import static com.commercetools.sync.integration.commons.utils.ChannelITUtils.deleteChannels; -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedCustomObjects; -import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.deleteCustomerGroups; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createTypeIfNotAlreadyExisting; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndCompose; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.deleteStates; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.deleteTaxCategories; -import static java.util.Collections.singletonList; -import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class ProductITUtils { - /** - * Deletes all products, product types, categories and types from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the product types from. - */ - public static void deleteProductSyncTestData(@Nonnull final SphereClient ctpClient) { - deleteAllProducts(ctpClient); - deleteProductTypes(ctpClient); - deleteAllCategories(ctpClient); - deleteTypes(ctpClient); - deleteChannels(ctpClient); - deleteStates(ctpClient); - deleteTaxCategories(ctpClient); - deleteCustomerGroups(ctpClient); - deleteWaitingToBeResolvedCustomObjects(ctpClient); - } - - /** - * Unpublishes all published products, then deletes all products from the CTP project defined by the - * {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the products from. - */ - public static void deleteAllProducts(@Nonnull final SphereClient ctpClient) { - queryAndCompose(ctpClient, ProductQuery.of(), product -> safeDeleteProduct(ctpClient, product)); - } - - /** - * If the {@code product} is published, issues an unpublish request followed by a delete. Otherwise, - * issues a delete request right away. - * - * @param ctpClient defines the CTP project to delete the product from. - * @param product the product to be deleted. - * @return a {@link CompletionStage} containing the deleted product. - */ - @Nonnull - private static CompletionStage safeDeleteProduct(@Nonnull final SphereClient ctpClient, - @Nonnull final Product product) { - return product.getMasterData().isPublished() - ? unpublishAndDeleteProduct(ctpClient, product) : deleteProduct(ctpClient, product); - } - - /** - * Issues an unpublish request followed by a delete. - * - * @param ctpClient defines the CTP project to delete the product from. - * @param product the product to be unpublished and deleted. - * @return a {@link CompletionStage} containing the deleted product. - */ - @Nonnull - private static CompletionStage unpublishAndDeleteProduct(@Nonnull final SphereClient ctpClient, - @Nonnull final Product product) { - return ctpClient.execute(buildUnpublishRequest(product)) - .thenCompose(unpublishedProduct -> - deleteProduct(ctpClient, unpublishedProduct)); - } - - /** - * Note: This method assumes the product is unpublished. - * - * @param ctpClient the client defining the CTP project to delete the product from. - * @param product the product to be deleted. - * @return a {@link CompletionStage} containing the deleted product. If the product supplied was already unpublished - * the method will return a completion stage that completed exceptionally. - */ - @Nonnull - private static CompletionStage deleteProduct(@Nonnull final SphereClient ctpClient, - @Nonnull final Product product) { - return ctpClient.execute(ProductDeleteCommand.of(product)); - } - - /** - * Builds an unpublish request for the supplied product. - * - * @param product defines the product to build an un publish request for. - * @return an unpublish request for the supplied product. - */ - @Nonnull - private static SphereRequest buildUnpublishRequest(@Nonnull final Product product) { - return ProductUpdateCommand.of(product, singletonList(Unpublish.of())); - } - - /** - * Gets the supplied {@link ProductDraft} with the price reference attached on all its variants' prices. - * - *

TODO: GITHUB ISSUE#152 - * - * @param productDraft the product draft to attach the channel reference on its variants' prices. - * @param channelReference the channel reference to attach on the product draft's variants' prices. - * @return the product draft with the supplied references attached on the product draft's variants' prices. - */ - public static ProductDraft getDraftWithPriceReferences(@Nonnull final ProductDraft productDraft, - @Nullable final Reference channelReference, - @Nullable final CustomFieldsDraft customFieldsDraft) { - final List allVariants = productDraft - .getVariants().stream().map(productVariant -> { - final List priceDraftsWithChannelReferences = getPriceDraftsWithReferences(productVariant, - channelReference, customFieldsDraft); - return ProductVariantDraftBuilder.of(productVariant) - .prices(priceDraftsWithChannelReferences) - .build(); - }) + /** + * Deletes all products, product types, categories and types from the CTP project defined by the + * {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the product types from. + */ + public static void deleteProductSyncTestData(@Nonnull final SphereClient ctpClient) { + deleteAllProducts(ctpClient); + deleteProductTypes(ctpClient); + deleteAllCategories(ctpClient); + deleteTypes(ctpClient); + deleteChannels(ctpClient); + deleteStates(ctpClient); + deleteTaxCategories(ctpClient); + deleteCustomerGroups(ctpClient); + deleteWaitingToBeResolvedCustomObjects(ctpClient); + } + + /** + * Unpublishes all published products, then deletes all products from the CTP project defined by + * the {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the products from. + */ + public static void deleteAllProducts(@Nonnull final SphereClient ctpClient) { + queryAndCompose(ctpClient, ProductQuery.of(), product -> safeDeleteProduct(ctpClient, product)); + } + + /** + * If the {@code product} is published, issues an unpublish request followed by a delete. + * Otherwise, issues a delete request right away. + * + * @param ctpClient defines the CTP project to delete the product from. + * @param product the product to be deleted. + * @return a {@link CompletionStage} containing the deleted product. + */ + @Nonnull + private static CompletionStage safeDeleteProduct( + @Nonnull final SphereClient ctpClient, @Nonnull final Product product) { + return product.getMasterData().isPublished() + ? unpublishAndDeleteProduct(ctpClient, product) + : deleteProduct(ctpClient, product); + } + + /** + * Issues an unpublish request followed by a delete. + * + * @param ctpClient defines the CTP project to delete the product from. + * @param product the product to be unpublished and deleted. + * @return a {@link CompletionStage} containing the deleted product. + */ + @Nonnull + private static CompletionStage unpublishAndDeleteProduct( + @Nonnull final SphereClient ctpClient, @Nonnull final Product product) { + return ctpClient + .execute(buildUnpublishRequest(product)) + .thenCompose(unpublishedProduct -> deleteProduct(ctpClient, unpublishedProduct)); + } + + /** + * Note: This method assumes the product is unpublished. + * + * @param ctpClient the client defining the CTP project to delete the product from. + * @param product the product to be deleted. + * @return a {@link CompletionStage} containing the deleted product. If the product supplied was + * already unpublished the method will return a completion stage that completed exceptionally. + */ + @Nonnull + private static CompletionStage deleteProduct( + @Nonnull final SphereClient ctpClient, @Nonnull final Product product) { + return ctpClient.execute(ProductDeleteCommand.of(product)); + } + + /** + * Builds an unpublish request for the supplied product. + * + * @param product defines the product to build an un publish request for. + * @return an unpublish request for the supplied product. + */ + @Nonnull + private static SphereRequest buildUnpublishRequest(@Nonnull final Product product) { + return ProductUpdateCommand.of(product, singletonList(Unpublish.of())); + } + + /** + * Gets the supplied {@link ProductDraft} with the price reference attached on all its variants' + * prices. + * + *

TODO: GITHUB ISSUE#152 + * + * @param productDraft the product draft to attach the channel reference on its variants' prices. + * @param channelReference the channel reference to attach on the product draft's variants' + * prices. + * @return the product draft with the supplied references attached on the product draft's + * variants' prices. + */ + public static ProductDraft getDraftWithPriceReferences( + @Nonnull final ProductDraft productDraft, + @Nullable final Reference channelReference, + @Nullable final CustomFieldsDraft customFieldsDraft) { + final List allVariants = + productDraft.getVariants().stream() + .map( + productVariant -> { + final List priceDraftsWithChannelReferences = + getPriceDraftsWithReferences( + productVariant, channelReference, customFieldsDraft); + return ProductVariantDraftBuilder.of(productVariant) + .prices(priceDraftsWithChannelReferences) + .build(); + }) .collect(toList()); - return ofNullable(productDraft.getMasterVariant()) - .map(masterVariant -> { - final List priceDraftsWithReferences = getPriceDraftsWithReferences(masterVariant, - channelReference, customFieldsDraft); - final ProductVariantDraft masterVariantWithPriceDrafts = - ProductVariantDraftBuilder.of(masterVariant) - .prices(priceDraftsWithReferences) - .build(); - - return ProductDraftBuilder.of(productDraft) - .masterVariant(masterVariantWithPriceDrafts) - .variants(allVariants) - .build(); + return ofNullable(productDraft.getMasterVariant()) + .map( + masterVariant -> { + final List priceDraftsWithReferences = + getPriceDraftsWithReferences(masterVariant, channelReference, customFieldsDraft); + final ProductVariantDraft masterVariantWithPriceDrafts = + ProductVariantDraftBuilder.of(masterVariant) + .prices(priceDraftsWithReferences) + .build(); + + return ProductDraftBuilder.of(productDraft) + .masterVariant(masterVariantWithPriceDrafts) + .variants(allVariants) + .build(); }) - .orElse(ProductDraftBuilder.of(productDraft).variants(allVariants).build()); - } - - /** - * Builds a list of {@link PriceDraft} elements which are identical to the supplied {@link ProductVariantDraft}'s - * list of prices and sets the channel and custom type references on the prices if they are not null. - * - *

TODO: GITHUB ISSUE#152 - * - * @param productVariant the product variant to create an identical price list from. - * @param channelReference the channel reference to set on the resulting price drafts. - * @param customFieldsDraft the custom fields to set on the resulting price drafts. - * @return a list of {@link PriceDraft} elements which are identical to the supplied {@link ProductVariantDraft}'s - * list of prices and sets the channel and custom type references on the prices if they are not null. - */ - @Nonnull - private static List getPriceDraftsWithReferences( - @Nonnull final ProductVariantDraft productVariant, - @Nullable final ResourceIdentifier channelReference, - @Nullable final CustomFieldsDraft customFieldsDraft) { - - return productVariant.getPrices() - .stream() - .map(PriceDraftBuilder::of) - .map(priceDraftBuilder -> ofNullable(channelReference).map(priceDraftBuilder::channel) - .orElse(priceDraftBuilder)) - .map(priceDraftBuilder -> ofNullable(customFieldsDraft).map(priceDraftBuilder::custom) - .orElse(priceDraftBuilder)) - .map(PriceDraftBuilder::build) - .collect(toList()); - } - - /** - * This method blocks to create a price custom Type on the CTP project defined by the supplied - * {@code ctpClient}, with the supplied data. - * @param typeKey the type key - * @param locale the locale to be used for specifying the type name and field definitions names. - * @param name the name of the custom type. - * @param ctpClient defines the CTP project to create the type on. - */ - public static Type createPricesCustomType( - @Nonnull final String typeKey, - @Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final SphereClient ctpClient) { - - return createTypeIfNotAlreadyExisting( - typeKey, locale, name, ResourceTypeIdsSetBuilder.of().addPrices(), ctpClient); - } - - private ProductITUtils() { - } + .orElse(ProductDraftBuilder.of(productDraft).variants(allVariants).build()); + } + + /** + * Builds a list of {@link PriceDraft} elements which are identical to the supplied {@link + * ProductVariantDraft}'s list of prices and sets the channel and custom type references on the + * prices if they are not null. + * + *

TODO: GITHUB ISSUE#152 + * + * @param productVariant the product variant to create an identical price list from. + * @param channelReference the channel reference to set on the resulting price drafts. + * @param customFieldsDraft the custom fields to set on the resulting price drafts. + * @return a list of {@link PriceDraft} elements which are identical to the supplied {@link + * ProductVariantDraft}'s list of prices and sets the channel and custom type references on + * the prices if they are not null. + */ + @Nonnull + private static List getPriceDraftsWithReferences( + @Nonnull final ProductVariantDraft productVariant, + @Nullable final ResourceIdentifier channelReference, + @Nullable final CustomFieldsDraft customFieldsDraft) { + + return productVariant.getPrices().stream() + .map(PriceDraftBuilder::of) + .map( + priceDraftBuilder -> + ofNullable(channelReference) + .map(priceDraftBuilder::channel) + .orElse(priceDraftBuilder)) + .map( + priceDraftBuilder -> + ofNullable(customFieldsDraft) + .map(priceDraftBuilder::custom) + .orElse(priceDraftBuilder)) + .map(PriceDraftBuilder::build) + .collect(toList()); + } + + /** + * This method blocks to create a price custom Type on the CTP project defined by the supplied + * {@code ctpClient}, with the supplied data. + * + * @param typeKey the type key + * @param locale the locale to be used for specifying the type name and field definitions names. + * @param name the name of the custom type. + * @param ctpClient defines the CTP project to create the type on. + */ + public static Type createPricesCustomType( + @Nonnull final String typeKey, + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final SphereClient ctpClient) { + + return createTypeIfNotAlreadyExisting( + typeKey, locale, name, ResourceTypeIdsSetBuilder.of().addPrices(), ctpClient); + } + + private ProductITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ProductTypeITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ProductTypeITUtils.java index f52aea9ea0..647c1f3a7d 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ProductTypeITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ProductTypeITUtils.java @@ -1,5 +1,14 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Optional.ofNullable; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.utils.CtpQueryUtils; import io.sphere.sdk.client.ConcurrentModificationException; import io.sphere.sdk.client.SphereClient; @@ -30,8 +39,6 @@ import io.sphere.sdk.producttypes.commands.updateactions.RemoveAttributeDefinition; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; import io.sphere.sdk.producttypes.queries.ProductTypeQueryBuilder; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -44,462 +51,497 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static java.util.Optional.ofNullable; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; public final class ProductTypeITUtils { - private static final String LOCALISED_STRING_ATTRIBUTE_NAME = "backgroundColor"; - private static final String BOOLEAN_ATTRIBUTE_NAME = "invisibleInShop"; - - public static final String PRODUCT_TYPE_KEY_1 = "key_1"; - public static final String PRODUCT_TYPE_KEY_2 = "key_2"; - public static final String PRODUCT_TYPE_KEY_3 = "key_3"; - public static final String PRODUCT_TYPE_KEY_4 = "key_4"; - - public static final String PRODUCT_TYPE_NAME_1 = "name_1"; - public static final String PRODUCT_TYPE_NAME_2 = "name_2"; - public static final String PRODUCT_TYPE_NAME_3 = "name_3"; - public static final String PRODUCT_TYPE_NAME_4 = "name_4"; - - public static final String PRODUCT_TYPE_DESCRIPTION_1 = "description_1"; - public static final String PRODUCT_TYPE_DESCRIPTION_2 = "description_2"; - public static final String PRODUCT_TYPE_DESCRIPTION_3 = "description_3"; - public static final String PRODUCT_TYPE_DESCRIPTION_4 = "description_4"; - - public static final AttributeDefinitionDraft ATTRIBUTE_DEFINITION_DRAFT_1 = AttributeDefinitionDraftBuilder - .of( - StringAttributeType.of(), - "attr_name_1", - LocalizedString.ofEnglish("attr_label_1"), - true - ) - .inputTip(LocalizedString.ofEnglish("inputTip1")) - .build(); - - public static final AttributeDefinitionDraft ATTRIBUTE_DEFINITION_DRAFT_2 = AttributeDefinitionDraftBuilder - .of( - StringAttributeType.of(), - "attr_name_2", - LocalizedString.ofEnglish("attr_label_2"), - true - ) - .inputTip(LocalizedString.ofEnglish("inputTip2")) - .build(); - - public static final AttributeDefinitionDraft ATTRIBUTE_DEFINITION_DRAFT_3 = AttributeDefinitionDraftBuilder - .of( - StringAttributeType.of(), - "attr_name_3", - LocalizedString.ofEnglish("attr_label_3"), - true - ) - .inputTip(LocalizedString.ofEnglish("inputTip3")) - .build(); - - - public static final ProductTypeDraft productTypeDraft1 = ProductTypeDraft.ofAttributeDefinitionDrafts( - PRODUCT_TYPE_KEY_1, - PRODUCT_TYPE_NAME_1, - PRODUCT_TYPE_DESCRIPTION_1, - asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2) - ); - - public static final ProductTypeDraft productTypeDraft2 = ProductTypeDraft.ofAttributeDefinitionDrafts( - PRODUCT_TYPE_KEY_2, - PRODUCT_TYPE_NAME_2, - PRODUCT_TYPE_DESCRIPTION_2, - singletonList(ATTRIBUTE_DEFINITION_DRAFT_1) - ); - - /** - * Populate source CTP project. - * Creates product type with key PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1 and - * attributes attributeDefinitionDraft1, attributeDefinitionDraft2. - * Creates product type with key PRODUCT_TYPE_KEY_2, PRODUCT_TYPE_NAME_2, PRODUCT_TYPE_DESCRIPTION_2 and - * attributes attributeDefinitionDraft1. - */ - public static void populateSourceProject() { - CTP_SOURCE_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft1)).toCompletableFuture().join(); - CTP_SOURCE_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft2)).toCompletableFuture().join(); - } - - public static void populateSourcesProjectWithNestedAttributes() { - final ProductType productType1 = - CTP_SOURCE_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft1)).toCompletableFuture().join(); - final ProductType productType2 = - CTP_SOURCE_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft2)).toCompletableFuture().join(); - - final AttributeDefinition nestedTypeAttr1 = AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productType1)) - .isSearchable(false) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets - // it to true by default - .build(); - - final AttributeDefinition nestedTypeAttr2 = AttributeDefinitionBuilder - .of("nestedattr2", ofEnglish("nestedattr2"), NestedAttributeType.of(productType2)) - .isSearchable(false) - .build(); - - final ProductTypeDraft productTypeDraft3 = ProductTypeDraft.ofAttributeDefinitionDrafts( - PRODUCT_TYPE_KEY_3, - PRODUCT_TYPE_NAME_3, - PRODUCT_TYPE_DESCRIPTION_3, - asList(AttributeDefinitionDraftBuilder.of(nestedTypeAttr1).build(), - AttributeDefinitionDraftBuilder.of(nestedTypeAttr2).build())); - - CTP_SOURCE_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft3)).toCompletableFuture().join(); - - final ProductTypeDraft productTypeDraft4 = ProductTypeDraft.ofAttributeDefinitionDrafts( - PRODUCT_TYPE_KEY_4, - PRODUCT_TYPE_NAME_4, - PRODUCT_TYPE_DESCRIPTION_4, - singletonList(AttributeDefinitionDraftBuilder.of(nestedTypeAttr1).build())); - - CTP_SOURCE_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft4)).toCompletableFuture().join(); - } - - /** - * Populate source CTP project. - * Creates product type with key PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1 and - * attributes attributeDefinitionDraft1, attributeDefinitionDraft2. - */ - public static void populateTargetProject() { - CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft1)).toCompletableFuture().join(); - } - - public static void populateTargetProjectWithNestedAttributes() { - final ProductType productType1 = - CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft1)).toCompletableFuture().join(); - - final ProductType productType2 = - CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft2)).toCompletableFuture().join(); - - final AttributeDefinition nestedTypeAttr1 = AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productType1)) - .isSearchable(false) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets - // it to true by default - .build(); - - final AttributeDefinition nestedTypeAttr2 = AttributeDefinitionBuilder - .of("nestedattr2", ofEnglish("nestedattr2"), NestedAttributeType.of(productType2)) - .isSearchable(false) - .build(); - - final ProductTypeDraft productTypeDraft3 = ProductTypeDraft.ofAttributeDefinitionDrafts( - PRODUCT_TYPE_KEY_3, - PRODUCT_TYPE_NAME_3, - PRODUCT_TYPE_DESCRIPTION_3, - asList(AttributeDefinitionDraftBuilder.of(nestedTypeAttr1).build(), - AttributeDefinitionDraftBuilder.of(nestedTypeAttr2).build())); - - CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft3)).toCompletableFuture().join(); - } - - /** - * Deletes all ProductTypes from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and - * {@code CTP_TARGET_CLIENT}. - */ - public static void deleteProductTypesFromTargetAndSource() { - deleteProductTypes(CTP_TARGET_CLIENT); - deleteProductTypes(CTP_SOURCE_CLIENT); - } - - /** - * Deletes all product types from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the product types from. - */ - public static void deleteProductTypes(@Nonnull final SphereClient ctpClient) { - deleteProductTypesWithRetry(ctpClient); - } - - /** - * Deletes all attributes with references and - * then product types from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the product types from. - */ - public static void removeAttributeReferencesAndDeleteProductTypes(@Nonnull final SphereClient ctpClient) { - deleteProductTypeAttributes(ctpClient); - deleteProductTypes(ctpClient); - } - - private static void deleteProductTypesWithRetry(@Nonnull final SphereClient ctpClient) { - final Consumer> pageConsumer = - pageElements -> CompletableFuture.allOf(pageElements.stream() - .map(productType -> deleteProductTypeWithRetry(ctpClient, productType)) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)) - .join(); - - CtpQueryUtils.queryAll(ctpClient, ProductTypeQuery.of(), pageConsumer, 50) - .toCompletableFuture() - .join(); - } - - private static CompletionStage deleteProductTypeWithRetry(@Nonnull final SphereClient ctpClient, - @Nonnull final ProductType productType) { - return ctpClient.execute(ProductTypeDeleteCommand.of(productType)) - .handle((result, throwable) -> { - if (throwable instanceof ConcurrentModificationException) { - Long currentVersion = ((ConcurrentModificationException)throwable).getCurrentVersion(); - SphereRequest retry = - ProductTypeDeleteCommand.of(Versioned.of(productType.getId(), currentVersion)); - - ctpClient.execute(retry); - } - return result; - }); - } - - /** - * Deletes all product type attributes from the CTP project defined by the {@code ctpClient} to able to - * delete a product type if it is referenced by at least one product type. - * - * @param ctpClient defines the CTP project to delete the product types from. - */ - private static void deleteProductTypeAttributes(@Nonnull final SphereClient ctpClient) { - final ConcurrentHashMap>> productTypesToUpdate = - new ConcurrentHashMap<>(); - - CtpQueryUtils - .queryAll(ctpClient, - ProductTypeQuery.of(), page -> { - page.forEach(productType -> { - final Set> removeActions = - productType - .getAttributes() - .stream() - .map(attributeDefinition -> RemoveAttributeDefinition - .of(attributeDefinition.getName())) - .collect(Collectors.toSet()); - productTypesToUpdate.put(productType, removeActions); - }); - }) - .thenCompose(aVoid -> - CompletableFuture.allOf(productTypesToUpdate - .entrySet() - .stream() - .map(entry -> removeAttributeDefinitionWithRetry(ctpClient, entry) - ) - .toArray(CompletableFuture[]::new)) - ) + private static final String LOCALISED_STRING_ATTRIBUTE_NAME = "backgroundColor"; + private static final String BOOLEAN_ATTRIBUTE_NAME = "invisibleInShop"; + + public static final String PRODUCT_TYPE_KEY_1 = "key_1"; + public static final String PRODUCT_TYPE_KEY_2 = "key_2"; + public static final String PRODUCT_TYPE_KEY_3 = "key_3"; + public static final String PRODUCT_TYPE_KEY_4 = "key_4"; + + public static final String PRODUCT_TYPE_NAME_1 = "name_1"; + public static final String PRODUCT_TYPE_NAME_2 = "name_2"; + public static final String PRODUCT_TYPE_NAME_3 = "name_3"; + public static final String PRODUCT_TYPE_NAME_4 = "name_4"; + + public static final String PRODUCT_TYPE_DESCRIPTION_1 = "description_1"; + public static final String PRODUCT_TYPE_DESCRIPTION_2 = "description_2"; + public static final String PRODUCT_TYPE_DESCRIPTION_3 = "description_3"; + public static final String PRODUCT_TYPE_DESCRIPTION_4 = "description_4"; + + public static final AttributeDefinitionDraft ATTRIBUTE_DEFINITION_DRAFT_1 = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), + "attr_name_1", + LocalizedString.ofEnglish("attr_label_1"), + true) + .inputTip(LocalizedString.ofEnglish("inputTip1")) + .build(); + + public static final AttributeDefinitionDraft ATTRIBUTE_DEFINITION_DRAFT_2 = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), + "attr_name_2", + LocalizedString.ofEnglish("attr_label_2"), + true) + .inputTip(LocalizedString.ofEnglish("inputTip2")) + .build(); + + public static final AttributeDefinitionDraft ATTRIBUTE_DEFINITION_DRAFT_3 = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), + "attr_name_3", + LocalizedString.ofEnglish("attr_label_3"), + true) + .inputTip(LocalizedString.ofEnglish("inputTip3")) + .build(); + + public static final ProductTypeDraft productTypeDraft1 = + ProductTypeDraft.ofAttributeDefinitionDrafts( + PRODUCT_TYPE_KEY_1, + PRODUCT_TYPE_NAME_1, + PRODUCT_TYPE_DESCRIPTION_1, + asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2)); + + public static final ProductTypeDraft productTypeDraft2 = + ProductTypeDraft.ofAttributeDefinitionDrafts( + PRODUCT_TYPE_KEY_2, + PRODUCT_TYPE_NAME_2, + PRODUCT_TYPE_DESCRIPTION_2, + singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); + + /** + * Populate source CTP project. Creates product type with key PRODUCT_TYPE_KEY_1, + * PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1 and attributes attributeDefinitionDraft1, + * attributeDefinitionDraft2. Creates product type with key PRODUCT_TYPE_KEY_2, + * PRODUCT_TYPE_NAME_2, PRODUCT_TYPE_DESCRIPTION_2 and attributes attributeDefinitionDraft1. + */ + public static void populateSourceProject() { + CTP_SOURCE_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft1)) + .toCompletableFuture() + .join(); + CTP_SOURCE_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft2)) + .toCompletableFuture() + .join(); + } + + public static void populateSourcesProjectWithNestedAttributes() { + final ProductType productType1 = + CTP_SOURCE_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft1)) .toCompletableFuture() .join(); - - try { - // The removal of the attributes is eventually consistent. - // Here with one second break we are slowing down the ITs a little bit so CTP could remove the attributes. - // see: SUPPORT-8408 - Thread.sleep(1000); - } catch (InterruptedException expected) { } - } - - private static CompletionStage removeAttributeDefinitionWithRetry( - @Nonnull final SphereClient ctpClient, - @Nonnull final Map.Entry>> entry) { - - return ctpClient.execute( - ProductTypeUpdateCommand.of(entry.getKey(), new ArrayList<>(entry.getValue()))) - .handle((result, throwable) -> { - if (throwable instanceof ConcurrentModificationException) { - Long currentVersion = - ((ConcurrentModificationException) throwable).getCurrentVersion(); - Versioned versioned = - Versioned.of(entry.getKey().getId(), currentVersion); - SphereRequest retry = - ProductTypeUpdateCommand.of(versioned, - new ArrayList<>(entry.getValue())); - - ctpClient.execute(retry); - } - return result; - }); - } - - /** - * This method blocks to create a product Type on the CTP project defined by the supplied {@code ctpClient} with - * the supplied data. - * - * @param productTypeKey the product type key - * @param locale the locale to be used for specifying the product type name and field definitions names. - * @param name the name of the product type. - * @param ctpClient defines the CTP project to create the product type on. - */ - public static void createProductType(@Nonnull final String productTypeKey, - @Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final SphereClient ctpClient) { - if (!productTypeExists(productTypeKey, ctpClient)) { - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of(productTypeKey, name, "description", buildAttributeDefinitionDrafts(locale)) - .build(); - ctpClient.execute(ProductTypeCreateCommand.of(productTypeDraft)).toCompletableFuture().join(); - } - } - - /** - * This method blocks to create a product type, which is defined by the JSON resource found in the supplied - * {@code jsonResourcePath}, in the CTP project defined by the supplied {@code ctpClient}. - * - * @param jsonResourcePath defines the path of the JSON resource of the product type. - * @param ctpClient defines the CTP project to create the product type on. - */ - public static ProductType createProductType(@Nonnull final String jsonResourcePath, - @Nonnull final SphereClient ctpClient) { - final ProductType productTypeFromJson = readObjectFromResource(jsonResourcePath, ProductType.class); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder.of(productTypeFromJson) - .build(); - return ctpClient.execute(ProductTypeCreateCommand.of(productTypeDraft)) - .toCompletableFuture().join(); - } - - /** - * Builds a list of two field definitions; one for a {@link LocalizedStringAttributeType} and one for a - * {@link BooleanAttributeType}. The JSON of the created attribute definition list looks as follows: - * - *

"attributes": [ - * { - * "name": "backgroundColor", - * "label": { - * "en": "backgroundColor" - * }, - * "type": { - * "name": "LocalizedString" - * }, - * "inputHint": "SingleLine" - * }, - * { - * "name": "invisibleInShop", - * "label": { - * "en": "invisibleInShop" - * }, - * "type": { - * "name": "Boolean" - * }, - * "inputHint": "SingleLine" - * } - * ] - * - * @param locale defines the locale for which the field definition names are going to be bound to. - * @return the list of field definitions. - */ - private static List buildAttributeDefinitionDrafts(@Nonnull final Locale locale) { - return asList( - AttributeDefinitionDraftBuilder.of(LocalizedStringAttributeType.of(), LOCALISED_STRING_ATTRIBUTE_NAME, - LocalizedString.of(locale, LOCALISED_STRING_ATTRIBUTE_NAME), false).build(), - AttributeDefinitionDraftBuilder.of(BooleanAttributeType.of(), BOOLEAN_ATTRIBUTE_NAME, - LocalizedString.of(locale, BOOLEAN_ATTRIBUTE_NAME), false).build() - ); - - } - - private static boolean productTypeExists(@Nonnull final String productTypeKey, - @Nonnull final SphereClient ctpClient) { - final Optional productTypeOptional = ctpClient - .execute(ProductTypeQuery.of().byKey(productTypeKey)) + final ProductType productType2 = + CTP_SOURCE_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft2)) .toCompletableFuture() - .join().head(); - return productTypeOptional.isPresent(); - } + .join(); - /** - * Tries to fetch product type of {@code key} using {@code sphereClient}. - * - * @param sphereClient sphere client used to execute requests. - * @param key key of requested product type. - * @return {@link Optional} which may contain product type of {@code key}. - */ - public static Optional getProductTypeByKey( - @Nonnull final SphereClient sphereClient, - @Nonnull final String key) { - - final ProductTypeQuery query = ProductTypeQueryBuilder - .of() - .plusPredicates(queryModel -> queryModel.key().is(key)) + final AttributeDefinition nestedTypeAttr1 = + AttributeDefinitionBuilder.of( + "nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productType1)) + .isSearchable(false) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets + // it to true by default .build(); - return sphereClient.execute(query).toCompletableFuture().join().head(); - } - - public static void assertAttributesAreEqual(@Nonnull final List attributes, - @Nonnull final List attributesDrafts) { - - assertThat(attributes).hasSameSizeAs(attributesDrafts); - IntStream.range(0, attributesDrafts.size()) - .forEach(index -> { - final AttributeDefinition attribute = attributes.get(index); - final AttributeDefinitionDraft attributeDraft = attributesDrafts.get(index); - - assertThat(attribute.getName()).isEqualTo(attributeDraft.getName()); - - assertThat(attribute.getLabel()).isEqualTo(attributeDraft.getLabel()); - - assertThat(attribute.getAttributeType()).isEqualTo(attributeDraft.getAttributeType()); - - assertThat(attribute.getInputHint()) - .isEqualTo(ofNullable(attributeDraft.getInputHint()).orElse(TextInputHint.SINGLE_LINE)); - - assertThat(attribute.getInputTip()).isEqualTo(attributeDraft.getInputTip()); - - assertThat(attribute.isRequired()).isEqualTo(attributeDraft.isRequired()); - - assertThat(attribute.isSearchable()) - .isEqualTo(ofNullable(attributeDraft.isSearchable()).orElse(true)); - - assertThat(attribute.getAttributeConstraint()) - .isEqualTo(ofNullable(attributeDraft.getAttributeConstraint()) - .orElse(AttributeConstraint.NONE)); - - if (attribute.getAttributeType().getClass() == EnumAttributeType.class) { - assertPlainEnumsValuesAreEqual( - ((EnumAttributeType) attribute.getAttributeType()).getValues(), - ((EnumAttributeType) attributeDraft.getAttributeType()).getValues() - ); - } else if (attribute.getAttributeType().getClass() == LocalizedEnumAttributeType.class) { - assertLocalizedEnumsValuesAreEqual( - ((LocalizedEnumAttributeType) attribute.getAttributeType()).getValues(), - ((LocalizedEnumAttributeType) attributeDraft.getAttributeType()).getValues() - ); - } - }); - } + final AttributeDefinition nestedTypeAttr2 = + AttributeDefinitionBuilder.of( + "nestedattr2", ofEnglish("nestedattr2"), NestedAttributeType.of(productType2)) + .isSearchable(false) + .build(); - private static void assertPlainEnumsValuesAreEqual(@Nonnull final List enumValues, - @Nonnull final List enumValuesDrafts) { + final ProductTypeDraft productTypeDraft3 = + ProductTypeDraft.ofAttributeDefinitionDrafts( + PRODUCT_TYPE_KEY_3, + PRODUCT_TYPE_NAME_3, + PRODUCT_TYPE_DESCRIPTION_3, + asList( + AttributeDefinitionDraftBuilder.of(nestedTypeAttr1).build(), + AttributeDefinitionDraftBuilder.of(nestedTypeAttr2).build())); + + CTP_SOURCE_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft3)) + .toCompletableFuture() + .join(); + + final ProductTypeDraft productTypeDraft4 = + ProductTypeDraft.ofAttributeDefinitionDrafts( + PRODUCT_TYPE_KEY_4, + PRODUCT_TYPE_NAME_4, + PRODUCT_TYPE_DESCRIPTION_4, + singletonList(AttributeDefinitionDraftBuilder.of(nestedTypeAttr1).build())); + + CTP_SOURCE_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft4)) + .toCompletableFuture() + .join(); + } + + /** + * Populate source CTP project. Creates product type with key PRODUCT_TYPE_KEY_1, + * PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1 and attributes attributeDefinitionDraft1, + * attributeDefinitionDraft2. + */ + public static void populateTargetProject() { + CTP_TARGET_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft1)) + .toCompletableFuture() + .join(); + } + + public static void populateTargetProjectWithNestedAttributes() { + final ProductType productType1 = + CTP_TARGET_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft1)) + .toCompletableFuture() + .join(); - IntStream.range(0, enumValuesDrafts.size()) - .forEach(index -> { - final EnumValue enumValue = enumValues.get(index); - final EnumValue enumValueDraft = enumValuesDrafts.get(index); + final ProductType productType2 = + CTP_TARGET_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft2)) + .toCompletableFuture() + .join(); - assertThat(enumValue.getKey()).isEqualTo(enumValueDraft.getKey()); - assertThat(enumValue.getLabel()).isEqualTo(enumValueDraft.getLabel()); - }); - } + final AttributeDefinition nestedTypeAttr1 = + AttributeDefinitionBuilder.of( + "nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productType1)) + .isSearchable(false) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets + // it to true by default + .build(); - private static void assertLocalizedEnumsValuesAreEqual(@Nonnull final List enumValues, - @Nonnull final List enumValuesDrafts) { + final AttributeDefinition nestedTypeAttr2 = + AttributeDefinitionBuilder.of( + "nestedattr2", ofEnglish("nestedattr2"), NestedAttributeType.of(productType2)) + .isSearchable(false) + .build(); - IntStream.range(0, enumValuesDrafts.size()) - .forEach(index -> { - final LocalizedEnumValue enumValue = enumValues.get(index); - final LocalizedEnumValue enumValueDraft = enumValuesDrafts.get(index); + final ProductTypeDraft productTypeDraft3 = + ProductTypeDraft.ofAttributeDefinitionDrafts( + PRODUCT_TYPE_KEY_3, + PRODUCT_TYPE_NAME_3, + PRODUCT_TYPE_DESCRIPTION_3, + asList( + AttributeDefinitionDraftBuilder.of(nestedTypeAttr1).build(), + AttributeDefinitionDraftBuilder.of(nestedTypeAttr2).build())); + + CTP_TARGET_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft3)) + .toCompletableFuture() + .join(); + } + + /** + * Deletes all ProductTypes from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and {@code + * CTP_TARGET_CLIENT}. + */ + public static void deleteProductTypesFromTargetAndSource() { + deleteProductTypes(CTP_TARGET_CLIENT); + deleteProductTypes(CTP_SOURCE_CLIENT); + } + + /** + * Deletes all product types from the CTP project defined by the {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the product types from. + */ + public static void deleteProductTypes(@Nonnull final SphereClient ctpClient) { + deleteProductTypesWithRetry(ctpClient); + } + + /** + * Deletes all attributes with references and then product types from the CTP project defined by + * the {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the product types from. + */ + public static void removeAttributeReferencesAndDeleteProductTypes( + @Nonnull final SphereClient ctpClient) { + deleteProductTypeAttributes(ctpClient); + deleteProductTypes(ctpClient); + } + + private static void deleteProductTypesWithRetry(@Nonnull final SphereClient ctpClient) { + final Consumer> pageConsumer = + pageElements -> + CompletableFuture.allOf( + pageElements.stream() + .map(productType -> deleteProductTypeWithRetry(ctpClient, productType)) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); - assertThat(enumValue.getKey()).isEqualTo(enumValueDraft.getKey()); - assertThat(enumValue.getLabel()).isEqualTo(enumValueDraft.getLabel()); - }); + CtpQueryUtils.queryAll(ctpClient, ProductTypeQuery.of(), pageConsumer, 50) + .toCompletableFuture() + .join(); + } + + private static CompletionStage deleteProductTypeWithRetry( + @Nonnull final SphereClient ctpClient, @Nonnull final ProductType productType) { + return ctpClient + .execute(ProductTypeDeleteCommand.of(productType)) + .handle( + (result, throwable) -> { + if (throwable instanceof ConcurrentModificationException) { + Long currentVersion = + ((ConcurrentModificationException) throwable).getCurrentVersion(); + SphereRequest retry = + ProductTypeDeleteCommand.of(Versioned.of(productType.getId(), currentVersion)); + + ctpClient.execute(retry); + } + return result; + }); + } + + /** + * Deletes all product type attributes from the CTP project defined by the {@code ctpClient} to + * able to delete a product type if it is referenced by at least one product type. + * + * @param ctpClient defines the CTP project to delete the product types from. + */ + private static void deleteProductTypeAttributes(@Nonnull final SphereClient ctpClient) { + final ConcurrentHashMap>> productTypesToUpdate = + new ConcurrentHashMap<>(); + + CtpQueryUtils.queryAll( + ctpClient, + ProductTypeQuery.of(), + page -> { + page.forEach( + productType -> { + final Set> removeActions = + productType.getAttributes().stream() + .map( + attributeDefinition -> + RemoveAttributeDefinition.of(attributeDefinition.getName())) + .collect(Collectors.toSet()); + productTypesToUpdate.put(productType, removeActions); + }); + }) + .thenCompose( + aVoid -> + CompletableFuture.allOf( + productTypesToUpdate.entrySet().stream() + .map(entry -> removeAttributeDefinitionWithRetry(ctpClient, entry)) + .toArray(CompletableFuture[]::new))) + .toCompletableFuture() + .join(); + + try { + // The removal of the attributes is eventually consistent. + // Here with one second break we are slowing down the ITs a little bit so CTP could remove the + // attributes. + // see: SUPPORT-8408 + Thread.sleep(1000); + } catch (InterruptedException expected) { } - - private ProductTypeITUtils() { + } + + private static CompletionStage removeAttributeDefinitionWithRetry( + @Nonnull final SphereClient ctpClient, + @Nonnull final Map.Entry>> entry) { + + return ctpClient + .execute(ProductTypeUpdateCommand.of(entry.getKey(), new ArrayList<>(entry.getValue()))) + .handle( + (result, throwable) -> { + if (throwable instanceof ConcurrentModificationException) { + Long currentVersion = + ((ConcurrentModificationException) throwable).getCurrentVersion(); + Versioned versioned = + Versioned.of(entry.getKey().getId(), currentVersion); + SphereRequest retry = + ProductTypeUpdateCommand.of(versioned, new ArrayList<>(entry.getValue())); + + ctpClient.execute(retry); + } + return result; + }); + } + + /** + * This method blocks to create a product Type on the CTP project defined by the supplied {@code + * ctpClient} with the supplied data. + * + * @param productTypeKey the product type key + * @param locale the locale to be used for specifying the product type name and field definitions + * names. + * @param name the name of the product type. + * @param ctpClient defines the CTP project to create the product type on. + */ + public static void createProductType( + @Nonnull final String productTypeKey, + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final SphereClient ctpClient) { + if (!productTypeExists(productTypeKey, ctpClient)) { + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of( + productTypeKey, name, "description", buildAttributeDefinitionDrafts(locale)) + .build(); + ctpClient.execute(ProductTypeCreateCommand.of(productTypeDraft)).toCompletableFuture().join(); } + } + + /** + * This method blocks to create a product type, which is defined by the JSON resource found in the + * supplied {@code jsonResourcePath}, in the CTP project defined by the supplied {@code + * ctpClient}. + * + * @param jsonResourcePath defines the path of the JSON resource of the product type. + * @param ctpClient defines the CTP project to create the product type on. + */ + public static ProductType createProductType( + @Nonnull final String jsonResourcePath, @Nonnull final SphereClient ctpClient) { + final ProductType productTypeFromJson = + readObjectFromResource(jsonResourcePath, ProductType.class); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of(productTypeFromJson).build(); + return ctpClient + .execute(ProductTypeCreateCommand.of(productTypeDraft)) + .toCompletableFuture() + .join(); + } + + /** + * Builds a list of two field definitions; one for a {@link LocalizedStringAttributeType} and one + * for a {@link BooleanAttributeType}. The JSON of the created attribute definition list looks as + * follows: + * + *

"attributes": [ { "name": "backgroundColor", "label": { "en": "backgroundColor" }, "type": { + * "name": "LocalizedString" }, "inputHint": "SingleLine" }, { "name": "invisibleInShop", "label": + * { "en": "invisibleInShop" }, "type": { "name": "Boolean" }, "inputHint": "SingleLine" } ] + * + * @param locale defines the locale for which the field definition names are going to be bound to. + * @return the list of field definitions. + */ + private static List buildAttributeDefinitionDrafts( + @Nonnull final Locale locale) { + return asList( + AttributeDefinitionDraftBuilder.of( + LocalizedStringAttributeType.of(), + LOCALISED_STRING_ATTRIBUTE_NAME, + LocalizedString.of(locale, LOCALISED_STRING_ATTRIBUTE_NAME), + false) + .build(), + AttributeDefinitionDraftBuilder.of( + BooleanAttributeType.of(), + BOOLEAN_ATTRIBUTE_NAME, + LocalizedString.of(locale, BOOLEAN_ATTRIBUTE_NAME), + false) + .build()); + } + + private static boolean productTypeExists( + @Nonnull final String productTypeKey, @Nonnull final SphereClient ctpClient) { + final Optional productTypeOptional = + ctpClient + .execute(ProductTypeQuery.of().byKey(productTypeKey)) + .toCompletableFuture() + .join() + .head(); + return productTypeOptional.isPresent(); + } + + /** + * Tries to fetch product type of {@code key} using {@code sphereClient}. + * + * @param sphereClient sphere client used to execute requests. + * @param key key of requested product type. + * @return {@link Optional} which may contain product type of {@code key}. + */ + public static Optional getProductTypeByKey( + @Nonnull final SphereClient sphereClient, @Nonnull final String key) { + + final ProductTypeQuery query = + ProductTypeQueryBuilder.of().plusPredicates(queryModel -> queryModel.key().is(key)).build(); + + return sphereClient.execute(query).toCompletableFuture().join().head(); + } + + public static void assertAttributesAreEqual( + @Nonnull final List attributes, + @Nonnull final List attributesDrafts) { + + assertThat(attributes).hasSameSizeAs(attributesDrafts); + IntStream.range(0, attributesDrafts.size()) + .forEach( + index -> { + final AttributeDefinition attribute = attributes.get(index); + final AttributeDefinitionDraft attributeDraft = attributesDrafts.get(index); + + assertThat(attribute.getName()).isEqualTo(attributeDraft.getName()); + + assertThat(attribute.getLabel()).isEqualTo(attributeDraft.getLabel()); + + assertThat(attribute.getAttributeType()).isEqualTo(attributeDraft.getAttributeType()); + + assertThat(attribute.getInputHint()) + .isEqualTo( + ofNullable(attributeDraft.getInputHint()).orElse(TextInputHint.SINGLE_LINE)); + + assertThat(attribute.getInputTip()).isEqualTo(attributeDraft.getInputTip()); + + assertThat(attribute.isRequired()).isEqualTo(attributeDraft.isRequired()); + + assertThat(attribute.isSearchable()) + .isEqualTo(ofNullable(attributeDraft.isSearchable()).orElse(true)); + + assertThat(attribute.getAttributeConstraint()) + .isEqualTo( + ofNullable(attributeDraft.getAttributeConstraint()) + .orElse(AttributeConstraint.NONE)); + + if (attribute.getAttributeType().getClass() == EnumAttributeType.class) { + assertPlainEnumsValuesAreEqual( + ((EnumAttributeType) attribute.getAttributeType()).getValues(), + ((EnumAttributeType) attributeDraft.getAttributeType()).getValues()); + } else if (attribute.getAttributeType().getClass() + == LocalizedEnumAttributeType.class) { + assertLocalizedEnumsValuesAreEqual( + ((LocalizedEnumAttributeType) attribute.getAttributeType()).getValues(), + ((LocalizedEnumAttributeType) attributeDraft.getAttributeType()).getValues()); + } + }); + } + + private static void assertPlainEnumsValuesAreEqual( + @Nonnull final List enumValues, @Nonnull final List enumValuesDrafts) { + + IntStream.range(0, enumValuesDrafts.size()) + .forEach( + index -> { + final EnumValue enumValue = enumValues.get(index); + final EnumValue enumValueDraft = enumValuesDrafts.get(index); + + assertThat(enumValue.getKey()).isEqualTo(enumValueDraft.getKey()); + assertThat(enumValue.getLabel()).isEqualTo(enumValueDraft.getLabel()); + }); + } + + private static void assertLocalizedEnumsValuesAreEqual( + @Nonnull final List enumValues, + @Nonnull final List enumValuesDrafts) { + + IntStream.range(0, enumValuesDrafts.size()) + .forEach( + index -> { + final LocalizedEnumValue enumValue = enumValues.get(index); + final LocalizedEnumValue enumValueDraft = enumValuesDrafts.get(index); + + assertThat(enumValue.getKey()).isEqualTo(enumValueDraft.getKey()); + assertThat(enumValue.getLabel()).isEqualTo(enumValueDraft.getLabel()); + }); + } + + private ProductTypeITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ShoppingListITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ShoppingListITUtils.java index c0adbcbdc2..b59756bbf9 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ShoppingListITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/ShoppingListITUtils.java @@ -1,5 +1,14 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.deleteCustomers; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import io.sphere.sdk.client.SphereClient; @@ -32,84 +41,82 @@ import io.sphere.sdk.types.TypeDraft; import io.sphere.sdk.types.TypeDraftBuilder; import io.sphere.sdk.types.commands.TypeCreateCommand; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.time.ZonedDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.deleteCustomers; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; - +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public final class ShoppingListITUtils { - /** - * Deletes all shopping lists, products and product types from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete test data from. - */ - public static void deleteShoppingListTestData(@Nonnull final SphereClient ctpClient) { - deleteShoppingLists(ctpClient); - deleteCustomers(ctpClient); - deleteTypes(ctpClient); - deleteAllProducts(ctpClient); - deleteProductTypes(ctpClient); - } - - /** - * Deletes all ShoppingLists from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the ShoppingLists from. - */ - public static void deleteShoppingLists(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, ShoppingListQuery.of(), ShoppingListDeleteCommand::of); - } - - - /** - * Creates a {@link ShoppingList} in the CTP project defined by the {@code ctpClient} in a blocking fashion. - * - * @param ctpClient defines the CTP project to create the ShoppingList in. - * @param name the name of the ShoppingList to create. - * @param key the key of the ShoppingList to create. - * @return the created ShoppingList. - */ - public static ShoppingList createShoppingList(@Nonnull final SphereClient ctpClient, @Nonnull final String name, - @Nonnull final String key) { - - return createShoppingList(ctpClient, name, key, null, null, null, null); - } - - /** - * Creates a {@link ShoppingList} in the CTP project defined by the {@code ctpClient} in a blocking fashion. - * - * @param ctpClient defines the CTP project to create the ShoppingList in. - * @param name the name of the ShoppingList to create. - * @param key the key of the ShoppingList to create. - * @param desc the description of the ShoppingList to create. - * @param anonymousId the anonymous ID of the ShoppingList to create. - * @param slug the slug of the ShoppingList to create. - * @param deleteDaysAfterLastModification the deleteDaysAfterLastModification of the ShoppingList to create. - * @return the created ShoppingList. - */ - public static ShoppingList createShoppingList(@Nonnull final SphereClient ctpClient, @Nonnull final String name, - @Nonnull final String key, @Nullable final String desc, - @Nullable final String anonymousId, @Nullable final String slug, - @Nullable final Integer deleteDaysAfterLastModification) { - - final ShoppingListDraft shoppingListDraft = ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish(name)) + /** + * Deletes all shopping lists, products and product types from the CTP project defined by the + * {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete test data from. + */ + public static void deleteShoppingListTestData(@Nonnull final SphereClient ctpClient) { + deleteShoppingLists(ctpClient); + deleteCustomers(ctpClient); + deleteTypes(ctpClient); + deleteAllProducts(ctpClient); + deleteProductTypes(ctpClient); + } + + /** + * Deletes all ShoppingLists from the CTP project defined by the {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the ShoppingLists from. + */ + public static void deleteShoppingLists(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, ShoppingListQuery.of(), ShoppingListDeleteCommand::of); + } + + /** + * Creates a {@link ShoppingList} in the CTP project defined by the {@code ctpClient} in a + * blocking fashion. + * + * @param ctpClient defines the CTP project to create the ShoppingList in. + * @param name the name of the ShoppingList to create. + * @param key the key of the ShoppingList to create. + * @return the created ShoppingList. + */ + public static ShoppingList createShoppingList( + @Nonnull final SphereClient ctpClient, + @Nonnull final String name, + @Nonnull final String key) { + + return createShoppingList(ctpClient, name, key, null, null, null, null); + } + + /** + * Creates a {@link ShoppingList} in the CTP project defined by the {@code ctpClient} in a + * blocking fashion. + * + * @param ctpClient defines the CTP project to create the ShoppingList in. + * @param name the name of the ShoppingList to create. + * @param key the key of the ShoppingList to create. + * @param desc the description of the ShoppingList to create. + * @param anonymousId the anonymous ID of the ShoppingList to create. + * @param slug the slug of the ShoppingList to create. + * @param deleteDaysAfterLastModification the deleteDaysAfterLastModification of the ShoppingList + * to create. + * @return the created ShoppingList. + */ + public static ShoppingList createShoppingList( + @Nonnull final SphereClient ctpClient, + @Nonnull final String name, + @Nonnull final String key, + @Nullable final String desc, + @Nullable final String anonymousId, + @Nullable final String slug, + @Nullable final Integer deleteDaysAfterLastModification) { + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish(name)) .key(key) .description(desc == null ? null : LocalizedString.ofEnglish(desc)) .anonymousId(anonymousId) @@ -117,251 +124,278 @@ public static ShoppingList createShoppingList(@Nonnull final SphereClient ctpCli .deleteDaysAfterLastModification(deleteDaysAfterLastModification) .build(); - return executeBlocking(ctpClient.execute(ShoppingListCreateCommand.of(shoppingListDraft))); - } - - /** - * Creates a sample {@link ShoppingList} in the CTP project defined by the {@code ctpClient} in a blocking fashion. - * - * @param ctpClient defines the CTP project to create the ShoppingList in. - * @return the created ShoppingList. - */ - @Nonnull - public static ImmutablePair createSampleShoppingListCarrotCake( - @Nonnull final SphereClient ctpClient) { - - createIngredientProducts(ctpClient); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("Carrot Cake")) - .key("shopping-list-key") - .slug(LocalizedString.ofEnglish("carrot-cake")) - .description(LocalizedString.ofEnglish("Carrot cake recipe - ingredients")) - .anonymousId("public-carrot-cake-shopping-list") - .deleteDaysAfterLastModification(30) - .custom(createSampleTypes(ctpClient)) - .lineItems(buildIngredientsLineItemDrafts()) - .textLineItems(buildRecipeTextLineItemDrafts()) - .build(); - - final ShoppingList shoppingList = - executeBlocking(ctpClient.execute(ShoppingListCreateCommand.of(shoppingListDraft))); - return ImmutablePair.of(shoppingList, shoppingListDraft); - } - - @Nonnull - private static CustomFieldsDraft createSampleTypes(@Nonnull final SphereClient ctpClient) { - final TypeDraft shoppingListTypeDraft = TypeDraftBuilder - .of("custom-type-shopping-list", LocalizedString.ofEnglish("name"), + return executeBlocking(ctpClient.execute(ShoppingListCreateCommand.of(shoppingListDraft))); + } + + /** + * Creates a sample {@link ShoppingList} in the CTP project defined by the {@code ctpClient} in a + * blocking fashion. + * + * @param ctpClient defines the CTP project to create the ShoppingList in. + * @return the created ShoppingList. + */ + @Nonnull + public static ImmutablePair createSampleShoppingListCarrotCake( + @Nonnull final SphereClient ctpClient) { + + createIngredientProducts(ctpClient); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("Carrot Cake")) + .key("shopping-list-key") + .slug(LocalizedString.ofEnglish("carrot-cake")) + .description(LocalizedString.ofEnglish("Carrot cake recipe - ingredients")) + .anonymousId("public-carrot-cake-shopping-list") + .deleteDaysAfterLastModification(30) + .custom(createSampleTypes(ctpClient)) + .lineItems(buildIngredientsLineItemDrafts()) + .textLineItems(buildRecipeTextLineItemDrafts()) + .build(); + + final ShoppingList shoppingList = + executeBlocking(ctpClient.execute(ShoppingListCreateCommand.of(shoppingListDraft))); + return ImmutablePair.of(shoppingList, shoppingListDraft); + } + + @Nonnull + private static CustomFieldsDraft createSampleTypes(@Nonnull final SphereClient ctpClient) { + final TypeDraft shoppingListTypeDraft = + TypeDraftBuilder.of( + "custom-type-shopping-list", + LocalizedString.ofEnglish("name"), ResourceTypeIdsSetBuilder.of().add(ShoppingList.resourceTypeId())) - .fieldDefinitions(asList( - FieldDefinition.of(StringFieldType.of(), "nutrition", - LocalizedString.ofEnglish("nutrition per serving"), false), - FieldDefinition.of(NumberFieldType.of(), "servings", - LocalizedString.ofEnglish("servings"), false))) + .fieldDefinitions( + asList( + FieldDefinition.of( + StringFieldType.of(), + "nutrition", + LocalizedString.ofEnglish("nutrition per serving"), + false), + FieldDefinition.of( + NumberFieldType.of(), + "servings", + LocalizedString.ofEnglish("servings"), + false))) .build(); - final TypeDraft lineItemTypeDraft = TypeDraftBuilder - .of("custom-type-line-items", LocalizedString.ofEnglish("name"), + final TypeDraft lineItemTypeDraft = + TypeDraftBuilder.of( + "custom-type-line-items", + LocalizedString.ofEnglish("name"), ResourceTypeIdsSetBuilder.of().add(LineItem.resourceTypeId())) - .fieldDefinitions(asList( - FieldDefinition.of(StringFieldType.of(), "ingredient", - LocalizedString.ofEnglish("ingredient"), false), - FieldDefinition.of(StringFieldType.of(), "amount", - LocalizedString.ofEnglish("amount"), false))) + .fieldDefinitions( + asList( + FieldDefinition.of( + StringFieldType.of(), + "ingredient", + LocalizedString.ofEnglish("ingredient"), + false), + FieldDefinition.of( + StringFieldType.of(), + "amount", + LocalizedString.ofEnglish("amount"), + false))) .build(); - final TypeDraft textLineItemTypeDraft = TypeDraftBuilder - .of("custom-type-text-line-items", LocalizedString.ofEnglish("name"), + final TypeDraft textLineItemTypeDraft = + TypeDraftBuilder.of( + "custom-type-text-line-items", + LocalizedString.ofEnglish("name"), ResourceTypeIdsSetBuilder.of().add(TextLineItem.resourceTypeId())) - .fieldDefinitions(singletonList( - FieldDefinition.of(StringFieldType.of(), "utensils", - LocalizedString.ofEnglish("utensils"), false))) + .fieldDefinitions( + singletonList( + FieldDefinition.of( + StringFieldType.of(), + "utensils", + LocalizedString.ofEnglish("utensils"), + false))) .build(); - CompletableFuture - .allOf( - ctpClient.execute(TypeCreateCommand.of(shoppingListTypeDraft)).toCompletableFuture(), - ctpClient.execute(TypeCreateCommand.of(lineItemTypeDraft)).toCompletableFuture(), - ctpClient.execute(TypeCreateCommand.of(textLineItemTypeDraft)).toCompletableFuture()) + CompletableFuture.allOf( + ctpClient.execute(TypeCreateCommand.of(shoppingListTypeDraft)).toCompletableFuture(), + ctpClient.execute(TypeCreateCommand.of(lineItemTypeDraft)).toCompletableFuture(), + ctpClient.execute(TypeCreateCommand.of(textLineItemTypeDraft)).toCompletableFuture()) + .join(); + + final Map servingsFields = new HashMap<>(); + servingsFields.put( + "nutrition", + JsonNodeFactory.instance.textNode( + "Per servings: 475 cal, 11g protein, 28g, fat, 44g carb")); + servingsFields.put("servings", JsonNodeFactory.instance.numberNode(12)); + + return CustomFieldsDraft.ofTypeKeyAndJson(shoppingListTypeDraft.getKey(), servingsFields); + } + + private static void createIngredientProducts(@Nonnull final SphereClient ctpClient) { + final ProductType productType = + ctpClient + .execute( + ProductTypeCreateCommand.of( + ProductTypeDraft.ofAttributeDefinitionDrafts( + "productTypeKey", "productTypeName", "desc", null))) + .toCompletableFuture() .join(); - final Map servingsFields = new HashMap<>(); - servingsFields.put("nutrition", - JsonNodeFactory.instance.textNode("Per servings: 475 cal, 11g protein, 28g, fat, 44g carb")); - servingsFields.put("servings", JsonNodeFactory.instance.numberNode(12)); - - return CustomFieldsDraft.ofTypeKeyAndJson(shoppingListTypeDraft.getKey(), - servingsFields); - } - - private static void createIngredientProducts(@Nonnull final SphereClient ctpClient) { - final ProductType productType = ctpClient - .execute( - ProductTypeCreateCommand.of(ProductTypeDraft.ofAttributeDefinitionDrafts( - "productTypeKey", - "productTypeName", - "desc", null))) - .toCompletableFuture().join(); - - final ProductDraft productDraft = ProductDraftBuilder - .of(productType, + final ProductDraft productDraft = + ProductDraftBuilder.of( + productType, LocalizedString.ofEnglish("product1"), LocalizedString.ofEnglish("product1"), ProductVariantDraftBuilder.of().sku("SKU-1").key("variant1").build()) .key("product-1-sample-carrot-cake") - .variants(asList( - ProductVariantDraftBuilder.of().sku("SKU-2").key("variant2").build(), - ProductVariantDraftBuilder.of().sku("SKU-3").key("variant3").build())) + .variants( + asList( + ProductVariantDraftBuilder.of().sku("SKU-2").key("variant2").build(), + ProductVariantDraftBuilder.of().sku("SKU-3").key("variant3").build())) .publish(true) .build(); - final ProductDraft productDraft2 = ProductDraftBuilder - .of(productType, + final ProductDraft productDraft2 = + ProductDraftBuilder.of( + productType, LocalizedString.ofEnglish("product2"), LocalizedString.ofEnglish("product2"), ProductVariantDraftBuilder.of().sku("SKU-4").key("variant4").build()) .key("product-2-sample-carrot-cake") - .variants(asList( - ProductVariantDraftBuilder.of().sku("SKU-5").key("variant5").build(), - ProductVariantDraftBuilder.of().sku("SKU-6").key("variant6").build())) + .variants( + asList( + ProductVariantDraftBuilder.of().sku("SKU-5").key("variant5").build(), + ProductVariantDraftBuilder.of().sku("SKU-6").key("variant6").build())) .publish(true) .build(); - CompletableFuture - .allOf( - ctpClient.execute(ProductCreateCommand.of(productDraft)).toCompletableFuture(), - ctpClient.execute(ProductCreateCommand.of(productDraft2)).toCompletableFuture()) - .join(); - } + CompletableFuture.allOf( + ctpClient.execute(ProductCreateCommand.of(productDraft)).toCompletableFuture(), + ctpClient.execute(ProductCreateCommand.of(productDraft2)).toCompletableFuture()) + .join(); + } - @Nonnull - private static List buildIngredientsLineItemDrafts() { + @Nonnull + private static List buildIngredientsLineItemDrafts() { - final LineItemDraft item1 = LineItemDraftBuilder - .ofSku("SKU-1", 1L) + final LineItemDraft item1 = + LineItemDraftBuilder.ofSku("SKU-1", 1L) .custom(buildIngredientCustomType("carrots", "280g")) .build(); - final LineItemDraft item2 = LineItemDraftBuilder - .ofSku("SKU-2", 7L) + final LineItemDraft item2 = + LineItemDraftBuilder.ofSku("SKU-2", 7L) .custom(buildIngredientCustomType("eggs", "7")) .build(); - final LineItemDraft item3 = LineItemDraftBuilder - .ofSku("SKU-3", 1L) + final LineItemDraft item3 = + LineItemDraftBuilder.ofSku("SKU-3", 1L) .custom(buildIngredientCustomType("sugar", "100g")) .build(); - final LineItemDraft item4 = LineItemDraftBuilder - .ofSku("SKU-4", 1L) + final LineItemDraft item4 = + LineItemDraftBuilder.ofSku("SKU-4", 1L) .custom(buildIngredientCustomType("flour", "70g")) .build(); - final LineItemDraft item5 = LineItemDraftBuilder - .ofSku("SKU-5", 1L) + final LineItemDraft item5 = + LineItemDraftBuilder.ofSku("SKU-5", 1L) .custom(buildIngredientCustomType("baking powder", "1 tsp")) .build(); - final LineItemDraft item6 = LineItemDraftBuilder - .ofSku("SKU-6", 1L) + final LineItemDraft item6 = + LineItemDraftBuilder.ofSku("SKU-6", 1L) .custom(buildIngredientCustomType("cinnamon", "2 tsp")) .build(); - return asList(item1, item2, item3, item4, item5, item6); - } - - /** - * Creates an instance of {@link CustomFieldsDraft} with the type key 'custom-type-line-items' and - * two custom fields 'ingredient' and'amount'. - * - * @param ingredient the text field. - * @param amount the text field. - * @return an instance of {@link CustomFieldsDraft} with the type key 'custom-type-line-items' and - * two custom fields 'ingredient' and'amount'. - */ - @Nonnull - public static CustomFieldsDraft buildIngredientCustomType( - @Nonnull final String ingredient, - @Nonnull final String amount) { - - final Map map = new HashMap<>(); - map.put("ingredient", JsonNodeFactory.instance.textNode(ingredient)); - map.put("amount", JsonNodeFactory.instance.textNode(amount)); - - return CustomFieldsDraft.ofTypeKeyAndJson("custom-type-line-items", map); - } - - @Nonnull - private static List buildRecipeTextLineItemDrafts() { - - final TextLineItemDraftDsl item1 = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 1"), 1L) - .description(LocalizedString.ofEnglish( - "Peel carrots and set aside, separate eggs into small balls.")) - .custom(buildUtensilsCustomType("Peeler, 2 small bowls")) - .build(); - - final TextLineItemDraftDsl item2 = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 2"), 1L) - .description(LocalizedString.ofEnglish( - "Mix powder and baking powder in a large bowl set aside, " - + "Blend slowly egg yolks and cinnamon until smooth.")) - .custom(buildUtensilsCustomType("2 large bowls, hand mixer")) - .build(); - - final TextLineItemDraftDsl item3 = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 3"), 1L) - .description(LocalizedString.ofEnglish( - "Mix egg whites and sugar until stiff.")) - .custom(buildUtensilsCustomType("1 large bowl")) - .build(); - - final TextLineItemDraftDsl item4 = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 4"), 1L) - .description(LocalizedString.ofEnglish( - "Transfer egg whites into other egg mixture, combine with powder, " - + "add peeled carrots, stir with spatula.")) - .custom(buildUtensilsCustomType("Rubber spatula")) - .build(); - - final TextLineItemDraftDsl item5 = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 5"), 1L) - .description(LocalizedString.ofEnglish( - "Put cake mixture into cake pan, bake appr 40 min with 180 C degree")) - .custom(buildUtensilsCustomType("Cake pan, oven")) - .build(); - - final TextLineItemDraftDsl item6 = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 6"), 1L) - .description(LocalizedString.ofEnglish( - "Decorate as you wish and serve, enjoy!")) - .custom(buildUtensilsCustomType("Knife, cake plate.")) - .addedAt(ZonedDateTime.parse("2020-11-06T10:00:00.000Z")) - .build(); - - return asList(item1, item2, item3, item4, item5, item6); - } - - /** - * Creates an instance of {@link CustomFieldsDraft} with the type key 'custom-type-text-line-items' and - * two custom fields 'utensil'. - * - * @param utensils the text field. - * @return an instance of {@link CustomFieldsDraft} with the type key 'custom-type-text-line-items' and - * two custom fields 'utensils'. - */ - @Nonnull - public static CustomFieldsDraft buildUtensilsCustomType( - @Nonnull final String utensils) { - - final Map map = new HashMap<>(); - map.put("utensils", JsonNodeFactory.instance.textNode(utensils)); - - return CustomFieldsDraft.ofTypeKeyAndJson("custom-type-text-line-items", map); - } - - private ShoppingListITUtils() { - } + return asList(item1, item2, item3, item4, item5, item6); + } + + /** + * Creates an instance of {@link CustomFieldsDraft} with the type key 'custom-type-line-items' and + * two custom fields 'ingredient' and'amount'. + * + * @param ingredient the text field. + * @param amount the text field. + * @return an instance of {@link CustomFieldsDraft} with the type key 'custom-type-line-items' and + * two custom fields 'ingredient' and'amount'. + */ + @Nonnull + public static CustomFieldsDraft buildIngredientCustomType( + @Nonnull final String ingredient, @Nonnull final String amount) { + + final Map map = new HashMap<>(); + map.put("ingredient", JsonNodeFactory.instance.textNode(ingredient)); + map.put("amount", JsonNodeFactory.instance.textNode(amount)); + + return CustomFieldsDraft.ofTypeKeyAndJson("custom-type-line-items", map); + } + + @Nonnull + private static List buildRecipeTextLineItemDrafts() { + + final TextLineItemDraftDsl item1 = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 1"), 1L) + .description( + LocalizedString.ofEnglish( + "Peel carrots and set aside, separate eggs into small balls.")) + .custom(buildUtensilsCustomType("Peeler, 2 small bowls")) + .build(); + + final TextLineItemDraftDsl item2 = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 2"), 1L) + .description( + LocalizedString.ofEnglish( + "Mix powder and baking powder in a large bowl set aside, " + + "Blend slowly egg yolks and cinnamon until smooth.")) + .custom(buildUtensilsCustomType("2 large bowls, hand mixer")) + .build(); + + final TextLineItemDraftDsl item3 = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 3"), 1L) + .description(LocalizedString.ofEnglish("Mix egg whites and sugar until stiff.")) + .custom(buildUtensilsCustomType("1 large bowl")) + .build(); + + final TextLineItemDraftDsl item4 = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 4"), 1L) + .description( + LocalizedString.ofEnglish( + "Transfer egg whites into other egg mixture, combine with powder, " + + "add peeled carrots, stir with spatula.")) + .custom(buildUtensilsCustomType("Rubber spatula")) + .build(); + + final TextLineItemDraftDsl item5 = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 5"), 1L) + .description( + LocalizedString.ofEnglish( + "Put cake mixture into cake pan, bake appr 40 min with 180 C degree")) + .custom(buildUtensilsCustomType("Cake pan, oven")) + .build(); + + final TextLineItemDraftDsl item6 = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 6"), 1L) + .description(LocalizedString.ofEnglish("Decorate as you wish and serve, enjoy!")) + .custom(buildUtensilsCustomType("Knife, cake plate.")) + .addedAt(ZonedDateTime.parse("2020-11-06T10:00:00.000Z")) + .build(); + + return asList(item1, item2, item3, item4, item5, item6); + } + + /** + * Creates an instance of {@link CustomFieldsDraft} with the type key + * 'custom-type-text-line-items' and two custom fields 'utensil'. + * + * @param utensils the text field. + * @return an instance of {@link CustomFieldsDraft} with the type key + * 'custom-type-text-line-items' and two custom fields 'utensils'. + */ + @Nonnull + public static CustomFieldsDraft buildUtensilsCustomType(@Nonnull final String utensils) { + + final Map map = new HashMap<>(); + map.put("utensils", JsonNodeFactory.instance.textNode(utensils)); + + return CustomFieldsDraft.ofTypeKeyAndJson("custom-type-text-line-items", map); + } + + private ShoppingListITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/SphereClientUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/SphereClientUtils.java index d5906e101f..789ce02af7 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/SphereClientUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/SphereClientUtils.java @@ -1,49 +1,51 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.commons.utils.ClientConfigurationUtils.createClient; +import static java.lang.String.format; + import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.client.SphereClientConfig; - -import javax.annotation.Nonnull; import java.io.InputStream; import java.util.Properties; - -import static com.commercetools.sync.commons.utils.ClientConfigurationUtils.createClient; -import static java.lang.String.format; +import javax.annotation.Nonnull; public class SphereClientUtils { - public static final String IT_PROPERTIES = "it.properties"; - - public static final SphereClientConfig CTP_SOURCE_CLIENT_CONFIG = getCtpSourceClientConfig(); - public static final SphereClientConfig CTP_TARGET_CLIENT_CONFIG = getCtpTargetClientConfig(); - - public static final SphereClient CTP_SOURCE_CLIENT = createClient(CTP_SOURCE_CLIENT_CONFIG); - public static final SphereClient CTP_TARGET_CLIENT = createClient(CTP_TARGET_CLIENT_CONFIG); - - private static SphereClientConfig getCtpSourceClientConfig() { - return getCtpClientConfig("source.", "SOURCE"); - } - - private static SphereClientConfig getCtpTargetClientConfig() { - return getCtpClientConfig("target.", "TARGET"); - } - - private static SphereClientConfig getCtpClientConfig(@Nonnull final String propertiesPrefix, - @Nonnull final String envVarPrefix) { - try { - InputStream propStream = SphereClientUtils.class.getClassLoader().getResourceAsStream(IT_PROPERTIES); - if (propStream != null) { - Properties itProperties = new Properties(); - itProperties.load(propStream); - return SphereClientConfig.ofProperties(itProperties, propertiesPrefix); - } - } catch (Exception exception) { - throw new IllegalStateException(format("IT properties file \"%s\" found, but CTP properties" - + " for prefix \"%s\" can't be read", IT_PROPERTIES, propertiesPrefix), exception); - } - - return SphereClientConfig.ofEnvironmentVariables(envVarPrefix); + public static final String IT_PROPERTIES = "it.properties"; + + public static final SphereClientConfig CTP_SOURCE_CLIENT_CONFIG = getCtpSourceClientConfig(); + public static final SphereClientConfig CTP_TARGET_CLIENT_CONFIG = getCtpTargetClientConfig(); + + public static final SphereClient CTP_SOURCE_CLIENT = createClient(CTP_SOURCE_CLIENT_CONFIG); + public static final SphereClient CTP_TARGET_CLIENT = createClient(CTP_TARGET_CLIENT_CONFIG); + + private static SphereClientConfig getCtpSourceClientConfig() { + return getCtpClientConfig("source.", "SOURCE"); + } + + private static SphereClientConfig getCtpTargetClientConfig() { + return getCtpClientConfig("target.", "TARGET"); + } + + private static SphereClientConfig getCtpClientConfig( + @Nonnull final String propertiesPrefix, @Nonnull final String envVarPrefix) { + try { + InputStream propStream = + SphereClientUtils.class.getClassLoader().getResourceAsStream(IT_PROPERTIES); + if (propStream != null) { + Properties itProperties = new Properties(); + itProperties.load(propStream); + return SphereClientConfig.ofProperties(itProperties, propertiesPrefix); + } + } catch (Exception exception) { + throw new IllegalStateException( + format( + "IT properties file \"%s\" found, but CTP properties" + + " for prefix \"%s\" can't be read", + IT_PROPERTIES, propertiesPrefix), + exception); } + return SphereClientConfig.ofEnvironmentVariables(envVarPrefix); + } } - diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/StateITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/StateITUtils.java index f16eb8a503..f6de31a72c 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/StateITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/StateITUtils.java @@ -1,5 +1,13 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.states.commands.updateactions.SetTransitions.of; +import static io.sphere.sdk.utils.CompletableFutureUtils.listOfFuturesToFutureOfList; +import static java.lang.String.format; + import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; @@ -16,9 +24,6 @@ import io.sphere.sdk.states.commands.updateactions.SetTransitions; import io.sphere.sdk.states.queries.StateQuery; import io.sphere.sdk.states.queries.StateQueryBuilder; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -27,134 +32,145 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.states.commands.updateactions.SetTransitions.of; -import static io.sphere.sdk.utils.CompletableFutureUtils.listOfFuturesToFutureOfList; -import static java.lang.String.format; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class StateITUtils { - public static final String STATE_KEY_1 = "key_1"; - public static final LocalizedString STATE_NAME_1 = LocalizedString.ofEnglish("name_1"); - public static final LocalizedString STATE_DESCRIPTION_1 = LocalizedString.ofEnglish("description_1"); - private static final String STATE_KEY = "old_state_key"; - - private StateITUtils() { - } - - /** - * Deletes all states from CTP projects defined - * by the {@code CTP_SOURCE_CLIENT} and {@code CTP_TARGET_CLIENT}. - */ - public static void deleteStatesFromTargetAndSource() { - deleteStates(CTP_TARGET_CLIENT); - deleteStates(CTP_SOURCE_CLIENT); - } - - /** - * Deletes all states from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the states from. - */ - public static void deleteStates(@Nonnull final SphereClient ctpClient) { - //delete transitions - QueryExecutionUtils.queryAll(ctpClient, StateQueryBuilder - .of() - .plusPredicates(QueryPredicate.of("builtIn = false")).build()) - .thenCompose(result -> { - final List> clearStates = new ArrayList<>(); - result.stream().forEach(state -> { - if (state.getTransitions() != null && !state.getTransitions().isEmpty()) { - List> emptyUpdateActions = Collections.emptyList(); - clearStates.add(ctpClient.execute(StateUpdateCommand.of(state, emptyUpdateActions))); - } else { - clearStates.add(CompletableFuture.completedFuture(state)); - } - }); - return listOfFuturesToFutureOfList(clearStates); - }).thenAccept(clearedStates -> clearedStates.forEach(stateToRemove -> - ctpClient.execute(StateDeleteCommand.of(stateToRemove))) - ).toCompletableFuture().join(); - } - - /** - * Deletes all states with {@code stateType} from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the states from. - */ - public static void deleteStates(@Nonnull final SphereClient ctpClient, - @Nonnull final StateType stateType) { - final QueryPredicate stateQueryPredicate = - QueryPredicate.of(format("type= \"%s\"", stateType.toSphereName())); - final StateQuery stateQuery = StateQuery.of().withPredicates(stateQueryPredicate); - queryAndExecute(ctpClient, stateQuery, StateDeleteCommand::of); - } - - /** - * Creates a {@link State} with the {@link StateType} supplied in the CTP project defined by the {@code ctpClient} - * in a blocking fashion. The created state will have a key with the value {@value STATE_KEY}. - * - * @param ctpClient defines the CTP project to create the state in. - * @param stateType defines the state type to create the state with. - * @return the created state. - */ - public static State createState(@Nonnull final SphereClient ctpClient, @Nonnull final StateType stateType) { - return createState(ctpClient, STATE_KEY, stateType, null); - } - - /** - * Creates a {@link State} with the {@code stateKey} and the {@link StateType} supplied in the CTP project defined - * by the {@code ctpClient} in a blocking fashion. Optionally transition state can be provided to create the state - * with. - * - * @param ctpClient defines the CTP project to create the state in. - * @param stateKey defines the key to create the state with. - * @param stateType defines the state type to create the state with. - * @param transitionState defines the transition state to create the state with. - * @return the created state. - */ - public static State createState(@Nonnull final SphereClient ctpClient, @Nonnull final String stateKey, - @Nonnull final StateType stateType, @Nullable final State transitionState) { - final StateDraft stateDraft = StateDraftBuilder.of(stateKey, stateType) - .transitions(Optional.ofNullable(transitionState).map(state -> { - final Set> stateTransitions = new HashSet<>(); - stateTransitions.add(State.referenceOfId(state.getId())); - return stateTransitions; - }).orElse(null)) - .build(); - return executeBlocking(ctpClient.execute(StateCreateCommand.of(stateDraft))); - } - - /** - * Deletes all transitions defined in the given {@code state} from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the states from. - * @param state defines the state to remove transitions from. - */ - public static void clearTransitions(@Nonnull final SphereClient ctpClient, @Nonnull final State state) { - final Set> stateTransitions = Optional - .ofNullable(state.getTransitions()) - .orElse(new HashSet<>()); - stateTransitions.clear(); - - SetTransitions setTransitions = of(stateTransitions); - executeBlocking(ctpClient.execute(StateUpdateCommand.of(state, setTransitions))); - } - - public static Optional getStateByKey( - @Nonnull final SphereClient sphereClient, - @Nonnull final String key) { - - final StateQuery query = StateQueryBuilder - .of() - .plusPredicates(queryModel -> queryModel.key().is(key)) + public static final String STATE_KEY_1 = "key_1"; + public static final LocalizedString STATE_NAME_1 = LocalizedString.ofEnglish("name_1"); + public static final LocalizedString STATE_DESCRIPTION_1 = + LocalizedString.ofEnglish("description_1"); + private static final String STATE_KEY = "old_state_key"; + + private StateITUtils() {} + + /** + * Deletes all states from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and {@code + * CTP_TARGET_CLIENT}. + */ + public static void deleteStatesFromTargetAndSource() { + deleteStates(CTP_TARGET_CLIENT); + deleteStates(CTP_SOURCE_CLIENT); + } + + /** + * Deletes all states from the CTP project defined by the {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the states from. + */ + public static void deleteStates(@Nonnull final SphereClient ctpClient) { + // delete transitions + QueryExecutionUtils.queryAll( + ctpClient, + StateQueryBuilder.of().plusPredicates(QueryPredicate.of("builtIn = false")).build()) + .thenCompose( + result -> { + final List> clearStates = new ArrayList<>(); + result.stream() + .forEach( + state -> { + if (state.getTransitions() != null && !state.getTransitions().isEmpty()) { + List> emptyUpdateActions = + Collections.emptyList(); + clearStates.add( + ctpClient.execute(StateUpdateCommand.of(state, emptyUpdateActions))); + } else { + clearStates.add(CompletableFuture.completedFuture(state)); + } + }); + return listOfFuturesToFutureOfList(clearStates); + }) + .thenAccept( + clearedStates -> + clearedStates.forEach( + stateToRemove -> ctpClient.execute(StateDeleteCommand.of(stateToRemove)))) + .toCompletableFuture() + .join(); + } + + /** + * Deletes all states with {@code stateType} from the CTP project defined by the {@code + * ctpClient}. + * + * @param ctpClient defines the CTP project to delete the states from. + */ + public static void deleteStates( + @Nonnull final SphereClient ctpClient, @Nonnull final StateType stateType) { + final QueryPredicate stateQueryPredicate = + QueryPredicate.of(format("type= \"%s\"", stateType.toSphereName())); + final StateQuery stateQuery = StateQuery.of().withPredicates(stateQueryPredicate); + queryAndExecute(ctpClient, stateQuery, StateDeleteCommand::of); + } + + /** + * Creates a {@link State} with the {@link StateType} supplied in the CTP project defined by the + * {@code ctpClient} in a blocking fashion. The created state will have a key with the value + * {@value STATE_KEY}. + * + * @param ctpClient defines the CTP project to create the state in. + * @param stateType defines the state type to create the state with. + * @return the created state. + */ + public static State createState( + @Nonnull final SphereClient ctpClient, @Nonnull final StateType stateType) { + return createState(ctpClient, STATE_KEY, stateType, null); + } + + /** + * Creates a {@link State} with the {@code stateKey} and the {@link StateType} supplied in the CTP + * project defined by the {@code ctpClient} in a blocking fashion. Optionally transition state can + * be provided to create the state with. + * + * @param ctpClient defines the CTP project to create the state in. + * @param stateKey defines the key to create the state with. + * @param stateType defines the state type to create the state with. + * @param transitionState defines the transition state to create the state with. + * @return the created state. + */ + public static State createState( + @Nonnull final SphereClient ctpClient, + @Nonnull final String stateKey, + @Nonnull final StateType stateType, + @Nullable final State transitionState) { + final StateDraft stateDraft = + StateDraftBuilder.of(stateKey, stateType) + .transitions( + Optional.ofNullable(transitionState) + .map( + state -> { + final Set> stateTransitions = new HashSet<>(); + stateTransitions.add(State.referenceOfId(state.getId())); + return stateTransitions; + }) + .orElse(null)) .build(); - - return sphereClient.execute(query).toCompletableFuture().join().head(); - } - + return executeBlocking(ctpClient.execute(StateCreateCommand.of(stateDraft))); + } + + /** + * Deletes all transitions defined in the given {@code state} from the CTP project defined by the + * {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the states from. + * @param state defines the state to remove transitions from. + */ + public static void clearTransitions( + @Nonnull final SphereClient ctpClient, @Nonnull final State state) { + final Set> stateTransitions = + Optional.ofNullable(state.getTransitions()).orElse(new HashSet<>()); + stateTransitions.clear(); + + SetTransitions setTransitions = of(stateTransitions); + executeBlocking(ctpClient.execute(StateUpdateCommand.of(state, setTransitions))); + } + + public static Optional getStateByKey( + @Nonnull final SphereClient sphereClient, @Nonnull final String key) { + + final StateQuery query = + StateQueryBuilder.of().plusPredicates(queryModel -> queryModel.key().is(key)).build(); + + return sphereClient.execute(query).toCompletableFuture().join().head(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/StoreITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/StoreITUtils.java index 76976d97c2..f3af4e2e49 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/StoreITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/StoreITUtils.java @@ -1,5 +1,10 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; + import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.stores.Store; import io.sphere.sdk.stores.StoreDraft; @@ -7,45 +12,40 @@ import io.sphere.sdk.stores.commands.StoreCreateCommand; import io.sphere.sdk.stores.commands.StoreDeleteCommand; import io.sphere.sdk.stores.queries.StoreQuery; - import javax.annotation.Nonnull; -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; - public final class StoreITUtils { - /** - * Deletes all stores from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and - * {@code CTP_TARGET_CLIENT}. - */ - public static void deleteStoresFromTargetAndSource() { - deleteStores(CTP_TARGET_CLIENT); - deleteStores(CTP_SOURCE_CLIENT); - } + /** + * Deletes all stores from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and {@code + * CTP_TARGET_CLIENT}. + */ + public static void deleteStoresFromTargetAndSource() { + deleteStores(CTP_TARGET_CLIENT); + deleteStores(CTP_SOURCE_CLIENT); + } - /** - * Deletes all stores from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the stores from. - */ - public static void deleteStores(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, StoreQuery.of(), StoreDeleteCommand::of); - } + /** + * Deletes all stores from the CTP project defined by the {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the stores from. + */ + public static void deleteStores(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, StoreQuery.of(), StoreDeleteCommand::of); + } - /** - * Creates a {@link Store} in the CTP project defined by the {@code ctpClient} in a blocking fashion. - * - * @param ctpClient defines the CTP project to create the Store in. - * @param key the key of the Store to create. - * @return the created store. - */ - public static Store createStore(@Nonnull final SphereClient ctpClient, @Nonnull final String key) { - final StoreDraft storeDraft = StoreDraftBuilder.of(key).build(); - return executeBlocking(ctpClient.execute(StoreCreateCommand.of(storeDraft))); - } + /** + * Creates a {@link Store} in the CTP project defined by the {@code ctpClient} in a blocking + * fashion. + * + * @param ctpClient defines the CTP project to create the Store in. + * @param key the key of the Store to create. + * @return the created store. + */ + public static Store createStore( + @Nonnull final SphereClient ctpClient, @Nonnull final String key) { + final StoreDraft storeDraft = StoreDraftBuilder.of(key).build(); + return executeBlocking(ctpClient.execute(StoreCreateCommand.of(storeDraft))); + } - private StoreITUtils() { - } + private StoreITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/TaxCategoryITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/TaxCategoryITUtils.java index 5cd711b8a7..03ebd68fc2 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/TaxCategoryITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/TaxCategoryITUtils.java @@ -1,5 +1,11 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.util.Collections.singletonList; + import com.neovisionaries.i18n.CountryCode; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.taxcategories.TaxCategory; @@ -11,85 +17,75 @@ import io.sphere.sdk.taxcategories.commands.TaxCategoryDeleteCommand; import io.sphere.sdk.taxcategories.queries.TaxCategoryQuery; import io.sphere.sdk.taxcategories.queries.TaxCategoryQueryBuilder; - -import javax.annotation.Nonnull; - import java.util.Optional; - -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.util.Collections.singletonList; +import javax.annotation.Nonnull; public final class TaxCategoryITUtils { - public static final String TAXCATEGORY_KEY = "old_tax_category_key"; - private static final String TAXCATEGORY_NAME = "old_tax_category_name"; - private static final String TAXCATEGORY_DESCRIPTION = "old_tax_category_desc"; - private static final String TAXCATEGORY_TAXRATE_NAME = "old_tax_rate_name"; - private static final double TAXCATEGORY_TAXRATE_AMOUNT = 0.2; + public static final String TAXCATEGORY_KEY = "old_tax_category_key"; + private static final String TAXCATEGORY_NAME = "old_tax_category_name"; + private static final String TAXCATEGORY_DESCRIPTION = "old_tax_category_desc"; + private static final String TAXCATEGORY_TAXRATE_NAME = "old_tax_rate_name"; + private static final double TAXCATEGORY_TAXRATE_AMOUNT = 0.2; - public static final String TAXCATEGORY_KEY_1 = "key_1"; - public static final String TAXCATEGORY_NAME_1 = "name_1"; - public static final String TAXCATEGORY_DESCRIPTION_1 = "description_1"; + public static final String TAXCATEGORY_KEY_1 = "key_1"; + public static final String TAXCATEGORY_NAME_1 = "name_1"; + public static final String TAXCATEGORY_DESCRIPTION_1 = "description_1"; - /** - * Deletes all Tax categories from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and - * {@code CTP_TARGET_CLIENT}. - */ - public static void deleteTaxCategoriesFromTargetAndSource() { - deleteTaxCategories(CTP_TARGET_CLIENT); - deleteTaxCategories(CTP_SOURCE_CLIENT); - } + /** + * Deletes all Tax categories from CTP projects defined by the {@code CTP_SOURCE_CLIENT} and + * {@code CTP_TARGET_CLIENT}. + */ + public static void deleteTaxCategoriesFromTargetAndSource() { + deleteTaxCategories(CTP_TARGET_CLIENT); + deleteTaxCategories(CTP_SOURCE_CLIENT); + } - /** - * Deletes all tax categories from the CTP project defined by the {@code ctpClient}. - * - * @param ctpClient defines the CTP project to delete the tax categories from. - */ - public static void deleteTaxCategories(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, TaxCategoryQuery.of(), TaxCategoryDeleteCommand::of); - } + /** + * Deletes all tax categories from the CTP project defined by the {@code ctpClient}. + * + * @param ctpClient defines the CTP project to delete the tax categories from. + */ + public static void deleteTaxCategories(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, TaxCategoryQuery.of(), TaxCategoryDeleteCommand::of); + } - /** - * Creates a {@link TaxCategory} in the CTP project defined by the {@code ctpClient} in a blocking fashion. - * The created tax category will have a key with the value {@value TAXCATEGORY_KEY}, a name with the value - * {@value TAXCATEGORY_NAME}, a description with the value {@value TAXCATEGORY_DESCRIPTION} and a tax rate with the - * name {@value TAXCATEGORY_TAXRATE_NAME} and amount {@value TAXCATEGORY_TAXRATE_AMOUNT}. - * - * @param ctpClient defines the CTP project to create the tax category in. - * @return the created tax category. - */ - public static TaxCategory createTaxCategory(@Nonnull final SphereClient ctpClient) { - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of(TAXCATEGORY_NAME, singletonList(createTaxRateDraft()), TAXCATEGORY_DESCRIPTION) + /** + * Creates a {@link TaxCategory} in the CTP project defined by the {@code ctpClient} in a blocking + * fashion. The created tax category will have a key with the value {@value TAXCATEGORY_KEY}, a + * name with the value {@value TAXCATEGORY_NAME}, a description with the value {@value + * TAXCATEGORY_DESCRIPTION} and a tax rate with the name {@value TAXCATEGORY_TAXRATE_NAME} and + * amount {@value TAXCATEGORY_TAXRATE_AMOUNT}. + * + * @param ctpClient defines the CTP project to create the tax category in. + * @return the created tax category. + */ + public static TaxCategory createTaxCategory(@Nonnull final SphereClient ctpClient) { + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + TAXCATEGORY_NAME, singletonList(createTaxRateDraft()), TAXCATEGORY_DESCRIPTION) .key(TAXCATEGORY_KEY) .build(); - return executeBlocking(ctpClient.execute(TaxCategoryCreateCommand.of(taxCategoryDraft))); - } + return executeBlocking(ctpClient.execute(TaxCategoryCreateCommand.of(taxCategoryDraft))); + } - /** - * Creates a {@link TaxRateDraft} with the name {@value TAXCATEGORY_TAXRATE_NAME} - * and amount {@value TAXCATEGORY_TAXRATE_AMOUNT}. - * - * @return the created tax rate draft. - */ - public static TaxRateDraft createTaxRateDraft() { - return TaxRateDraftBuilder.of(TAXCATEGORY_TAXRATE_NAME, TAXCATEGORY_TAXRATE_AMOUNT, true, CountryCode.DE) - .build(); - } - - public static Optional getTaxCategoryByKey( - @Nonnull final SphereClient sphereClient, - @Nonnull final String key) { - final TaxCategoryQuery query = TaxCategoryQueryBuilder - .of() - .plusPredicates(queryModel -> queryModel.key().is(key)) - .build(); - return sphereClient.execute(query).toCompletableFuture().join().head(); - } + /** + * Creates a {@link TaxRateDraft} with the name {@value TAXCATEGORY_TAXRATE_NAME} and amount + * {@value TAXCATEGORY_TAXRATE_AMOUNT}. + * + * @return the created tax rate draft. + */ + public static TaxRateDraft createTaxRateDraft() { + return TaxRateDraftBuilder.of( + TAXCATEGORY_TAXRATE_NAME, TAXCATEGORY_TAXRATE_AMOUNT, true, CountryCode.DE) + .build(); + } - private TaxCategoryITUtils() { - } + public static Optional getTaxCategoryByKey( + @Nonnull final SphereClient sphereClient, @Nonnull final String key) { + final TaxCategoryQuery query = + TaxCategoryQueryBuilder.of().plusPredicates(queryModel -> queryModel.key().is(key)).build(); + return sphereClient.execute(query).toCompletableFuture().join().head(); + } + private TaxCategoryITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/TypeITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/TypeITUtils.java index fbe88d4d0e..f972ea44c8 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/commons/utils/TypeITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/commons/utils/TypeITUtils.java @@ -1,5 +1,10 @@ package com.commercetools.sync.integration.commons.utils; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static java.util.Collections.singletonList; + import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.TextInputHint; @@ -13,126 +18,119 @@ import io.sphere.sdk.types.commands.TypeDeleteCommand; import io.sphere.sdk.types.queries.TypeQuery; import io.sphere.sdk.types.queries.TypeQueryBuilder; - -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.Optional; - -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static java.util.Collections.singletonList; +import javax.annotation.Nonnull; public final class TypeITUtils { - public static final String TYPE_KEY_1 = "key_1"; - public static final String TYPE_KEY_2 = "key_2"; - - public static final LocalizedString TYPE_NAME_1 = LocalizedString.ofEnglish("name_1"); - public static final LocalizedString TYPE_NAME_2 = LocalizedString.ofEnglish("name_2"); - - public static final String FIELD_DEFINITION_NAME_1 = "field_name_1"; - private static final String FIELD_DEFINITION_NAME_2 = "field_name_2"; - private static final String FIELD_DEFINITION_NAME_3 = "field_name_3"; - - public static final LocalizedString FIELD_DEFINITION_LABEL_1 = LocalizedString.ofEnglish("label_1"); - private static final LocalizedString FIELD_DEFINITION_LABEL_2 = LocalizedString.ofEnglish("label_2"); - private static final LocalizedString FIELD_DEFINITION_LABEL_3 = LocalizedString.ofEnglish("label_3"); - - public static final LocalizedString TYPE_DESCRIPTION_1 = LocalizedString.ofEnglish("description_1"); - public static final LocalizedString TYPE_DESCRIPTION_2 = LocalizedString.ofEnglish("description_2"); - - public static final FieldDefinition FIELD_DEFINITION_1 = FieldDefinition.of( - StringFieldType.of(), - FIELD_DEFINITION_NAME_1, - FIELD_DEFINITION_LABEL_1, - true, - TextInputHint.SINGLE_LINE); - public static final FieldDefinition FIELD_DEFINITION_2 = FieldDefinition.of( - StringFieldType.of(), - FIELD_DEFINITION_NAME_2, - FIELD_DEFINITION_LABEL_2, - true, - TextInputHint.SINGLE_LINE); - public static final FieldDefinition FIELD_DEFINITION_3 = FieldDefinition.of( - StringFieldType.of(), - FIELD_DEFINITION_NAME_3, - FIELD_DEFINITION_LABEL_3, - true, - TextInputHint.SINGLE_LINE); - - private static final TypeDraft typeDraft1 = TypeDraftBuilder - .of(TYPE_KEY_1, - TYPE_NAME_1, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(Arrays.asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2)) - .build(); - private static final TypeDraft typeDraft2 = TypeDraftBuilder - .of(TYPE_KEY_2, - TYPE_NAME_2, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_2) - .fieldDefinitions(singletonList(FIELD_DEFINITION_2)) - .build(); - - /** - * Deletes all types from CTP project, represented by provided {@code ctpClient}. - * - * @param ctpClient represents the CTP project the types will be deleted from. - */ - public static void deleteTypes(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, TypeQuery.of(), TypeDeleteCommand::of); - } - - /** - * Deletes all types from CTP projects defined by {@code CTP_SOURCE_CLIENT} and - * {@code CTP_TARGET_CLIENT}. - */ - public static void deleteTypesFromTargetAndSource() { - deleteTypes(CTP_SOURCE_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - } - - /** - * Populate source CTP project. - * Creates type with key TYPE_KEY_1, TYPE_NAME_1, TYPE_DESCRIPTION_1 and - * fields FIELD_DEFINITION_1, FIELD_DEFINITION_2. - * Creates type with key TYPE_KEY_2, TYPE_NAME_2, TYPE_DESCRIPTION_2 and - * fields FIELD_DEFINITION_1. - */ - public static void populateSourceProject() { - CTP_SOURCE_CLIENT.execute(TypeCreateCommand.of(typeDraft1)).toCompletableFuture().join(); - CTP_SOURCE_CLIENT.execute(TypeCreateCommand.of(typeDraft2)).toCompletableFuture().join(); - } - - /** - * Populate source CTP project. - * Creates type with key TYPE_KEY_1, TYPE_NAME_1, TYPE_DESCRIPTION_1 and - * fields FIELD_DEFINITION_1, FIELD_DEFINITION_2. - */ - public static void populateTargetProject() { - CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(typeDraft1)).toCompletableFuture().join(); - } - - /** - * Tries to fetch type of {@code key} using {@code sphereClient}. - * - * @param sphereClient sphere client used to execute requests. - * @param key key of requested type. - * @return {@link Optional} which may contain type of {@code key}. - */ - public static Optional getTypeByKey( - @Nonnull final SphereClient sphereClient, - @Nonnull final String key) { - - final TypeQuery query = TypeQueryBuilder - .of() - .plusPredicates(queryModel -> queryModel.key().is(key)) - .build(); - - return sphereClient.execute(query).toCompletableFuture().join().head(); - } - - private TypeITUtils() { - } + public static final String TYPE_KEY_1 = "key_1"; + public static final String TYPE_KEY_2 = "key_2"; + + public static final LocalizedString TYPE_NAME_1 = LocalizedString.ofEnglish("name_1"); + public static final LocalizedString TYPE_NAME_2 = LocalizedString.ofEnglish("name_2"); + + public static final String FIELD_DEFINITION_NAME_1 = "field_name_1"; + private static final String FIELD_DEFINITION_NAME_2 = "field_name_2"; + private static final String FIELD_DEFINITION_NAME_3 = "field_name_3"; + + public static final LocalizedString FIELD_DEFINITION_LABEL_1 = + LocalizedString.ofEnglish("label_1"); + private static final LocalizedString FIELD_DEFINITION_LABEL_2 = + LocalizedString.ofEnglish("label_2"); + private static final LocalizedString FIELD_DEFINITION_LABEL_3 = + LocalizedString.ofEnglish("label_3"); + + public static final LocalizedString TYPE_DESCRIPTION_1 = + LocalizedString.ofEnglish("description_1"); + public static final LocalizedString TYPE_DESCRIPTION_2 = + LocalizedString.ofEnglish("description_2"); + + public static final FieldDefinition FIELD_DEFINITION_1 = + FieldDefinition.of( + StringFieldType.of(), + FIELD_DEFINITION_NAME_1, + FIELD_DEFINITION_LABEL_1, + true, + TextInputHint.SINGLE_LINE); + public static final FieldDefinition FIELD_DEFINITION_2 = + FieldDefinition.of( + StringFieldType.of(), + FIELD_DEFINITION_NAME_2, + FIELD_DEFINITION_LABEL_2, + true, + TextInputHint.SINGLE_LINE); + public static final FieldDefinition FIELD_DEFINITION_3 = + FieldDefinition.of( + StringFieldType.of(), + FIELD_DEFINITION_NAME_3, + FIELD_DEFINITION_LABEL_3, + true, + TextInputHint.SINGLE_LINE); + + private static final TypeDraft typeDraft1 = + TypeDraftBuilder.of( + TYPE_KEY_1, TYPE_NAME_1, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(Arrays.asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2)) + .build(); + private static final TypeDraft typeDraft2 = + TypeDraftBuilder.of( + TYPE_KEY_2, TYPE_NAME_2, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_2) + .fieldDefinitions(singletonList(FIELD_DEFINITION_2)) + .build(); + + /** + * Deletes all types from CTP project, represented by provided {@code ctpClient}. + * + * @param ctpClient represents the CTP project the types will be deleted from. + */ + public static void deleteTypes(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, TypeQuery.of(), TypeDeleteCommand::of); + } + + /** + * Deletes all types from CTP projects defined by {@code CTP_SOURCE_CLIENT} and {@code + * CTP_TARGET_CLIENT}. + */ + public static void deleteTypesFromTargetAndSource() { + deleteTypes(CTP_SOURCE_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + } + + /** + * Populate source CTP project. Creates type with key TYPE_KEY_1, TYPE_NAME_1, TYPE_DESCRIPTION_1 + * and fields FIELD_DEFINITION_1, FIELD_DEFINITION_2. Creates type with key TYPE_KEY_2, + * TYPE_NAME_2, TYPE_DESCRIPTION_2 and fields FIELD_DEFINITION_1. + */ + public static void populateSourceProject() { + CTP_SOURCE_CLIENT.execute(TypeCreateCommand.of(typeDraft1)).toCompletableFuture().join(); + CTP_SOURCE_CLIENT.execute(TypeCreateCommand.of(typeDraft2)).toCompletableFuture().join(); + } + + /** + * Populate source CTP project. Creates type with key TYPE_KEY_1, TYPE_NAME_1, TYPE_DESCRIPTION_1 + * and fields FIELD_DEFINITION_1, FIELD_DEFINITION_2. + */ + public static void populateTargetProject() { + CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(typeDraft1)).toCompletableFuture().join(); + } + + /** + * Tries to fetch type of {@code key} using {@code sphereClient}. + * + * @param sphereClient sphere client used to execute requests. + * @param key key of requested type. + * @return {@link Optional} which may contain type of {@code key}. + */ + public static Optional getTypeByKey( + @Nonnull final SphereClient sphereClient, @Nonnull final String key) { + + final TypeQuery query = + TypeQueryBuilder.of().plusPredicates(queryModel -> queryModel.key().is(key)).build(); + + return sphereClient.execute(query).toCompletableFuture().join().head(); + } + + private TypeITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/cartdiscounts/CartDiscountSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/cartdiscounts/CartDiscountSyncIT.java index 12281a5098..d06894817a 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/cartdiscounts/CartDiscountSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/cartdiscounts/CartDiscountSyncIT.java @@ -1,5 +1,19 @@ package com.commercetools.sync.integration.ctpprojectsource.cartdiscounts; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountReferenceResolutionUtils.buildCartDiscountQuery; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountReferenceResolutionUtils.mapToCartDiscountDrafts; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.createCartDiscountCustomType; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.deleteCartDiscountsFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.populateSourceProject; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.populateTargetProject; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.cartdiscounts.CartDiscountSync; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; @@ -19,146 +33,138 @@ import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; import io.sphere.sdk.utils.MoneyImpl; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; - -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountReferenceResolutionUtils.buildCartDiscountQuery; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountReferenceResolutionUtils.mapToCartDiscountDrafts; -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.createCartDiscountCustomType; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.deleteCartDiscountsFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.populateSourceProject; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.populateTargetProject; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CartDiscountSyncIT { - @BeforeEach - void setup() { - deleteCartDiscountsFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - populateSourceProject(); - populateTargetProject(); - } - - @AfterAll - static void tearDown() { - deleteCartDiscountsFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - } - - @Test - void sync_WithoutUpdates_ShouldReturnProperStatistics() { - // preparation - final List cartDiscounts = CTP_SOURCE_CLIENT + @BeforeEach + void setup() { + deleteCartDiscountsFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + populateSourceProject(); + populateTargetProject(); + } + + @AfterAll + static void tearDown() { + deleteCartDiscountsFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + } + + @Test + void sync_WithoutUpdates_ShouldReturnProperStatistics() { + // preparation + final List cartDiscounts = + CTP_SOURCE_CLIENT .execute(buildCartDiscountQuery()) - .toCompletableFuture().join().getResults(); - - final List cartDiscountDrafts = mapToCartDiscountDrafts(cartDiscounts); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + .toCompletableFuture() + .join() + .getResults(); + + final List cartDiscountDrafts = mapToCartDiscountDrafts(cartDiscounts); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync - .sync(cartDiscountDrafts) - .toCompletableFuture().join(); + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync.sync(cartDiscountDrafts).toCompletableFuture().join(); - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(cartDiscountSyncStatistics).hasValues(2, 1, 0, 0); - assertThat(cartDiscountSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 cart discounts were processed in total" + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(cartDiscountSyncStatistics).hasValues(2, 1, 0, 0); + assertThat(cartDiscountSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 cart discounts were processed in total" + " (1 created, 0 updated and 0 failed to sync)."); + } - } - - @Test - void sync_WithUpdates_ShouldReturnProperStatistics() { - // preparation - final List cartDiscounts = CTP_SOURCE_CLIENT + @Test + void sync_WithUpdates_ShouldReturnProperStatistics() { + // preparation + final List cartDiscounts = + CTP_SOURCE_CLIENT .execute(buildCartDiscountQuery()) - .toCompletableFuture().join().getResults(); - final String newTypeKey = "new-type"; - createCartDiscountCustomType(newTypeKey, Locale.ENGLISH, newTypeKey, CTP_SOURCE_CLIENT); - final Type newTargetCustomType = createCartDiscountCustomType(newTypeKey, Locale.ENGLISH, newTypeKey, - CTP_TARGET_CLIENT); - - final List cartDiscountDrafts = - mapToCartDiscountDrafts(cartDiscounts); - - // Apply some changes - final List updatedCartDiscountDrafts = cartDiscountDrafts - .stream() - .map(draft -> CartDiscountDraftBuilder - .of(draft) - .cartPredicate(CartPredicate.of("totalPrice >= \"100 EUR\"")) - .value(AbsoluteCartDiscountValue.of(MoneyImpl.of(40, EUR))) - .target(ShippingCostTarget.of()) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(newTypeKey, emptyMap())) - .build()) + .toCompletableFuture() + .join() + .getResults(); + final String newTypeKey = "new-type"; + createCartDiscountCustomType(newTypeKey, Locale.ENGLISH, newTypeKey, CTP_SOURCE_CLIENT); + final Type newTargetCustomType = + createCartDiscountCustomType(newTypeKey, Locale.ENGLISH, newTypeKey, CTP_TARGET_CLIENT); + + final List cartDiscountDrafts = mapToCartDiscountDrafts(cartDiscounts); + + // Apply some changes + final List updatedCartDiscountDrafts = + cartDiscountDrafts.stream() + .map( + draft -> + CartDiscountDraftBuilder.of(draft) + .cartPredicate(CartPredicate.of("totalPrice >= \"100 EUR\"")) + .value(AbsoluteCartDiscountValue.of(MoneyImpl.of(40, EUR))) + .target(ShippingCostTarget.of()) + .custom(CustomFieldsDraft.ofTypeKeyAndJson(newTypeKey, emptyMap())) + .build()) .collect(Collectors.toList()); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final List> updateActionsList = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .beforeUpdateCallback((updateActions, newCartDiscount, oldCartDiscount) -> { - updateActionsList.addAll(updateActions); - return updateActions; - }) + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final List> updateActionsList = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .beforeUpdateCallback( + (updateActions, newCartDiscount, oldCartDiscount) -> { + updateActionsList.addAll(updateActions); + return updateActions; + }) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync - .sync(updatedCartDiscountDrafts) - .toCompletableFuture().join(); + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync.sync(updatedCartDiscountDrafts).toCompletableFuture().join(); - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActionsList).containsExactly( - ChangeValue.of(CartDiscountValue.ofAbsolute(Collections.singletonList(MoneyImpl.of(40, EUR)))), + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActionsList) + .containsExactly( + ChangeValue.of( + CartDiscountValue.ofAbsolute(Collections.singletonList(MoneyImpl.of(40, EUR)))), ChangeCartPredicate.of("totalPrice >= \"100 EUR\""), ChangeTarget.of(ShippingCostTarget.of()), - SetCustomType.ofTypeIdAndJson(newTargetCustomType.getId(), emptyMap()) - ); - assertThat(cartDiscountSyncStatistics).hasValues(2, 1, 1, 0); - assertThat(cartDiscountSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 cart discounts were processed in total" + SetCustomType.ofTypeIdAndJson(newTargetCustomType.getId(), emptyMap())); + assertThat(cartDiscountSyncStatistics).hasValues(2, 1, 1, 0); + assertThat(cartDiscountSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 cart discounts were processed in total" + " (1 created, 1 updated and 0 failed to sync)."); - } + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/categories/CategorySyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/categories/CategorySyncIT.java index 8147175c47..dd575b7f9a 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/categories/CategorySyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/categories/CategorySyncIT.java @@ -1,29 +1,5 @@ package com.commercetools.sync.integration.ctpprojectsource.categories; -import com.commercetools.sync.categories.CategorySync; -import com.commercetools.sync.categories.CategorySyncOptions; -import com.commercetools.sync.categories.CategorySyncOptionsBuilder; -import com.commercetools.sync.categories.helpers.CategorySyncStatistics; -import io.sphere.sdk.categories.Category; -import io.sphere.sdk.categories.CategoryDraft; -import io.sphere.sdk.categories.CategoryDraftBuilder; -import io.sphere.sdk.categories.commands.CategoryCreateCommand; -import io.sphere.sdk.client.ErrorResponseException; -import io.sphere.sdk.models.LocalizedString; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.models.errors.DuplicateFieldError; -import io.sphere.sdk.types.CustomFieldsDraft; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; - import static com.commercetools.sync.categories.utils.CategoryReferenceResolutionUtils.buildCategoryQuery; import static com.commercetools.sync.categories.utils.CategoryReferenceResolutionUtils.mapToCategoryDrafts; import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; @@ -44,366 +20,439 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; -class CategorySyncIT { - private CategorySync categorySync; - - private List callBackErrorResponses = new ArrayList<>(); - private List callBackExceptions = new ArrayList<>(); - private List callBackWarningResponses = new ArrayList<>(); - - /** - * Delete all categories and types from source and target project. Then create custom types for source and target - * CTP project categories. - */ - @BeforeAll - static void setup() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteAllCategories(CTP_SOURCE_CLIENT); - deleteTypesFromTargetAndSource(); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, "anyName", CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, "anyName", CTP_SOURCE_CLIENT); - } +import com.commercetools.sync.categories.CategorySync; +import com.commercetools.sync.categories.CategorySyncOptions; +import com.commercetools.sync.categories.CategorySyncOptionsBuilder; +import com.commercetools.sync.categories.helpers.CategorySyncStatistics; +import io.sphere.sdk.categories.Category; +import io.sphere.sdk.categories.CategoryDraft; +import io.sphere.sdk.categories.CategoryDraftBuilder; +import io.sphere.sdk.categories.commands.CategoryCreateCommand; +import io.sphere.sdk.client.ErrorResponseException; +import io.sphere.sdk.models.LocalizedString; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.models.errors.DuplicateFieldError; +import io.sphere.sdk.types.CustomFieldsDraft; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; - /** - * Deletes Categories and Types from source and target CTP projects, then it populates target CTP project with - * category test data. - */ - @BeforeEach - void setupTest() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteAllCategories(CTP_SOURCE_CLIENT); - - createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); - - callBackErrorResponses = new ArrayList<>(); - callBackExceptions = new ArrayList<>(); - callBackWarningResponses = new ArrayList<>(); - - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - callBackErrorResponses.add(exception.getMessage()); - callBackExceptions.add(exception.getCause()); - }) +class CategorySyncIT { + private CategorySync categorySync; + + private List callBackErrorResponses = new ArrayList<>(); + private List callBackExceptions = new ArrayList<>(); + private List callBackWarningResponses = new ArrayList<>(); + + /** + * Delete all categories and types from source and target project. Then create custom types for + * source and target CTP project categories. + */ + @BeforeAll + static void setup() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteAllCategories(CTP_SOURCE_CLIENT); + deleteTypesFromTargetAndSource(); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, "anyName", CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, "anyName", CTP_SOURCE_CLIENT); + } + + /** + * Deletes Categories and Types from source and target CTP projects, then it populates target CTP + * project with category test data. + */ + @BeforeEach + void setupTest() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteAllCategories(CTP_SOURCE_CLIENT); + + createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); + + callBackErrorResponses = new ArrayList<>(); + callBackExceptions = new ArrayList<>(); + callBackWarningResponses = new ArrayList<>(); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + callBackErrorResponses.add(exception.getMessage()); + callBackExceptions.add(exception.getCause()); + }) .warningCallback( - (exception, oldResource, newResource) -> callBackWarningResponses.add(exception.getMessage())) + (exception, oldResource, newResource) -> + callBackWarningResponses.add(exception.getMessage())) .build(); - categorySync = new CategorySync(categorySyncOptions); + categorySync = new CategorySync(categorySyncOptions); + } + + /** Cleans up the target and source test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteAllCategories(CTP_SOURCE_CLIENT); + deleteTypesFromTargetAndSource(); + } + + @Test + void syncDrafts_withChangesOnly_ShouldUpdateCategories() { + createCategories( + CTP_SOURCE_CLIENT, getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, 2)); + + final List categories = + CTP_SOURCE_CLIENT.execute(buildCategoryQuery()).toCompletableFuture().join().getResults(); + + final List categoryDrafts = mapToCategoryDrafts(categories); + + final CategorySyncStatistics syncStatistics = + categorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(2, 0, 2, 0); + assertThat(callBackErrorResponses).isEmpty(); + assertThat(callBackExceptions).isEmpty(); + assertThat(callBackWarningResponses).isEmpty(); + } + + @Test + void syncDrafts_withNewCategories_ShouldCreateCategories() { + createCategories( + CTP_SOURCE_CLIENT, getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", null, 3)); + + final List categories = + CTP_SOURCE_CLIENT.execute(buildCategoryQuery()).toCompletableFuture().join().getResults(); + + final List categoryDrafts = mapToCategoryDrafts(categories); + + final CategorySyncStatistics syncStatistics = + categorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(3, 1, 2, 0, 0); + assertThat(callBackErrorResponses).isEmpty(); + assertThat(callBackExceptions).isEmpty(); + assertThat(callBackWarningResponses).isEmpty(); + } + + @Test + void syncDrafts_withNewShuffledBatchOfCategories_ShouldCreateCategories() { + // -----------------Test Setup------------------------------------ + // Delete all categories in target project + deleteAllCategories(CTP_TARGET_CLIENT); + + // Create a total of 130 categories in the source project + final List subFamily = createChildren(5, null, "root", CTP_SOURCE_CLIENT); + + for (final Category child : subFamily) { + final List subsubFamily = + createChildren(5, child, child.getName().get(Locale.ENGLISH), CTP_SOURCE_CLIENT); + for (final Category subChild : subsubFamily) { + createChildren(4, subChild, subChild.getName().get(Locale.ENGLISH), CTP_SOURCE_CLIENT); + } } - - /** - * Cleans up the target and source test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteAllCategories(CTP_SOURCE_CLIENT); - deleteTypesFromTargetAndSource(); + // --------------------------------------------------------------- + + // Fetch categories from source project + final List categories = + CTP_SOURCE_CLIENT.execute(buildCategoryQuery()).toCompletableFuture().join().getResults(); + + final List categoryDrafts = mapToCategoryDrafts(categories); + + // Make sure there is no hierarchical order + Collections.shuffle(categoryDrafts); + + // Simulate batches of categories where not all parent references are supplied at once. + final List> batches = batchElements(categoryDrafts, 13); + + final CategorySyncStatistics syncStatistics = + syncBatches(categorySync, batches, CompletableFuture.completedFuture(null)) + .toCompletableFuture() + .join(); + + assertThat(syncStatistics).hasValues(130, 130, 0, 0, 0); + assertThat(callBackErrorResponses).isEmpty(); + assertThat(callBackExceptions).isEmpty(); + assertThat(callBackWarningResponses).isEmpty(); + } + + @Test + void + syncDrafts_withExistingShuffledCategoriesWithChangingCategoryHierarchy_ShouldUpdateCategories() { + // -----------------Test Setup------------------------------------ + // Delete all categories in target project + deleteAllCategories(CTP_TARGET_CLIENT); + + // Create a total of 130 categories in the target project + final List subFamily = createChildren(5, null, "root", CTP_TARGET_CLIENT); + + for (final Category child : subFamily) { + final List subsubFamily = + createChildren(5, child, child.getName().get(Locale.ENGLISH), CTP_TARGET_CLIENT); + for (final Category subChild : subsubFamily) { + createChildren(4, subChild, subChild.getName().get(Locale.ENGLISH), CTP_TARGET_CLIENT); + } } - - @Test - void syncDrafts_withChangesOnly_ShouldUpdateCategories() { - createCategories(CTP_SOURCE_CLIENT, getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", - null, 2)); - - final List categories = CTP_SOURCE_CLIENT.execute(buildCategoryQuery()) - .toCompletableFuture().join().getResults(); - - final List categoryDrafts = mapToCategoryDrafts(categories); - - final CategorySyncStatistics syncStatistics = categorySync.sync(categoryDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(2, 0, 2, 0); - assertThat(callBackErrorResponses).isEmpty(); - assertThat(callBackExceptions).isEmpty(); - assertThat(callBackWarningResponses).isEmpty(); - } - - @Test - void syncDrafts_withNewCategories_ShouldCreateCategories() { - createCategories(CTP_SOURCE_CLIENT, getCategoryDraftsWithPrefix(Locale.ENGLISH, "new", - null, 3)); - - final List categories = CTP_SOURCE_CLIENT.execute(buildCategoryQuery()) - .toCompletableFuture().join().getResults(); - - final List categoryDrafts = mapToCategoryDrafts(categories); - - final CategorySyncStatistics syncStatistics = categorySync.sync(categoryDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(3, 1, 2, 0, 0); - assertThat(callBackErrorResponses).isEmpty(); - assertThat(callBackExceptions).isEmpty(); - assertThat(callBackWarningResponses).isEmpty(); - } - - @Test - void syncDrafts_withNewShuffledBatchOfCategories_ShouldCreateCategories() { - //-----------------Test Setup------------------------------------ - // Delete all categories in target project - deleteAllCategories(CTP_TARGET_CLIENT); - - // Create a total of 130 categories in the source project - final List subFamily = createChildren(5, null, "root", CTP_SOURCE_CLIENT); - - for (final Category child : subFamily) { - final List subsubFamily = - createChildren(5, child, child.getName().get(Locale.ENGLISH), CTP_SOURCE_CLIENT); - for (final Category subChild : subsubFamily) { - createChildren(4, subChild, subChild.getName().get(Locale.ENGLISH), CTP_SOURCE_CLIENT); - } - } - //--------------------------------------------------------------- - - // Fetch categories from source project - final List categories = CTP_SOURCE_CLIENT.execute(buildCategoryQuery()) - .toCompletableFuture().join().getResults(); - - final List categoryDrafts = mapToCategoryDrafts(categories); - - // Make sure there is no hierarchical order - Collections.shuffle(categoryDrafts); - - // Simulate batches of categories where not all parent references are supplied at once. - final List> batches = batchElements(categoryDrafts, 13); - - final CategorySyncStatistics syncStatistics = syncBatches(categorySync, batches, - CompletableFuture.completedFuture(null)).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(130, 130, 0, 0, 0); - assertThat(callBackErrorResponses).isEmpty(); - assertThat(callBackExceptions).isEmpty(); - assertThat(callBackWarningResponses).isEmpty(); - } - - @Test - void syncDrafts_withExistingShuffledCategoriesWithChangingCategoryHierarchy_ShouldUpdateCategories() { - //-----------------Test Setup------------------------------------ - // Delete all categories in target project - deleteAllCategories(CTP_TARGET_CLIENT); - - // Create a total of 130 categories in the target project - final List subFamily = - createChildren(5, null, "root", CTP_TARGET_CLIENT); - - for (final Category child : subFamily) { - final List subsubFamily = - createChildren(5, child, child.getName().get(Locale.ENGLISH), CTP_TARGET_CLIENT); - for (final Category subChild : subsubFamily) { - createChildren(4, subChild, subChild.getName().get(Locale.ENGLISH), CTP_TARGET_CLIENT); - } - } - //--------------------------------------------------------------- - - // Create a total of 130 categories in the source project - final List sourceSubFamily = createChildren(5, null, "root", CTP_SOURCE_CLIENT); - - for (final Category child : sourceSubFamily) { - final List subsubFamily = - createChildren(5, sourceSubFamily.get(0), - child.getName().get(Locale.ENGLISH), CTP_SOURCE_CLIENT); - for (final Category subChild : subsubFamily) { - createChildren(4, sourceSubFamily.get(0), - subChild.getName().get(Locale.ENGLISH), CTP_SOURCE_CLIENT); - } - } - //--------------------------------------------------------------- - - // Fetch categories from source project - final List categories = CTP_SOURCE_CLIENT.execute(buildCategoryQuery()) - .toCompletableFuture().join().getResults(); - - final List categoryDrafts = mapToCategoryDrafts(categories); - Collections.shuffle(categoryDrafts); - - final List> batches = batchElements(categoryDrafts, 13); - - final CategorySyncStatistics syncStatistics = syncBatches(categorySync, batches, - CompletableFuture.completedFuture(null)).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(130, 0, 120, 0, 0); - assertThat(callBackErrorResponses).isEmpty(); - assertThat(callBackExceptions).isEmpty(); - assertThat(callBackWarningResponses).isEmpty(); - } - - @Test - void syncDrafts_withExistingCategoriesThatChangeParents_ShouldUpdateCategories() { - //-----------------Test Setup------------------------------------ - // Delete all categories in target project - deleteAllCategories(CTP_TARGET_CLIENT); - - // Create a total of 3 categories in the target project (2 roots and 1 child to the first root) - final List subFamily = - createChildren(2, null, "root", CTP_TARGET_CLIENT); - - final Category firstRoot = subFamily.get(0); - createChildren(1, firstRoot, "child", CTP_TARGET_CLIENT); - - //--------------------------------------------------------------- - - // Create a total of 2 categories in the source project (2 roots and 1 child to the second root) - final List sourceSubFamily = - createChildren(2, null, "root", CTP_SOURCE_CLIENT); - - final Category secondRoot = sourceSubFamily.get(1); - createChildren(1, secondRoot, "child", CTP_SOURCE_CLIENT); - //--------------------------------------------------------------- - - // Fetch categories from source project - final List categories = CTP_SOURCE_CLIENT.execute(buildCategoryQuery()) - .toCompletableFuture().join().getResults(); - - final List categoryDrafts = mapToCategoryDrafts(categories); - Collections.shuffle(categoryDrafts); - - final List> batches = batchElements(categoryDrafts, 1); - - final CategorySyncStatistics syncStatistics = syncBatches(categorySync, batches, - CompletableFuture.completedFuture(null)).toCompletableFuture().join(); - - - assertThat(syncStatistics).hasValues(3, 0, 1, 0, 0); - assertThat(callBackErrorResponses).isEmpty(); - assertThat(callBackExceptions).isEmpty(); - assertThat(callBackWarningResponses).isEmpty(); + // --------------------------------------------------------------- + + // Create a total of 130 categories in the source project + final List sourceSubFamily = createChildren(5, null, "root", CTP_SOURCE_CLIENT); + + for (final Category child : sourceSubFamily) { + final List subsubFamily = + createChildren( + 5, sourceSubFamily.get(0), child.getName().get(Locale.ENGLISH), CTP_SOURCE_CLIENT); + for (final Category subChild : subsubFamily) { + createChildren( + 4, sourceSubFamily.get(0), subChild.getName().get(Locale.ENGLISH), CTP_SOURCE_CLIENT); + } } - - @Test - void syncDrafts_withANonExistingNewParent_ShouldUpdateCategories() { - //-----------------Test Setup------------------------------------ - // Delete all categories in target project - deleteAllCategories(CTP_TARGET_CLIENT); - String parentKey = "parent"; - // Create a total of 2 categories in the target project. - final CategoryDraft parentDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "parent"), + // --------------------------------------------------------------- + + // Fetch categories from source project + final List categories = + CTP_SOURCE_CLIENT.execute(buildCategoryQuery()).toCompletableFuture().join().getResults(); + + final List categoryDrafts = mapToCategoryDrafts(categories); + Collections.shuffle(categoryDrafts); + + final List> batches = batchElements(categoryDrafts, 13); + + final CategorySyncStatistics syncStatistics = + syncBatches(categorySync, batches, CompletableFuture.completedFuture(null)) + .toCompletableFuture() + .join(); + + assertThat(syncStatistics).hasValues(130, 0, 120, 0, 0); + assertThat(callBackErrorResponses).isEmpty(); + assertThat(callBackExceptions).isEmpty(); + assertThat(callBackWarningResponses).isEmpty(); + } + + @Test + void syncDrafts_withExistingCategoriesThatChangeParents_ShouldUpdateCategories() { + // -----------------Test Setup------------------------------------ + // Delete all categories in target project + deleteAllCategories(CTP_TARGET_CLIENT); + + // Create a total of 3 categories in the target project (2 roots and 1 child to the first root) + final List subFamily = createChildren(2, null, "root", CTP_TARGET_CLIENT); + + final Category firstRoot = subFamily.get(0); + createChildren(1, firstRoot, "child", CTP_TARGET_CLIENT); + + // --------------------------------------------------------------- + + // Create a total of 2 categories in the source project (2 roots and 1 child to the second root) + final List sourceSubFamily = createChildren(2, null, "root", CTP_SOURCE_CLIENT); + + final Category secondRoot = sourceSubFamily.get(1); + createChildren(1, secondRoot, "child", CTP_SOURCE_CLIENT); + // --------------------------------------------------------------- + + // Fetch categories from source project + final List categories = + CTP_SOURCE_CLIENT.execute(buildCategoryQuery()).toCompletableFuture().join().getResults(); + + final List categoryDrafts = mapToCategoryDrafts(categories); + Collections.shuffle(categoryDrafts); + + final List> batches = batchElements(categoryDrafts, 1); + + final CategorySyncStatistics syncStatistics = + syncBatches(categorySync, batches, CompletableFuture.completedFuture(null)) + .toCompletableFuture() + .join(); + + assertThat(syncStatistics).hasValues(3, 0, 1, 0, 0); + assertThat(callBackErrorResponses).isEmpty(); + assertThat(callBackExceptions).isEmpty(); + assertThat(callBackWarningResponses).isEmpty(); + } + + @Test + void syncDrafts_withANonExistingNewParent_ShouldUpdateCategories() { + // -----------------Test Setup------------------------------------ + // Delete all categories in target project + deleteAllCategories(CTP_TARGET_CLIENT); + String parentKey = "parent"; + // Create a total of 2 categories in the target project. + final CategoryDraft parentDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "parent"), LocalizedString.of(Locale.ENGLISH, "parent")) .key(parentKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(parentDraft)).toCompletableFuture().join(); + CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(parentDraft)).toCompletableFuture().join(); - final CategoryDraft childDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "child"), + final CategoryDraft childDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "child"), LocalizedString.of(Locale.ENGLISH, "child")) .key("child") .parent(ResourceIdentifier.ofKey(parentKey)) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(childDraft)).toCompletableFuture().join(); - //------------------------------------------------------------------------------------------------------------ - // Create a total of 2 categories in the source project - String newParentKey = "new-parent"; - final CategoryDraft sourceParentDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "new-parent"), + CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(childDraft)).toCompletableFuture().join(); + // ------------------------------------------------------------------------------------------------------------ + // Create a total of 2 categories in the source project + String newParentKey = "new-parent"; + final CategoryDraft sourceParentDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "new-parent"), LocalizedString.of(Locale.ENGLISH, "new-parent")) .key(newParentKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - CTP_SOURCE_CLIENT.execute(CategoryCreateCommand.of(sourceParentDraft)).toCompletableFuture().join(); - - final CategoryDraft sourceChildDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "child-new-name"), + CTP_SOURCE_CLIENT + .execute(CategoryCreateCommand.of(sourceParentDraft)) + .toCompletableFuture() + .join(); + + final CategoryDraft sourceChildDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "child-new-name"), LocalizedString.of(Locale.ENGLISH, "child")) .key("child") .parent(ResourceIdentifier.ofKey(newParentKey)) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - CTP_SOURCE_CLIENT.execute(CategoryCreateCommand.of(sourceChildDraft)).toCompletableFuture().join(); - //--------------------------------------------------------------- - - // Fetch categories from source project - final List categories = CTP_SOURCE_CLIENT + CTP_SOURCE_CLIENT + .execute(CategoryCreateCommand.of(sourceChildDraft)) + .toCompletableFuture() + .join(); + // --------------------------------------------------------------- + + // Fetch categories from source project + final List categories = + CTP_SOURCE_CLIENT .execute(buildCategoryQuery().withSort(sorting -> sorting.createdAt().sort().asc())) - .toCompletableFuture().join().getResults(); - - final List categoryDrafts = mapToCategoryDrafts(categories); - - // To simulate the new parent coming in a later draft - Collections.reverse(categoryDrafts); - - final List> batches = batchElements(categoryDrafts, 1); - - final CategorySyncStatistics syncStatistics = syncBatches(categorySync, batches, - CompletableFuture.completedFuture(null)).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(2, 1, 1, 0, 0); - assertThat(callBackErrorResponses).isEmpty(); - assertThat(callBackExceptions).isEmpty(); - assertThat(callBackWarningResponses).isEmpty(); - } - - @Test - void syncDrafts_fromCategoriesWithoutKeys_ShouldNotUpdateCategories() { - final CategoryDraft oldCategoryDraft1 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat1"), LocalizedString.of(Locale.ENGLISH, "furniture1")) + .toCompletableFuture() + .join() + .getResults(); + + final List categoryDrafts = mapToCategoryDrafts(categories); + + // To simulate the new parent coming in a later draft + Collections.reverse(categoryDrafts); + + final List> batches = batchElements(categoryDrafts, 1); + + final CategorySyncStatistics syncStatistics = + syncBatches(categorySync, batches, CompletableFuture.completedFuture(null)) + .toCompletableFuture() + .join(); + + assertThat(syncStatistics).hasValues(2, 1, 1, 0, 0); + assertThat(callBackErrorResponses).isEmpty(); + assertThat(callBackExceptions).isEmpty(); + assertThat(callBackWarningResponses).isEmpty(); + } + + @Test + void syncDrafts_fromCategoriesWithoutKeys_ShouldNotUpdateCategories() { + final CategoryDraft oldCategoryDraft1 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat1"), + LocalizedString.of(Locale.ENGLISH, "furniture1")) .custom(getCustomFieldsDraft()) .key("newKey1") .build(); - final CategoryDraft oldCategoryDraft2 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat2"), LocalizedString.of(Locale.ENGLISH, "furniture2")) + final CategoryDraft oldCategoryDraft2 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat2"), + LocalizedString.of(Locale.ENGLISH, "furniture2")) .custom(getCustomFieldsDraft()) .key("newKey2") .build(); - // Create two categories in the source with Keys. - List> futureCreations = new ArrayList<>(); - futureCreations.add(CTP_SOURCE_CLIENT.execute(CategoryCreateCommand.of(oldCategoryDraft1)) - .toCompletableFuture()); - futureCreations.add(CTP_SOURCE_CLIENT.execute(CategoryCreateCommand.of(oldCategoryDraft2)) - .toCompletableFuture()); - CompletableFuture.allOf(futureCreations.toArray(new CompletableFuture[futureCreations.size()])).join(); - - // Create two categories in the target without Keys. - futureCreations = new ArrayList<>(); - final CategoryDraft newCategoryDraft1 = CategoryDraftBuilder.of(oldCategoryDraft1).key(null).build(); - final CategoryDraft newCategoryDraft2 = CategoryDraftBuilder.of(oldCategoryDraft2).key(null).build(); - futureCreations.add(CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(newCategoryDraft1)) - .toCompletableFuture()); - futureCreations.add(CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(newCategoryDraft2)) - .toCompletableFuture()); - - CompletableFuture.allOf(futureCreations.toArray(new CompletableFuture[futureCreations.size()])).join(); - - //--------- - - final List categories = CTP_SOURCE_CLIENT.execute(buildCategoryQuery()) - .toCompletableFuture().join().getResults(); - - final List categoryDrafts = mapToCategoryDrafts(categories); - - final CategorySyncStatistics syncStatistics = categorySync.sync(categoryDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(2, 0, 0, 2, 0); - - assertThat(callBackErrorResponses) - .hasSize(2) - .allSatisfy(errorMessage -> { - assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); - assertThat(errorMessage).contains("\"field\" : \"slug.en\""); + // Create two categories in the source with Keys. + List> futureCreations = new ArrayList<>(); + futureCreations.add( + CTP_SOURCE_CLIENT + .execute(CategoryCreateCommand.of(oldCategoryDraft1)) + .toCompletableFuture()); + futureCreations.add( + CTP_SOURCE_CLIENT + .execute(CategoryCreateCommand.of(oldCategoryDraft2)) + .toCompletableFuture()); + CompletableFuture.allOf(futureCreations.toArray(new CompletableFuture[futureCreations.size()])) + .join(); + + // Create two categories in the target without Keys. + futureCreations = new ArrayList<>(); + final CategoryDraft newCategoryDraft1 = + CategoryDraftBuilder.of(oldCategoryDraft1).key(null).build(); + final CategoryDraft newCategoryDraft2 = + CategoryDraftBuilder.of(oldCategoryDraft2).key(null).build(); + futureCreations.add( + CTP_TARGET_CLIENT + .execute(CategoryCreateCommand.of(newCategoryDraft1)) + .toCompletableFuture()); + futureCreations.add( + CTP_TARGET_CLIENT + .execute(CategoryCreateCommand.of(newCategoryDraft2)) + .toCompletableFuture()); + + CompletableFuture.allOf(futureCreations.toArray(new CompletableFuture[futureCreations.size()])) + .join(); + + // --------- + + final List categories = + CTP_SOURCE_CLIENT.execute(buildCategoryQuery()).toCompletableFuture().join().getResults(); + + final List categoryDrafts = mapToCategoryDrafts(categories); + + final CategorySyncStatistics syncStatistics = + categorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(2, 0, 0, 2, 0); + + assertThat(callBackErrorResponses) + .hasSize(2) + .allSatisfy( + errorMessage -> { + assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); + assertThat(errorMessage).contains("\"field\" : \"slug.en\""); }); - assertThat(callBackExceptions) - .hasSize(2) - .allSatisfy(exception -> { - assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponse = ((ErrorResponseException)exception); - - final List fieldErrors = errorResponse - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - assertThat(fieldErrors).allSatisfy(error -> assertThat(error.getField()).isEqualTo("slug.en")); + assertThat(callBackExceptions) + .hasSize(2) + .allSatisfy( + exception -> { + assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponse = ((ErrorResponseException) exception); + + final List fieldErrors = + errorResponse.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + assertThat(fieldErrors) + .allSatisfy(error -> assertThat(error.getField()).isEqualTo("slug.en")); }); - assertThat(callBackWarningResponses).isEmpty(); - } + assertThat(callBackWarningResponses).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/customers/CustomerSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/customers/CustomerSyncIT.java index 610ea3f204..c40a4c9112 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/customers/CustomerSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/customers/CustomerSyncIT.java @@ -1,5 +1,18 @@ package com.commercetools.sync.integration.ctpprojectsource.customers; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.customers.utils.CustomerReferenceResolutionUtils.buildCustomerQuery; +import static com.commercetools.sync.customers.utils.CustomerReferenceResolutionUtils.mapToCustomerDrafts; +import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.createSampleCustomerJaneDoe; +import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.createSampleCustomerJohnDoe; +import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.deleteCustomerSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.StoreITUtils.createStore; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics; import com.commercetools.sync.customers.CustomerSync; import com.commercetools.sync.customers.CustomerSyncOptions; @@ -13,135 +26,115 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.stores.Store; import io.sphere.sdk.types.CustomFieldsDraft; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.customers.utils.CustomerReferenceResolutionUtils.buildCustomerQuery; -import static com.commercetools.sync.customers.utils.CustomerReferenceResolutionUtils.mapToCustomerDrafts; -import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.createSampleCustomerJaneDoe; -import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.createSampleCustomerJohnDoe; -import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.deleteCustomerSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.StoreITUtils.createStore; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomerSyncIT { - private List errorMessages; - private List exceptions; - private CustomerSync customerSync; - - @BeforeEach - void setup() { - deleteCustomerSyncTestDataFromProjects(); - - createSampleCustomerJohnDoe(CTP_SOURCE_CLIENT); - createSampleCustomerJaneDoe(CTP_SOURCE_CLIENT); - - createSampleCustomerJohnDoe(CTP_TARGET_CLIENT); - - setUpCustomerSync(); - } - - @AfterAll - static void tearDown() { - deleteCustomerSyncTestDataFromProjects(); - } - - private static void deleteCustomerSyncTestDataFromProjects() { - deleteCustomerSyncTestData(CTP_SOURCE_CLIENT); - deleteCustomerSyncTestData(CTP_TARGET_CLIENT); - } - - private void setUpCustomerSync() { - errorMessages = new ArrayList<>(); - exceptions = new ArrayList<>(); - final CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + private List errorMessages; + private List exceptions; + private CustomerSync customerSync; + + @BeforeEach + void setup() { + deleteCustomerSyncTestDataFromProjects(); + + createSampleCustomerJohnDoe(CTP_SOURCE_CLIENT); + createSampleCustomerJaneDoe(CTP_SOURCE_CLIENT); + + createSampleCustomerJohnDoe(CTP_TARGET_CLIENT); + + setUpCustomerSync(); + } + + @AfterAll + static void tearDown() { + deleteCustomerSyncTestDataFromProjects(); + } + + private static void deleteCustomerSyncTestDataFromProjects() { + deleteCustomerSyncTestData(CTP_SOURCE_CLIENT); + deleteCustomerSyncTestData(CTP_TARGET_CLIENT); + } + + private void setUpCustomerSync() { + errorMessages = new ArrayList<>(); + exceptions = new ArrayList<>(); + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - customerSync = new CustomerSync(customerSyncOptions); - } - - @Test - void sync_WithoutUpdates_ShouldReturnProperStatistics() { - - final List customers = CTP_SOURCE_CLIENT - .execute(buildCustomerQuery()) - .toCompletableFuture() - .join() - .getResults(); - - final List customerDrafts = mapToCustomerDrafts(customers); - - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(customerDrafts) - .toCompletableFuture() - .join(); - - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - - assertThat(customerSyncStatistics).hasValues(2, 1, 0, 0); - assertThat(customerSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 customers were processed in total (1 created, 0 updated and 0 failed to sync)."); - } - - @Test - void sync_WithUpdates_ShouldReturnProperStatistics() { - - final List customers = CTP_SOURCE_CLIENT - .execute(buildCustomerQuery()) - .toCompletableFuture() - .join() - .getResults(); - - final List updatedCustomerDrafts = prepareUpdatedCustomerDrafts(customers); - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(updatedCustomerDrafts) - .toCompletableFuture() - .join(); - - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(2, 1, 1, 0); - assertThat(customerSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 customers were processed in total (1 created, 1 updated and 0 failed to sync)."); - } - - private List prepareUpdatedCustomerDrafts(@Nonnull final List customers) { - - final Store storeCologne = createStore(CTP_TARGET_CLIENT, "store-cologne"); - - return mapToCustomerDrafts(customers) - .stream() - .map(customerDraft -> - CustomerDraftBuilder - .of(customerDraft) + customerSync = new CustomerSync(customerSyncOptions); + } + + @Test + void sync_WithoutUpdates_ShouldReturnProperStatistics() { + + final List customers = + CTP_SOURCE_CLIENT.execute(buildCustomerQuery()).toCompletableFuture().join().getResults(); + + final List customerDrafts = mapToCustomerDrafts(customers); + + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(customerDrafts).toCompletableFuture().join(); + + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + + assertThat(customerSyncStatistics).hasValues(2, 1, 0, 0); + assertThat(customerSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 customers were processed in total (1 created, 0 updated and 0 failed to sync)."); + } + + @Test + void sync_WithUpdates_ShouldReturnProperStatistics() { + + final List customers = + CTP_SOURCE_CLIENT.execute(buildCustomerQuery()).toCompletableFuture().join().getResults(); + + final List updatedCustomerDrafts = prepareUpdatedCustomerDrafts(customers); + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(updatedCustomerDrafts).toCompletableFuture().join(); + + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(2, 1, 1, 0); + assertThat(customerSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 customers were processed in total (1 created, 1 updated and 0 failed to sync)."); + } + + private List prepareUpdatedCustomerDrafts( + @Nonnull final List customers) { + + final Store storeCologne = createStore(CTP_TARGET_CLIENT, "store-cologne"); + + return mapToCustomerDrafts(customers).stream() + .map( + customerDraft -> + CustomerDraftBuilder.of(customerDraft) .plusStores(ResourceIdentifier.ofKey(storeCologne.getKey())) - .custom(CustomFieldsDraft.ofTypeKeyAndJson("customer-type-gold", - createCustomFieldsJsonMap())) - .addresses(singletonList(Address.of(CountryCode.DE).withCity("cologne").withKey("address1"))) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + "customer-type-gold", createCustomFieldsJsonMap())) + .addresses( + singletonList( + Address.of(CountryCode.DE).withCity("cologne").withKey("address1"))) .defaultBillingAddress(0) .billingAddresses(singletonList(0)) .defaultShippingAddress(0) .shippingAddresses(singletonList(0)) .build()) - .collect(Collectors.toList()); - } + .collect(Collectors.toList()); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductReferenceResolverIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductReferenceResolverIT.java index 2a59f95464..f1f2a773d3 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductReferenceResolverIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductReferenceResolverIT.java @@ -1,30 +1,5 @@ package com.commercetools.sync.integration.ctpprojectsource.products; -import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; -import com.commercetools.sync.products.ProductSync; -import com.commercetools.sync.products.ProductSyncOptions; -import com.commercetools.sync.products.ProductSyncOptionsBuilder; -import com.commercetools.sync.products.helpers.ProductSyncStatistics; -import io.sphere.sdk.categories.Category; -import io.sphere.sdk.models.Reference; -import io.sphere.sdk.products.Product; -import io.sphere.sdk.products.ProductDraft; -import io.sphere.sdk.products.commands.ProductCreateCommand; -import io.sphere.sdk.products.queries.ProductQuery; -import io.sphere.sdk.producttypes.ProductType; -import io.sphere.sdk.states.State; -import io.sphere.sdk.states.StateType; -import io.sphere.sdk.taxcategories.TaxCategory; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.CompletionException; - import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; @@ -50,134 +25,183 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; +import com.commercetools.sync.products.ProductSync; +import com.commercetools.sync.products.ProductSyncOptions; +import com.commercetools.sync.products.ProductSyncOptionsBuilder; +import com.commercetools.sync.products.helpers.ProductSyncStatistics; +import io.sphere.sdk.categories.Category; +import io.sphere.sdk.models.Reference; +import io.sphere.sdk.products.Product; +import io.sphere.sdk.products.ProductDraft; +import io.sphere.sdk.products.commands.ProductCreateCommand; +import io.sphere.sdk.products.queries.ProductQuery; +import io.sphere.sdk.producttypes.ProductType; +import io.sphere.sdk.states.State; +import io.sphere.sdk.states.StateType; +import io.sphere.sdk.taxcategories.TaxCategory; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletionException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class ProductReferenceResolverIT { - private static ProductType productTypeSource; - private static ProductType noKeyProductTypeSource; - - private static TaxCategory oldTaxCategory; - private static State oldProductState; - private static ProductQuery productQuery; - - private static List> categoryReferencesWithIds; - private ProductSync productSync; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - /** - * Delete all product related test data from target and source projects. Then creates custom types for both - * CTP projects categories. - */ - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - deleteProductSyncTestData(CTP_SOURCE_CLIENT); - - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_SOURCE_CLIENT); - - createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); - categoryReferencesWithIds = getReferencesWithIds( - createCategories(CTP_SOURCE_CLIENT, getCategoryDrafts(null, 2))); - - createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - createProductType(PRODUCT_TYPE_NO_KEY_RESOURCE_PATH, CTP_TARGET_CLIENT); - - productTypeSource = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_SOURCE_CLIENT); - noKeyProductTypeSource = createProductType(PRODUCT_TYPE_NO_KEY_RESOURCE_PATH, CTP_SOURCE_CLIENT); - - oldTaxCategory = createTaxCategory(CTP_SOURCE_CLIENT); - oldProductState = createState(CTP_SOURCE_CLIENT, StateType.PRODUCT_STATE); - createTaxCategory(CTP_TARGET_CLIENT); - createState(CTP_TARGET_CLIENT, StateType.PRODUCT_STATE); - - productQuery = buildProductQuery(); - } - - /** - * Deletes Products and Types from target CTP projects, then it populates target CTP project with product test - * data. - */ - @BeforeEach - void setupTest() { - deleteAllProducts(CTP_TARGET_CLIENT); - deleteAllProducts(CTP_SOURCE_CLIENT); - - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + private static ProductType productTypeSource; + private static ProductType noKeyProductTypeSource; + + private static TaxCategory oldTaxCategory; + private static State oldProductState; + private static ProductQuery productQuery; + + private static List> categoryReferencesWithIds; + private ProductSync productSync; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + /** + * Delete all product related test data from target and source projects. Then creates custom types + * for both CTP projects categories. + */ + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + deleteProductSyncTestData(CTP_SOURCE_CLIENT); + + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, + Locale.ENGLISH, + OLD_CATEGORY_CUSTOM_TYPE_NAME, + CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, + Locale.ENGLISH, + OLD_CATEGORY_CUSTOM_TYPE_NAME, + CTP_SOURCE_CLIENT); + + createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); + categoryReferencesWithIds = + getReferencesWithIds(createCategories(CTP_SOURCE_CLIENT, getCategoryDrafts(null, 2))); + + createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + createProductType(PRODUCT_TYPE_NO_KEY_RESOURCE_PATH, CTP_TARGET_CLIENT); + + productTypeSource = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_SOURCE_CLIENT); + noKeyProductTypeSource = + createProductType(PRODUCT_TYPE_NO_KEY_RESOURCE_PATH, CTP_SOURCE_CLIENT); + + oldTaxCategory = createTaxCategory(CTP_SOURCE_CLIENT); + oldProductState = createState(CTP_SOURCE_CLIENT, StateType.PRODUCT_STATE); + createTaxCategory(CTP_TARGET_CLIENT); + createState(CTP_TARGET_CLIENT, StateType.PRODUCT_STATE); + + productQuery = buildProductQuery(); + } + + /** + * Deletes Products and Types from target CTP projects, then it populates target CTP project with + * product test data. + */ + @BeforeEach + void setupTest() { + deleteAllProducts(CTP_TARGET_CLIENT); + deleteAllProducts(CTP_SOURCE_CLIENT); + + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) .errorCallback( (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }) - .warningCallback((exception, oldResource, newResource) -> - warningCallBackMessages.add(exception.getMessage())) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - productSync = new ProductSync(syncOptions); - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - deleteProductSyncTestData(CTP_SOURCE_CLIENT); - } - - @Test - void sync_withNewProductWithExistingCategoryAndProductTypeReferences_ShouldCreateProduct() { - // preparation - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, - productTypeSource.toReference(), oldTaxCategory.toReference(), oldProductState.toReference(), - categoryReferencesWithIds, createRandomCategoryOrderHints(categoryReferencesWithIds)); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(productDraft)).toCompletableFuture().join(); - - final List products = CTP_SOURCE_CLIENT.execute(productQuery) - .toCompletableFuture().join().getResults(); - - final List productDrafts = mapToProductDrafts(products); - - // test - final ProductSyncStatistics syncStatistics = productSync.sync(productDrafts).toCompletableFuture().join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withNewProductWithNoProductTypeKey_ShouldFailCreatingTheProduct() { - // preparation - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, - noKeyProductTypeSource.toReference(), oldTaxCategory.toReference(), oldProductState.toReference(), - categoryReferencesWithIds, createRandomCategoryOrderHints(categoryReferencesWithIds)); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(productDraft)).toCompletableFuture().join(); - - final List products = CTP_SOURCE_CLIENT.execute(productQuery) - .toCompletableFuture().join().getResults(); - - final List productDrafts = mapToProductDrafts(products); - - // test - final ProductSyncStatistics syncStatistics = productSync.sync(productDrafts).toCompletableFuture().join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).containsExactly(format("Failed to process the ProductDraft with" - + " key:'%s'. Reason: " - + ReferenceResolutionException.class.getCanonicalName() + ": " - + "Failed to resolve 'product-type' resource identifier on ProductDraft with " - + "key:'%s'. Reason: %s", - productDraft.getKey(), productDraft.getKey(), - BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); - assertThat(warningCallBackMessages).isEmpty(); - } + productSync = new ProductSync(syncOptions); + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + deleteProductSyncTestData(CTP_SOURCE_CLIENT); + } + + @Test + void sync_withNewProductWithExistingCategoryAndProductTypeReferences_ShouldCreateProduct() { + // preparation + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + productTypeSource.toReference(), + oldTaxCategory.toReference(), + oldProductState.toReference(), + categoryReferencesWithIds, + createRandomCategoryOrderHints(categoryReferencesWithIds)); + CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(productDraft)).toCompletableFuture().join(); + + final List products = + CTP_SOURCE_CLIENT.execute(productQuery).toCompletableFuture().join().getResults(); + + final List productDrafts = mapToProductDrafts(products); + + // test + final ProductSyncStatistics syncStatistics = + productSync.sync(productDrafts).toCompletableFuture().join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withNewProductWithNoProductTypeKey_ShouldFailCreatingTheProduct() { + // preparation + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + noKeyProductTypeSource.toReference(), + oldTaxCategory.toReference(), + oldProductState.toReference(), + categoryReferencesWithIds, + createRandomCategoryOrderHints(categoryReferencesWithIds)); + CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(productDraft)).toCompletableFuture().join(); + + final List products = + CTP_SOURCE_CLIENT.execute(productQuery).toCompletableFuture().join().getResults(); + + final List productDrafts = mapToProductDrafts(products); + + // test + final ProductSyncStatistics syncStatistics = + productSync.sync(productDrafts).toCompletableFuture().join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages) + .containsExactly( + format( + "Failed to process the ProductDraft with" + + " key:'%s'. Reason: " + + ReferenceResolutionException.class.getCanonicalName() + + ": " + + "Failed to resolve 'product-type' resource identifier on ProductDraft with " + + "key:'%s'. Reason: %s", + productDraft.getKey(), + productDraft.getKey(), + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); + assertThat(warningCallBackMessages).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductSyncIT.java index ec308ab680..4ddd9a11d7 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductSyncIT.java @@ -1,49 +1,5 @@ package com.commercetools.sync.integration.ctpprojectsource.products; -import com.commercetools.sync.products.ProductSync; -import com.commercetools.sync.products.ProductSyncOptions; -import com.commercetools.sync.products.ProductSyncOptionsBuilder; -import com.commercetools.sync.products.SyncFilter; -import com.commercetools.sync.products.helpers.ProductSyncStatistics; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; -import io.sphere.sdk.categories.Category; -import io.sphere.sdk.channels.Channel; -import io.sphere.sdk.channels.ChannelDraft; -import io.sphere.sdk.channels.commands.ChannelCreateCommand; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.models.Reference; -import io.sphere.sdk.products.Product; -import io.sphere.sdk.products.ProductDraft; -import io.sphere.sdk.products.ProductDraftBuilder; -import io.sphere.sdk.products.ProductVariantDraft; -import io.sphere.sdk.products.ProductVariantDraftBuilder; -import io.sphere.sdk.products.attributes.Attribute; -import io.sphere.sdk.products.attributes.AttributeDraft; -import io.sphere.sdk.products.commands.ProductCreateCommand; -import io.sphere.sdk.products.commands.updateactions.Publish; -import io.sphere.sdk.products.commands.updateactions.SetAttribute; -import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; -import io.sphere.sdk.products.queries.ProductByKeyGet; -import io.sphere.sdk.producttypes.ProductType; -import io.sphere.sdk.states.State; -import io.sphere.sdk.states.StateType; -import io.sphere.sdk.taxcategories.TaxCategory; -import io.sphere.sdk.types.CustomFieldsDraft; -import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; @@ -83,186 +39,288 @@ import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; +import com.commercetools.sync.products.ProductSync; +import com.commercetools.sync.products.ProductSyncOptions; +import com.commercetools.sync.products.ProductSyncOptionsBuilder; +import com.commercetools.sync.products.SyncFilter; +import com.commercetools.sync.products.helpers.ProductSyncStatistics; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.sphere.sdk.categories.Category; +import io.sphere.sdk.channels.Channel; +import io.sphere.sdk.channels.ChannelDraft; +import io.sphere.sdk.channels.commands.ChannelCreateCommand; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.models.Reference; +import io.sphere.sdk.products.Product; +import io.sphere.sdk.products.ProductDraft; +import io.sphere.sdk.products.ProductDraftBuilder; +import io.sphere.sdk.products.ProductVariantDraft; +import io.sphere.sdk.products.ProductVariantDraftBuilder; +import io.sphere.sdk.products.attributes.Attribute; +import io.sphere.sdk.products.attributes.AttributeDraft; +import io.sphere.sdk.products.commands.ProductCreateCommand; +import io.sphere.sdk.products.commands.updateactions.Publish; +import io.sphere.sdk.products.commands.updateactions.SetAttribute; +import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; +import io.sphere.sdk.products.queries.ProductByKeyGet; +import io.sphere.sdk.producttypes.ProductType; +import io.sphere.sdk.states.State; +import io.sphere.sdk.states.StateType; +import io.sphere.sdk.taxcategories.TaxCategory; +import io.sphere.sdk.types.CustomFieldsDraft; +import io.sphere.sdk.types.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class ProductSyncIT { - private static ProductType sourceProductType; - private static ProductType targetProductType; - - private static TaxCategory sourceTaxCategory; - private static TaxCategory targetTaxCategory; - - private static State sourceProductState; - private static State targetProductState; - - private static Channel sourcePriceChannel; - private static Channel targetPriceChannel; - - private static Type sourcePriceCustomType; - private static Type targetPriceCustomType; - - private static List> sourceCategoryReferencesWithIds; - private static List> targetCategoryReferencesWithIds; - private ProductSync productSync; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List> updateActions; - private List errorCallBackExceptions; - - /** - * Delete all product related test data from target and source projects. Then creates for both CTP projects price - * channels, product types, tax categories, categories, custom types for categories and product states. - */ - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - deleteProductSyncTestData(CTP_SOURCE_CLIENT); - - final ChannelDraft channelDraft1 = ChannelDraft.of(SUPPLY_CHANNEL_KEY_1); - targetPriceChannel = CTP_TARGET_CLIENT - .execute(ChannelCreateCommand.of(channelDraft1)).toCompletableFuture().join(); - sourcePriceChannel = CTP_SOURCE_CLIENT - .execute(ChannelCreateCommand.of(channelDraft1)).toCompletableFuture().join(); - - targetPriceCustomType = createPricesCustomType("pricesCustomTypeKey", ENGLISH, "pricesCustomTypeName", - CTP_TARGET_CLIENT); - sourcePriceCustomType = createPricesCustomType("pricesCustomTypeKey", ENGLISH, "pricesCustomTypeName", - CTP_SOURCE_CLIENT); - - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_SOURCE_CLIENT); - - - final List targetCategories = createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); - targetCategoryReferencesWithIds = getReferencesWithIds(targetCategories); - final List sourceCategories = createCategories(CTP_SOURCE_CLIENT, getCategoryDrafts(null, 2)); - sourceCategoryReferencesWithIds = getReferencesWithIds(sourceCategories); - - - targetProductType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - sourceProductType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_SOURCE_CLIENT); - - targetTaxCategory = createTaxCategory(CTP_TARGET_CLIENT); - sourceTaxCategory = createTaxCategory(CTP_SOURCE_CLIENT); - - targetProductState = createState(CTP_TARGET_CLIENT, StateType.PRODUCT_STATE); - sourceProductState = createState(CTP_SOURCE_CLIENT, StateType.PRODUCT_STATE); - } - - /** - * Deletes Products from the source and target CTP projects, clears the callback collections then it instantiates a - * new {@link ProductSync} instance. - */ - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - deleteAllProducts(CTP_SOURCE_CLIENT); - final ProductSyncOptions syncOptions = buildSyncOptions(); - productSync = new ProductSync(syncOptions); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - updateActions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback( - (exception, oldResource, newResource, updateActions) -> errorCallback( - exception.getMessage(), exception.getCause())) - .warningCallback((exception, oldResource, newResources) -> - warningCallBackMessages.add(exception.getMessage())) - .beforeUpdateCallback(this::beforeUpdateCallback) - .build(); - } - - private void errorCallback(@Nonnull final String errorMessage, @Nullable final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> beforeUpdateCallback(@Nonnull final List> updateActions, - @Nonnull final ProductDraft newProductDraft, - @Nonnull final Product oldProduct) { - this.updateActions.addAll(updateActions); - return updateActions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - deleteProductSyncTestData(CTP_SOURCE_CLIENT); - } - - @Test - void sync_withChangesOnly_ShouldUpdateProducts() { - final ProductDraft existingProductDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, - targetProductType.toReference(), targetTaxCategory.toReference(), targetProductState.toReference(), - targetCategoryReferencesWithIds, createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft)).toCompletableFuture().join(); - - final ProductDraft newProductDraft = createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - sourceProductType.toReference(), sourceTaxCategory.toReference(), sourceProductState.toReference(), - sourceCategoryReferencesWithIds, createRandomCategoryOrderHints(sourceCategoryReferencesWithIds)); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(newProductDraft)).toCompletableFuture().join(); - - final List products = CTP_SOURCE_CLIENT.execute(buildProductQuery()) - .toCompletableFuture().join().getResults(); - - final List productDrafts = mapToProductDrafts(products); - - final ProductSyncStatistics syncStatistics = productSync.sync(productDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withChangesOnlyAndUnPublish_ShouldUpdateProducts() { - final ProductDraft existingProductDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, - targetProductType.toReference(), targetTaxCategory.toReference(), targetProductState.toReference(), - targetCategoryReferencesWithIds, createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft)).toCompletableFuture().join(); - - final ProductDraft newProductDraft = createProductDraftBuilder(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - sourceProductType.toReference()) + private static ProductType sourceProductType; + private static ProductType targetProductType; + + private static TaxCategory sourceTaxCategory; + private static TaxCategory targetTaxCategory; + + private static State sourceProductState; + private static State targetProductState; + + private static Channel sourcePriceChannel; + private static Channel targetPriceChannel; + + private static Type sourcePriceCustomType; + private static Type targetPriceCustomType; + + private static List> sourceCategoryReferencesWithIds; + private static List> targetCategoryReferencesWithIds; + private ProductSync productSync; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List> updateActions; + private List errorCallBackExceptions; + + /** + * Delete all product related test data from target and source projects. Then creates for both CTP + * projects price channels, product types, tax categories, categories, custom types for categories + * and product states. + */ + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + deleteProductSyncTestData(CTP_SOURCE_CLIENT); + + final ChannelDraft channelDraft1 = ChannelDraft.of(SUPPLY_CHANNEL_KEY_1); + targetPriceChannel = + CTP_TARGET_CLIENT + .execute(ChannelCreateCommand.of(channelDraft1)) + .toCompletableFuture() + .join(); + sourcePriceChannel = + CTP_SOURCE_CLIENT + .execute(ChannelCreateCommand.of(channelDraft1)) + .toCompletableFuture() + .join(); + + targetPriceCustomType = + createPricesCustomType( + "pricesCustomTypeKey", ENGLISH, "pricesCustomTypeName", CTP_TARGET_CLIENT); + sourcePriceCustomType = + createPricesCustomType( + "pricesCustomTypeKey", ENGLISH, "pricesCustomTypeName", CTP_SOURCE_CLIENT); + + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, + Locale.ENGLISH, + OLD_CATEGORY_CUSTOM_TYPE_NAME, + CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, + Locale.ENGLISH, + OLD_CATEGORY_CUSTOM_TYPE_NAME, + CTP_SOURCE_CLIENT); + + final List targetCategories = + createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); + targetCategoryReferencesWithIds = getReferencesWithIds(targetCategories); + final List sourceCategories = + createCategories(CTP_SOURCE_CLIENT, getCategoryDrafts(null, 2)); + sourceCategoryReferencesWithIds = getReferencesWithIds(sourceCategories); + + targetProductType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + sourceProductType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_SOURCE_CLIENT); + + targetTaxCategory = createTaxCategory(CTP_TARGET_CLIENT); + sourceTaxCategory = createTaxCategory(CTP_SOURCE_CLIENT); + + targetProductState = createState(CTP_TARGET_CLIENT, StateType.PRODUCT_STATE); + sourceProductState = createState(CTP_SOURCE_CLIENT, StateType.PRODUCT_STATE); + } + + /** + * Deletes Products from the source and target CTP projects, clears the callback collections then + * it instantiates a new {@link ProductSync} instance. + */ + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + deleteAllProducts(CTP_SOURCE_CLIENT); + final ProductSyncOptions syncOptions = buildSyncOptions(); + productSync = new ProductSync(syncOptions); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + updateActions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallback(exception.getMessage(), exception.getCause())) + .warningCallback( + (exception, oldResource, newResources) -> + warningCallBackMessages.add(exception.getMessage())) + .beforeUpdateCallback(this::beforeUpdateCallback) + .build(); + } + + private void errorCallback( + @Nonnull final String errorMessage, @Nullable final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> beforeUpdateCallback( + @Nonnull final List> updateActions, + @Nonnull final ProductDraft newProductDraft, + @Nonnull final Product oldProduct) { + this.updateActions.addAll(updateActions); + return updateActions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + deleteProductSyncTestData(CTP_SOURCE_CLIENT); + } + + @Test + void sync_withChangesOnly_ShouldUpdateProducts() { + final ProductDraft existingProductDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + targetProductType.toReference(), + targetTaxCategory.toReference(), + targetProductState.toReference(), + targetCategoryReferencesWithIds, + createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(existingProductDraft)) + .toCompletableFuture() + .join(); + + final ProductDraft newProductDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + sourceProductType.toReference(), + sourceTaxCategory.toReference(), + sourceProductState.toReference(), + sourceCategoryReferencesWithIds, + createRandomCategoryOrderHints(sourceCategoryReferencesWithIds)); + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(newProductDraft)) + .toCompletableFuture() + .join(); + + final List products = + CTP_SOURCE_CLIENT.execute(buildProductQuery()).toCompletableFuture().join().getResults(); + + final List productDrafts = mapToProductDrafts(products); + + final ProductSyncStatistics syncStatistics = + productSync.sync(productDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withChangesOnlyAndUnPublish_ShouldUpdateProducts() { + final ProductDraft existingProductDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + targetProductType.toReference(), + targetTaxCategory.toReference(), + targetProductState.toReference(), + targetCategoryReferencesWithIds, + createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(existingProductDraft)) + .toCompletableFuture() + .join(); + + final ProductDraft newProductDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, sourceProductType.toReference()) .taxCategory(sourceTaxCategory) .state(sourceProductState) .categories(sourceCategoryReferencesWithIds) .categoryOrderHints(createRandomCategoryOrderHints(sourceCategoryReferencesWithIds)) - .publish(false).build(); - - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(newProductDraft)).toCompletableFuture().join(); - - final List products = CTP_SOURCE_CLIENT.execute(buildProductQuery()) - .toCompletableFuture().join().getResults(); - - final List productDrafts = mapToProductDrafts(products); - - final ProductSyncStatistics syncStatistics = productSync.sync(productDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withUpdatedDraftAndPublishFlagSetToTrue_ShouldPublishProductEvenItWasPublishedBefore() { - final ProductDraft publishedProductDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, - targetProductType.toReference(), targetTaxCategory.toReference(), targetProductState.toReference(), - targetCategoryReferencesWithIds, createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(publishedProductDraft)).toCompletableFuture().join(); + .publish(false) + .build(); - // new product draft has a publish flag set to true and the existing product is published already - final ProductDraft newProductDraft = createProductDraftBuilder(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - sourceProductType.toReference()) + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(newProductDraft)) + .toCompletableFuture() + .join(); + + final List products = + CTP_SOURCE_CLIENT.execute(buildProductQuery()).toCompletableFuture().join().getResults(); + + final List productDrafts = mapToProductDrafts(products); + + final ProductSyncStatistics syncStatistics = + productSync.sync(productDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withUpdatedDraftAndPublishFlagSetToTrue_ShouldPublishProductEvenItWasPublishedBefore() { + final ProductDraft publishedProductDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + targetProductType.toReference(), + targetTaxCategory.toReference(), + targetProductState.toReference(), + targetCategoryReferencesWithIds, + createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(publishedProductDraft)) + .toCompletableFuture() + .join(); + + // new product draft has a publish flag set to true and the existing product is published + // already + final ProductDraft newProductDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, sourceProductType.toReference()) .taxCategory(sourceTaxCategory) .state(sourceProductState) .categories(sourceCategoryReferencesWithIds) @@ -270,94 +328,132 @@ void sync_withUpdatedDraftAndPublishFlagSetToTrue_ShouldPublishProductEvenItWasP .publish(true) .build(); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(newProductDraft)).toCompletableFuture().join(); - - final List products = CTP_SOURCE_CLIENT.execute(buildProductQuery()) - .toCompletableFuture().join().getResults(); - - final List productDrafts = mapToProductDrafts(products); - - final ProductSyncStatistics syncStatistics = productSync.sync(productDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - // last action is publish - assertThat(updateActions.get(updateActions.size() - 1)).isEqualTo(Publish.of()); - } - - @Test - void sync_withPriceReferences_ShouldUpdateProducts() { - final ProductDraft existingProductDraft = createProductDraft(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, - targetProductType.toReference(), targetTaxCategory.toReference(), targetProductState.toReference(), - targetCategoryReferencesWithIds, createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); - - final ProductDraft existingDraftWithPriceReferences = - getDraftWithPriceReferences(existingProductDraft, targetPriceChannel.toReference(), - CustomFieldsDraft.ofTypeKeyAndJson(targetPriceCustomType.getKey(), emptyMap())); - - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingDraftWithPriceReferences)) - .toCompletableFuture().join(); - - final ProductDraft newProductDraft = createProductDraftBuilder(PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH, - sourceProductType.toReference()) + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(newProductDraft)) + .toCompletableFuture() + .join(); + + final List products = + CTP_SOURCE_CLIENT.execute(buildProductQuery()).toCompletableFuture().join().getResults(); + + final List productDrafts = mapToProductDrafts(products); + + final ProductSyncStatistics syncStatistics = + productSync.sync(productDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + // last action is publish + assertThat(updateActions.get(updateActions.size() - 1)).isEqualTo(Publish.of()); + } + + @Test + void sync_withPriceReferences_ShouldUpdateProducts() { + final ProductDraft existingProductDraft = + createProductDraft( + PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, + targetProductType.toReference(), + targetTaxCategory.toReference(), + targetProductState.toReference(), + targetCategoryReferencesWithIds, + createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); + + final ProductDraft existingDraftWithPriceReferences = + getDraftWithPriceReferences( + existingProductDraft, + targetPriceChannel.toReference(), + CustomFieldsDraft.ofTypeKeyAndJson(targetPriceCustomType.getKey(), emptyMap())); + + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(existingDraftWithPriceReferences)) + .toCompletableFuture() + .join(); + + final ProductDraft newProductDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH, sourceProductType.toReference()) .taxCategory(sourceTaxCategory) .state(sourceProductState) .categories(sourceCategoryReferencesWithIds) .categoryOrderHints(createRandomCategoryOrderHints(sourceCategoryReferencesWithIds)) - .publish(false).build(); - - final ProductDraft newDraftWithPriceReferences = - getDraftWithPriceReferences(newProductDraft, sourcePriceChannel.toReference(), - CustomFieldsDraft.ofTypeKeyAndJson(sourcePriceCustomType.getKey(), emptyMap())); - - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(newDraftWithPriceReferences)) - .toCompletableFuture().join(); - - final List products = CTP_SOURCE_CLIENT.execute(buildProductQuery()) - .toCompletableFuture().join().getResults(); - - final List productDrafts = mapToProductDrafts(products); - - final ProductSyncStatistics syncStatistics = productSync.sync(productDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withProductTypeReference_ShouldUpdateProducts() { - // Preparation - // Create custom options with whitelisting and action filter callback.. - final ProductSyncOptions customSyncOptions = - ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorCallback(exception.getMessage(), exception.getCause())) - .warningCallback((exception, oldResource, newResources) -> - warningCallBackMessages.add(exception.getMessage())) - .beforeUpdateCallback(this::beforeUpdateCallback) - .syncFilter(SyncFilter.ofWhiteList(ATTRIBUTES)) - .build(); - final ProductSync customSync = new ProductSync(customSyncOptions); - - // Create 3 existing products in target project with keys (productKey1, productKey2 and productKey3) - final ProductDraft existingProductDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, - targetProductType.toReference(), targetTaxCategory.toReference(), targetProductState.toReference(), - targetCategoryReferencesWithIds, createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft)).toCompletableFuture().join(); - - final ProductDraft existingProductDraft2 = createProductDraft(PRODUCT_KEY_2_RESOURCE_PATH, - targetProductType.toReference(), targetTaxCategory.toReference(), targetProductState.toReference(), - targetCategoryReferencesWithIds, createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft2)) - .toCompletableFuture().join(); - - final ProductDraft existingProductDraft3 = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - targetProductType.toReference()) + .publish(false) + .build(); + + final ProductDraft newDraftWithPriceReferences = + getDraftWithPriceReferences( + newProductDraft, + sourcePriceChannel.toReference(), + CustomFieldsDraft.ofTypeKeyAndJson(sourcePriceCustomType.getKey(), emptyMap())); + + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(newDraftWithPriceReferences)) + .toCompletableFuture() + .join(); + + final List products = + CTP_SOURCE_CLIENT.execute(buildProductQuery()).toCompletableFuture().join().getResults(); + + final List productDrafts = mapToProductDrafts(products); + + final ProductSyncStatistics syncStatistics = + productSync.sync(productDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withProductTypeReference_ShouldUpdateProducts() { + // Preparation + // Create custom options with whitelisting and action filter callback.. + final ProductSyncOptions customSyncOptions = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallback(exception.getMessage(), exception.getCause())) + .warningCallback( + (exception, oldResource, newResources) -> + warningCallBackMessages.add(exception.getMessage())) + .beforeUpdateCallback(this::beforeUpdateCallback) + .syncFilter(SyncFilter.ofWhiteList(ATTRIBUTES)) + .build(); + final ProductSync customSync = new ProductSync(customSyncOptions); + + // Create 3 existing products in target project with keys (productKey1, productKey2 and + // productKey3) + final ProductDraft existingProductDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + targetProductType.toReference(), + targetTaxCategory.toReference(), + targetProductState.toReference(), + targetCategoryReferencesWithIds, + createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(existingProductDraft)) + .toCompletableFuture() + .join(); + + final ProductDraft existingProductDraft2 = + createProductDraft( + PRODUCT_KEY_2_RESOURCE_PATH, + targetProductType.toReference(), + targetTaxCategory.toReference(), + targetProductState.toReference(), + targetCategoryReferencesWithIds, + createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(existingProductDraft2)) + .toCompletableFuture() + .join(); + + final ProductDraft existingProductDraft3 = + createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, targetProductType.toReference()) .slug(ofEnglish("newSlug3")) .key("productKey3") .masterVariant(ProductVariantDraftBuilder.of().key("v3").sku("s3").build()) @@ -366,16 +462,27 @@ void sync_withProductTypeReference_ShouldUpdateProducts() { .categories(Collections.emptySet()) .categoryOrderHints(null) .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft3)).toCompletableFuture().join(); - - // Create 2 existing products in source project with keys (productKey2 and productKey3) - final ProductDraft newProductDraft2 = createProductDraft(PRODUCT_KEY_2_RESOURCE_PATH, - sourceProductType.toReference(), sourceTaxCategory.toReference(), sourceProductState.toReference(), - sourceCategoryReferencesWithIds, createRandomCategoryOrderHints(sourceCategoryReferencesWithIds)); - final Product product2 = CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(newProductDraft2)) - .toCompletableFuture().join(); - final ProductDraft newProductDraft3 = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - sourceProductType.toReference()) + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(existingProductDraft3)) + .toCompletableFuture() + .join(); + + // Create 2 existing products in source project with keys (productKey2 and productKey3) + final ProductDraft newProductDraft2 = + createProductDraft( + PRODUCT_KEY_2_RESOURCE_PATH, + sourceProductType.toReference(), + sourceTaxCategory.toReference(), + sourceProductState.toReference(), + sourceCategoryReferencesWithIds, + createRandomCategoryOrderHints(sourceCategoryReferencesWithIds)); + final Product product2 = + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(newProductDraft2)) + .toCompletableFuture() + .join(); + final ProductDraft newProductDraft3 = + createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, sourceProductType.toReference()) .slug(ofEnglish("newSlug3")) .key("productKey3") .masterVariant(ProductVariantDraftBuilder.of().key("v3").sku("s3").build()) @@ -384,130 +491,149 @@ void sync_withProductTypeReference_ShouldUpdateProducts() { .categories(Collections.emptySet()) .categoryOrderHints(null) .build(); - final Product product3 = CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(newProductDraft3)) - .toCompletableFuture().join(); - - - // Create existing product with productKey1 in source project that has references to products with keys - // (productKey2 and productKey3). - - final ObjectNode productReferenceValue1 = getProductReferenceWithId(product2.getId()); - final ObjectNode productReferenceValue2 = getProductReferenceWithId(product3.getId()); - - final AttributeDraft productRefAttr = AttributeDraft.of("product-reference", productReferenceValue1); - final AttributeDraft productSetRefAttr = - getReferenceSetAttributeDraft("product-reference-set", productReferenceValue1, - productReferenceValue2); - final List attributeDrafts = existingProductDraft.getMasterVariant().getAttributes(); - attributeDrafts.addAll(Arrays.asList(productRefAttr, productSetRefAttr)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder.of() - .key("v1") - .sku("s1") - .attributes(attributeDrafts).build(); - - final ProductDraft newProductDraftWithProductReference = - createProductDraftBuilder(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, sourceProductType.toReference()) - .masterVariant(masterVariant) - .taxCategory(sourceTaxCategory.toReference()) - .state(sourceProductState.toReference()) - .categories(Collections.emptySet()) - .categoryOrderHints(null) - .build(); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(newProductDraftWithProductReference)) - .toCompletableFuture().join(); - - - // Test - final List products = CTP_SOURCE_CLIENT.execute(buildProductQuery()) - .toCompletableFuture().join().getResults(); - final List productDrafts = mapToProductDrafts(products); - final ProductSyncStatistics syncStatistics = customSync.sync(productDrafts).toCompletableFuture().join(); - - - // Assertion - assertThat(syncStatistics).hasValues(3, 0, 1, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - final Product targetProduct2 = CTP_TARGET_CLIENT.execute(ProductByKeyGet.of("productKey2")) - .toCompletableFuture() - .join(); - - final Product targetProduct3 = CTP_TARGET_CLIENT.execute(ProductByKeyGet.of("productKey3")) - .toCompletableFuture() - .join(); - - final ObjectNode targetProductReferenceValue2 = getProductReferenceWithId(targetProduct2.getId()); - final ObjectNode targetProductReferenceValue3 = getProductReferenceWithId(targetProduct3.getId()); - - final AttributeDraft targetProductRefAttr = - AttributeDraft.of("product-reference", targetProductReferenceValue2); - final AttributeDraft targetProductSetRefAttr = - getReferenceSetAttributeDraft("product-reference-set", targetProductReferenceValue2, - targetProductReferenceValue3); - - assertThat(updateActions).containsExactlyInAnyOrder( - SetAttributeInAllVariants.of(targetProductRefAttr.getName(), targetProductRefAttr.getValue(), true), - SetAttributeInAllVariants.of(targetProductSetRefAttr.getName(), targetProductSetRefAttr.getValue(), true), - Publish.of() - ); - } - - @Test - void sync_withChangedAttributes_ShouldUpdateProducts() { - // Preparation - // Create custom options with whitelisting and action filter callback.. - final ProductSyncOptions customSyncOptions = - ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorCallback(exception.getMessage(), exception.getCause())) - .warningCallback((exception, oldResource, newResources) -> - warningCallBackMessages.add(exception.getMessage())) - .beforeUpdateCallback(this::beforeUpdateCallback) - .syncFilter(SyncFilter.ofWhiteList(ATTRIBUTES)) - .build(); - final ProductSync customSync = new ProductSync(customSyncOptions); - - // Create existing products in target project with keys (productKey1) - final ProductDraft existingProductDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, - targetProductType.toReference(), targetTaxCategory.toReference(), targetProductState.toReference(), - targetCategoryReferencesWithIds, createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft)).toCompletableFuture().join(); - - - // Create existing product with productKey1 in source project with changed attributes - final ProductDraft newProductDraftWithProductReference = - createProductDraftBuilder(PRODUCT_KEY_1_CHANGED_ATTRIBUTES_RESOURCE_PATH, sourceProductType.toReference()) - .taxCategory(sourceTaxCategory.toReference()) - .state(sourceProductState.toReference()) - .categories(Collections.emptySet()) - .categoryOrderHints(null) - .build(); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(newProductDraftWithProductReference)) - .toCompletableFuture().join(); - - - // Test - final List products = CTP_SOURCE_CLIENT.execute(buildProductQuery()) - .toCompletableFuture().join().getResults(); - final List productDrafts = mapToProductDrafts(products); - final ProductSyncStatistics syncStatistics = customSync.sync(productDrafts).toCompletableFuture().join(); - - - // Assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - final AttributeDraft priceInfoAttrDraft = - AttributeDraft.of("priceInfo", JsonNodeFactory.instance.textNode("100/kg")); - final AttributeDraft angebotAttrDraft = - AttributeDraft.of("angebot", JsonNodeFactory.instance.textNode("big discount")); - - assertThat(updateActions).containsExactlyInAnyOrder( + final Product product3 = + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(newProductDraft3)) + .toCompletableFuture() + .join(); + + // Create existing product with productKey1 in source project that has references to products + // with keys + // (productKey2 and productKey3). + + final ObjectNode productReferenceValue1 = getProductReferenceWithId(product2.getId()); + final ObjectNode productReferenceValue2 = getProductReferenceWithId(product3.getId()); + + final AttributeDraft productRefAttr = + AttributeDraft.of("product-reference", productReferenceValue1); + final AttributeDraft productSetRefAttr = + getReferenceSetAttributeDraft( + "product-reference-set", productReferenceValue1, productReferenceValue2); + final List attributeDrafts = + existingProductDraft.getMasterVariant().getAttributes(); + attributeDrafts.addAll(Arrays.asList(productRefAttr, productSetRefAttr)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of().key("v1").sku("s1").attributes(attributeDrafts).build(); + + final ProductDraft newProductDraftWithProductReference = + createProductDraftBuilder( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, sourceProductType.toReference()) + .masterVariant(masterVariant) + .taxCategory(sourceTaxCategory.toReference()) + .state(sourceProductState.toReference()) + .categories(Collections.emptySet()) + .categoryOrderHints(null) + .build(); + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); + + // Test + final List products = + CTP_SOURCE_CLIENT.execute(buildProductQuery()).toCompletableFuture().join().getResults(); + final List productDrafts = mapToProductDrafts(products); + final ProductSyncStatistics syncStatistics = + customSync.sync(productDrafts).toCompletableFuture().join(); + + // Assertion + assertThat(syncStatistics).hasValues(3, 0, 1, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + final Product targetProduct2 = + CTP_TARGET_CLIENT.execute(ProductByKeyGet.of("productKey2")).toCompletableFuture().join(); + + final Product targetProduct3 = + CTP_TARGET_CLIENT.execute(ProductByKeyGet.of("productKey3")).toCompletableFuture().join(); + + final ObjectNode targetProductReferenceValue2 = + getProductReferenceWithId(targetProduct2.getId()); + final ObjectNode targetProductReferenceValue3 = + getProductReferenceWithId(targetProduct3.getId()); + + final AttributeDraft targetProductRefAttr = + AttributeDraft.of("product-reference", targetProductReferenceValue2); + final AttributeDraft targetProductSetRefAttr = + getReferenceSetAttributeDraft( + "product-reference-set", targetProductReferenceValue2, targetProductReferenceValue3); + + assertThat(updateActions) + .containsExactlyInAnyOrder( + SetAttributeInAllVariants.of( + targetProductRefAttr.getName(), targetProductRefAttr.getValue(), true), + SetAttributeInAllVariants.of( + targetProductSetRefAttr.getName(), targetProductSetRefAttr.getValue(), true), + Publish.of()); + } + + @Test + void sync_withChangedAttributes_ShouldUpdateProducts() { + // Preparation + // Create custom options with whitelisting and action filter callback.. + final ProductSyncOptions customSyncOptions = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallback(exception.getMessage(), exception.getCause())) + .warningCallback( + (exception, oldResource, newResources) -> + warningCallBackMessages.add(exception.getMessage())) + .beforeUpdateCallback(this::beforeUpdateCallback) + .syncFilter(SyncFilter.ofWhiteList(ATTRIBUTES)) + .build(); + final ProductSync customSync = new ProductSync(customSyncOptions); + + // Create existing products in target project with keys (productKey1) + final ProductDraft existingProductDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + targetProductType.toReference(), + targetTaxCategory.toReference(), + targetProductState.toReference(), + targetCategoryReferencesWithIds, + createRandomCategoryOrderHints(targetCategoryReferencesWithIds)); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(existingProductDraft)) + .toCompletableFuture() + .join(); + + // Create existing product with productKey1 in source project with changed attributes + final ProductDraft newProductDraftWithProductReference = + createProductDraftBuilder( + PRODUCT_KEY_1_CHANGED_ATTRIBUTES_RESOURCE_PATH, sourceProductType.toReference()) + .taxCategory(sourceTaxCategory.toReference()) + .state(sourceProductState.toReference()) + .categories(Collections.emptySet()) + .categoryOrderHints(null) + .build(); + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); + + // Test + final List products = + CTP_SOURCE_CLIENT.execute(buildProductQuery()).toCompletableFuture().join().getResults(); + final List productDrafts = mapToProductDrafts(products); + final ProductSyncStatistics syncStatistics = + customSync.sync(productDrafts).toCompletableFuture().join(); + + // Assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + final AttributeDraft priceInfoAttrDraft = + AttributeDraft.of("priceInfo", JsonNodeFactory.instance.textNode("100/kg")); + final AttributeDraft angebotAttrDraft = + AttributeDraft.of("angebot", JsonNodeFactory.instance.textNode("big discount")); + + assertThat(updateActions) + .containsExactlyInAnyOrder( SetAttributeInAllVariants.of(priceInfoAttrDraft, true), SetAttribute.of(1, angebotAttrDraft, true), SetAttributeInAllVariants.ofUnsetAttribute("size", true), @@ -521,52 +647,56 @@ void sync_withChangedAttributes_ShouldUpdateProducts() { SetAttributeInAllVariants.ofUnsetAttribute("anlieferung", true), SetAttributeInAllVariants.ofUnsetAttribute("zubereitung", true), SetAttribute.ofUnsetAttribute(1, "localisedText", true), - Publish.of() - ); - } - - @Test - void sync_withEmptySetAttribute_ShouldCreateProductWithAnEmptySetAttribute() { - // Preparation - // Create custom options with whitelisting and action filter callback.. - final ProductSyncOptions customSyncOptions = - ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback(((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage()))) - .beforeUpdateCallback(this::beforeUpdateCallback) - .syncFilter(SyncFilter.ofWhiteList(ATTRIBUTES)) - .build(); - final ProductSync customSync = new ProductSync(customSyncOptions); - - - // Create a product that will be referenced by another product in the target project - final ProductDraft productDraftToBeReferenced = ProductDraftBuilder - .of(targetProductType.toReference(), ofEnglish("root"), ofEnglish("root"), emptyList()) + Publish.of()); + } + + @Test + void sync_withEmptySetAttribute_ShouldCreateProductWithAnEmptySetAttribute() { + // Preparation + // Create custom options with whitelisting and action filter callback.. + final ProductSyncOptions customSyncOptions = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + ((exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()))) + .beforeUpdateCallback(this::beforeUpdateCallback) + .syncFilter(SyncFilter.ofWhiteList(ATTRIBUTES)) .build(); + final ProductSync customSync = new ProductSync(customSyncOptions); - final Product productToBeReferenced = - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftToBeReferenced)) - .toCompletableFuture().join(); + // Create a product that will be referenced by another product in the target project + final ProductDraft productDraftToBeReferenced = + ProductDraftBuilder.of( + targetProductType.toReference(), ofEnglish("root"), ofEnglish("root"), emptyList()) + .build(); + final Product productToBeReferenced = + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftToBeReferenced)) + .toCompletableFuture() + .join(); - // Create a product "bar" that references a product on the target project - final ObjectNode productReferenceValue1 = getProductReferenceWithId(productToBeReferenced.getId()); + // Create a product "bar" that references a product on the target project + final ObjectNode productReferenceValue1 = + getProductReferenceWithId(productToBeReferenced.getId()); - final AttributeDraft productSetRefAttr = - getReferenceSetAttributeDraft("product-reference-set", productReferenceValue1); + final AttributeDraft productSetRefAttr = + getReferenceSetAttributeDraft("product-reference-set", productReferenceValue1); - final ProductVariantDraft variantWithProductReferences = ProductVariantDraftBuilder - .of() + final ProductVariantDraft variantWithProductReferences = + ProductVariantDraftBuilder.of() .key("bar") .sku("bar") - .attributes(singletonList(productSetRefAttr)).build(); + .attributes(singletonList(productSetRefAttr)) + .build(); - final ProductDraft existingProductDraft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - targetProductType.toReference()) + final ProductDraft existingProductDraft = + createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, targetProductType.toReference()) .masterVariant(variantWithProductReferences) .taxCategory(targetTaxCategory.toReference()) .state(targetProductState.toReference()) @@ -574,72 +704,87 @@ void sync_withEmptySetAttribute_ShouldCreateProductWithAnEmptySetAttribute() { .categoryOrderHints(null) .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft)).toCompletableFuture().join(); - + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(existingProductDraft)) + .toCompletableFuture() + .join(); - // Create a product "bar" that has an empty references set on the source project (this is expected to update) - final ProductVariantDraft variantBarWithEmptyReferenceSet = ProductVariantDraftBuilder - .of() + // Create a product "bar" that has an empty references set on the source project (this is + // expected to update) + final ProductVariantDraft variantBarWithEmptyReferenceSet = + ProductVariantDraftBuilder.of() .key("bar") .sku("bar") .attributes(singletonList(getReferenceSetAttributeDraft(productSetRefAttr.getName()))) .build(); - final ProductDraft newProductDraftWithProductReference = - createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, sourceProductType.toReference()) - .masterVariant(variantBarWithEmptyReferenceSet) - .taxCategory(sourceTaxCategory.toReference()) - .state(sourceProductState.toReference()) - .categories(Collections.emptySet()) - .categoryOrderHints(null) - .build(); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(newProductDraftWithProductReference)) - .toCompletableFuture().join(); - - - // Create a product "foo" that has an empty references set on the source project (this is expected to create) - final ProductVariantDraft variantFooWithEmptyReferenceSet = ProductVariantDraftBuilder - .of() + final ProductDraft newProductDraftWithProductReference = + createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, sourceProductType.toReference()) + .masterVariant(variantBarWithEmptyReferenceSet) + .taxCategory(sourceTaxCategory.toReference()) + .state(sourceProductState.toReference()) + .categories(Collections.emptySet()) + .categoryOrderHints(null) + .build(); + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); + + // Create a product "foo" that has an empty references set on the source project (this is + // expected to create) + final ProductVariantDraft variantFooWithEmptyReferenceSet = + ProductVariantDraftBuilder.of() .key("foo") .sku("foo") .attributes(singletonList(getReferenceSetAttributeDraft(productSetRefAttr.getName()))) .build(); - final ProductDraft sourceProductDraft = - createProductDraftBuilder(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, sourceProductType.toReference()) - .taxCategory(sourceTaxCategory) - .state(sourceProductState) - .categories(sourceCategoryReferencesWithIds) - .categoryOrderHints(createRandomCategoryOrderHints(sourceCategoryReferencesWithIds)) - .masterVariant(variantFooWithEmptyReferenceSet) - .build(); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(sourceProductDraft)) - .toCompletableFuture().join(); - - - // Test - final List products = CTP_SOURCE_CLIENT.execute(buildProductQuery()) - .toCompletableFuture().join().getResults(); - final List productDrafts = mapToProductDrafts(products); - final ProductSyncStatistics syncStatistics = customSync.sync(productDrafts).toCompletableFuture().join(); - - - // Assertion - assertThat(syncStatistics).hasValues(2, 1, 1, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(updateActions).containsExactly( - SetAttributeInAllVariants.of(productSetRefAttr.getName(), JsonNodeFactory.instance.arrayNode(), true), + final ProductDraft sourceProductDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, sourceProductType.toReference()) + .taxCategory(sourceTaxCategory) + .state(sourceProductState) + .categories(sourceCategoryReferencesWithIds) + .categoryOrderHints(createRandomCategoryOrderHints(sourceCategoryReferencesWithIds)) + .masterVariant(variantFooWithEmptyReferenceSet) + .build(); + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(sourceProductDraft)) + .toCompletableFuture() + .join(); + + // Test + final List products = + CTP_SOURCE_CLIENT.execute(buildProductQuery()).toCompletableFuture().join().getResults(); + final List productDrafts = mapToProductDrafts(products); + final ProductSyncStatistics syncStatistics = + customSync.sync(productDrafts).toCompletableFuture().join(); + + // Assertion + assertThat(syncStatistics).hasValues(2, 1, 1, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(updateActions) + .containsExactly( + SetAttributeInAllVariants.of( + productSetRefAttr.getName(), JsonNodeFactory.instance.arrayNode(), true), Publish.of()); - final Product targetProduct = CTP_TARGET_CLIENT.execute(ProductByKeyGet.of(sourceProductDraft.getKey())) - .toCompletableFuture() - .join(); - - final Attribute targetAttribute = targetProduct.getMasterData().getStaged().getMasterVariant() - .getAttribute(productSetRefAttr.getName()); - assertThat(targetAttribute).isNotNull(); - assertThat(targetAttribute.getValueAsJsonNode()).isEmpty(); - } + final Product targetProduct = + CTP_TARGET_CLIENT + .execute(ProductByKeyGet.of(sourceProductDraft.getKey())) + .toCompletableFuture() + .join(); + + final Attribute targetAttribute = + targetProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .getAttribute(productSetRefAttr.getName()); + assertThat(targetAttribute).isNotNull(); + assertThat(targetAttribute.getValueAsJsonNode()).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductSyncWithAssetsIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductSyncWithAssetsIT.java index 43434b933d..e0e56f4131 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductSyncWithAssetsIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/ProductSyncWithAssetsIT.java @@ -1,5 +1,25 @@ package com.commercetools.sync.integration.ctpprojectsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ITUtils.BOOLEAN_CUSTOM_FIELD_NAME; +import static com.commercetools.sync.integration.commons.utils.ITUtils.LOCALISED_STRING_CUSTOM_FIELD_NAME; +import static com.commercetools.sync.integration.commons.utils.ITUtils.assertAssetsAreEqual; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetDraft; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetsCustomType; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createVariantDraft; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductReferenceResolutionUtils.buildProductQuery; +import static com.commercetools.sync.products.utils.ProductReferenceResolutionUtils.mapToProductDrafts; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toMap; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.products.ProductSync; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -25,231 +45,258 @@ import io.sphere.sdk.products.queries.ProductProjectionByKeyGet; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ITUtils.BOOLEAN_CUSTOM_FIELD_NAME; -import static com.commercetools.sync.integration.commons.utils.ITUtils.LOCALISED_STRING_CUSTOM_FIELD_NAME; -import static com.commercetools.sync.integration.commons.utils.ITUtils.assertAssetsAreEqual; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetDraft; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetsCustomType; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createVariantDraft; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductReferenceResolutionUtils.buildProductQuery; -import static com.commercetools.sync.products.utils.ProductReferenceResolutionUtils.mapToProductDrafts; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.stream.Collectors.toMap; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncWithAssetsIT { - private static final String ASSETS_CUSTOM_TYPE_KEY = "assetsCustomTypeKey"; - private static ProductType sourceProductType; - private static ProductType targetProductType; - - private static Type targetAssetCustomType; - private static Type sourceAssetCustomType; - - private ProductSync productSync; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List> updateActions; - private List errorCallBackExceptions; - - /** - * Delete all product related test data from target and source projects. Then creates for both CTP projects product - * types and asset custom types. - */ - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - deleteProductSyncTestData(CTP_SOURCE_CLIENT); - - targetProductType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - sourceProductType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_SOURCE_CLIENT); - - - targetAssetCustomType = createAssetsCustomType(ASSETS_CUSTOM_TYPE_KEY, Locale.ENGLISH, - "assetsCustomTypeName", CTP_TARGET_CLIENT); - sourceAssetCustomType = createAssetsCustomType(ASSETS_CUSTOM_TYPE_KEY, Locale.ENGLISH, - "assetsCustomTypeName", CTP_SOURCE_CLIENT); - } - - /** - * Deletes Products from the source and target CTP projects, clears the callback collections then it instantiates a - * new {@link ProductSync} instance. - */ - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - deleteAllProducts(CTP_SOURCE_CLIENT); - productSync = new ProductSync(buildSyncOptions()); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - updateActions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) - -> errorCallback(exception.getMessage(), exception.getCause())) - .warningCallback((exception, oldResource, newResource) -> - warningCallBackMessages.add(exception.getMessage())) - .beforeUpdateCallback(this::beforeUpdateCallback) - .build(); - } - - private void errorCallback(@Nonnull final String errorMessage, @Nullable final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> beforeUpdateCallback(@Nonnull final List> updateActions, - @Nonnull final ProductDraft newProductDraft, - @Nonnull final Product oldProduct) { - this.updateActions.addAll(updateActions); - return updateActions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - deleteProductSyncTestData(CTP_SOURCE_CLIENT); - } - - @Test - void sync_withNewProductWithAssets_shouldCreateProduct() { - final List assetDraftsToCreateOnExistingProduct = asList( + private static final String ASSETS_CUSTOM_TYPE_KEY = "assetsCustomTypeKey"; + private static ProductType sourceProductType; + private static ProductType targetProductType; + + private static Type targetAssetCustomType; + private static Type sourceAssetCustomType; + + private ProductSync productSync; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List> updateActions; + private List errorCallBackExceptions; + + /** + * Delete all product related test data from target and source projects. Then creates for both CTP + * projects product types and asset custom types. + */ + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + deleteProductSyncTestData(CTP_SOURCE_CLIENT); + + targetProductType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + sourceProductType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_SOURCE_CLIENT); + + targetAssetCustomType = + createAssetsCustomType( + ASSETS_CUSTOM_TYPE_KEY, Locale.ENGLISH, "assetsCustomTypeName", CTP_TARGET_CLIENT); + sourceAssetCustomType = + createAssetsCustomType( + ASSETS_CUSTOM_TYPE_KEY, Locale.ENGLISH, "assetsCustomTypeName", CTP_SOURCE_CLIENT); + } + + /** + * Deletes Products from the source and target CTP projects, clears the callback collections then + * it instantiates a new {@link ProductSync} instance. + */ + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + deleteAllProducts(CTP_SOURCE_CLIENT); + productSync = new ProductSync(buildSyncOptions()); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + updateActions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallback(exception.getMessage(), exception.getCause())) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .beforeUpdateCallback(this::beforeUpdateCallback) + .build(); + } + + private void errorCallback( + @Nonnull final String errorMessage, @Nullable final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> beforeUpdateCallback( + @Nonnull final List> updateActions, + @Nonnull final ProductDraft newProductDraft, + @Nonnull final Product oldProduct) { + this.updateActions.addAll(updateActions); + return updateActions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + deleteProductSyncTestData(CTP_SOURCE_CLIENT); + } + + @Test + void sync_withNewProductWithAssets_shouldCreateProduct() { + final List assetDraftsToCreateOnExistingProduct = + asList( createAssetDraft("1", ofEnglish("1"), sourceAssetCustomType.getId()), createAssetDraft("2", ofEnglish("2"), sourceAssetCustomType.getId()), createAssetDraft("3", ofEnglish("3"), sourceAssetCustomType.getId())); - final ProductDraft draftToCreateOnTargetProject = ProductDraftBuilder - .of(targetProductType.toReference(), ofEnglish("draftName"), ofEnglish("existingProductInTarget"), + final ProductDraft draftToCreateOnTargetProject = + ProductDraftBuilder.of( + targetProductType.toReference(), + ofEnglish("draftName"), + ofEnglish("existingProductInTarget"), ProductVariantDraftBuilder.of().key("k1").sku("sku1").build()) .key("existingProductInTarget") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(draftToCreateOnTargetProject)).toCompletableFuture().join(); - - final ProductDraft draftToCreateOnSourceProject = ProductDraftBuilder - .of(sourceProductType.toReference(), ofEnglish("draftName"), ofEnglish("existingProductInSource"), + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(draftToCreateOnTargetProject)) + .toCompletableFuture() + .join(); + + final ProductDraft draftToCreateOnSourceProject = + ProductDraftBuilder.of( + sourceProductType.toReference(), + ofEnglish("draftName"), + ofEnglish("existingProductInSource"), createVariantDraft("masterVariant", assetDraftsToCreateOnExistingProduct, null)) .key("existingProductInSource") .build(); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(draftToCreateOnSourceProject)).toCompletableFuture().join(); - - final List products = CTP_SOURCE_CLIENT.execute(buildProductQuery()) - .toCompletableFuture().join().getResults(); - - final List productDrafts = mapToProductDrafts(products); - - final ProductSyncStatistics syncStatistics = productSync.sync(productDrafts).toCompletableFuture().join(); - - // Assert results of sync - assertThat(syncStatistics).hasValues(1, 1, 0, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - // Assert that the product was created with the assets. - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of("existingProductInSource", ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - final List createdAssets = productProjection.getMasterVariant().getAssets(); - assertAssetsAreEqual(createdAssets, assetDraftsToCreateOnExistingProduct); - } - - @Test - void sync_withMatchingProductWithAssetChanges_shouldUpdateProduct() { - final Map customFieldsJsonMap = new HashMap<>(); - customFieldsJsonMap.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true)); - - final List assetDraftsToCreateOnExistingProductOnTargetProject = asList( + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(draftToCreateOnSourceProject)) + .toCompletableFuture() + .join(); + + final List products = + CTP_SOURCE_CLIENT.execute(buildProductQuery()).toCompletableFuture().join().getResults(); + + final List productDrafts = mapToProductDrafts(products); + + final ProductSyncStatistics syncStatistics = + productSync.sync(productDrafts).toCompletableFuture().join(); + + // Assert results of sync + assertThat(syncStatistics).hasValues(1, 1, 0, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + // Assert that the product was created with the assets. + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + "existingProductInSource", ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + final List createdAssets = productProjection.getMasterVariant().getAssets(); + assertAssetsAreEqual(createdAssets, assetDraftsToCreateOnExistingProduct); + } + + @Test + void sync_withMatchingProductWithAssetChanges_shouldUpdateProduct() { + final Map customFieldsJsonMap = new HashMap<>(); + customFieldsJsonMap.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true)); + + final List assetDraftsToCreateOnExistingProductOnTargetProject = + asList( createAssetDraft("1", ofEnglish("1"), targetAssetCustomType.getId()), createAssetDraft("2", ofEnglish("2"), targetAssetCustomType.getId()), createAssetDraft("3", ofEnglish("3"), targetAssetCustomType.getId())); - final List assetDraftsToCreateOnExistingProductOnSourceProject = asList( + final List assetDraftsToCreateOnExistingProductOnSourceProject = + asList( createAssetDraft("4", ofEnglish("4"), sourceAssetCustomType.getId()), - createAssetDraft("3", ofEnglish("3"), sourceAssetCustomType.getId(), customFieldsJsonMap), + createAssetDraft( + "3", ofEnglish("3"), sourceAssetCustomType.getId(), customFieldsJsonMap), createAssetDraft("2", ofEnglish("newName"))); - final String productKey = "same-product"; - final ProductDraft draftToCreateOnTargetProject = ProductDraftBuilder - .of(targetProductType.toReference(), ofEnglish("draftName"), ofEnglish("existingProductInTarget"), - createVariantDraft("masterVariant", assetDraftsToCreateOnExistingProductOnTargetProject, null)) + final String productKey = "same-product"; + final ProductDraft draftToCreateOnTargetProject = + ProductDraftBuilder.of( + targetProductType.toReference(), + ofEnglish("draftName"), + ofEnglish("existingProductInTarget"), + createVariantDraft( + "masterVariant", assetDraftsToCreateOnExistingProductOnTargetProject, null)) .key(productKey) .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(draftToCreateOnTargetProject)).toCompletableFuture().join(); - - final ProductDraft draftToCreateOnSourceProject = ProductDraftBuilder - .of(sourceProductType.toReference(), draftToCreateOnTargetProject.getName(), - draftToCreateOnTargetProject.getSlug(), createVariantDraft("masterVariant", - assetDraftsToCreateOnExistingProductOnSourceProject, null)) + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(draftToCreateOnTargetProject)) + .toCompletableFuture() + .join(); + + final ProductDraft draftToCreateOnSourceProject = + ProductDraftBuilder.of( + sourceProductType.toReference(), draftToCreateOnTargetProject.getName(), + draftToCreateOnTargetProject.getSlug(), + createVariantDraft( + "masterVariant", assetDraftsToCreateOnExistingProductOnSourceProject, null)) .key(productKey) .build(); - CTP_SOURCE_CLIENT.execute(ProductCreateCommand.of(draftToCreateOnSourceProject)).toCompletableFuture().join(); + CTP_SOURCE_CLIENT + .execute(ProductCreateCommand.of(draftToCreateOnSourceProject)) + .toCompletableFuture() + .join(); - final List products = CTP_SOURCE_CLIENT.execute(buildProductQuery()) - .toCompletableFuture().join().getResults(); + final List products = + CTP_SOURCE_CLIENT.execute(buildProductQuery()).toCompletableFuture().join().getResults(); - final List productDrafts = mapToProductDrafts(products); + final List productDrafts = mapToProductDrafts(products); - final ProductSyncStatistics syncStatistics = productSync.sync(productDrafts).toCompletableFuture().join(); + final ProductSyncStatistics syncStatistics = + productSync.sync(productDrafts).toCompletableFuture().join(); - // Assert results of sync - assertThat(syncStatistics).hasValues(1, 0, 1, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); + // Assert results of sync + assertThat(syncStatistics).hasValues(1, 0, 1, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); - // Assert that assets got updated correctly - final ProductProjection productProjection = CTP_TARGET_CLIENT + // Assert that assets got updated correctly + final ProductProjection productProjection = + CTP_TARGET_CLIENT .execute(ProductProjectionByKeyGet.of(productKey, ProductProjectionType.STAGED)) - .toCompletableFuture().join(); + .toCompletableFuture() + .join(); - assertThat(productProjection).isNotNull(); - final List createdAssets = productProjection.getMasterVariant().getAssets(); - assertAssetsAreEqual(createdAssets, assetDraftsToCreateOnExistingProductOnSourceProject); + assertThat(productProjection).isNotNull(); + final List createdAssets = productProjection.getMasterVariant().getAssets(); + assertAssetsAreEqual(createdAssets, assetDraftsToCreateOnExistingProductOnSourceProject); - // Assert update actions - final Map assetsKeyToIdMap = createdAssets.stream().collect(toMap(Asset::getKey, Asset::getId)); + // Assert update actions + final Map assetsKeyToIdMap = + createdAssets.stream().collect(toMap(Asset::getKey, Asset::getId)); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( RemoveAsset.ofVariantIdWithKey(1, "1", true), ChangeAssetName.ofAssetKeyAndVariantId(1, "2", ofEnglish("newName"), true), SetAssetCustomType.ofVariantIdAndAssetKey(1, "2", null, true), - SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey(1, "3", BOOLEAN_CUSTOM_FIELD_NAME, - customFieldsJsonMap.get(BOOLEAN_CUSTOM_FIELD_NAME), true), - SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey(1, "3", LOCALISED_STRING_CUSTOM_FIELD_NAME, - null, true), - ChangeAssetOrder.ofVariantId(1, asList(assetsKeyToIdMap.get("3"), assetsKeyToIdMap.get("2")), true), - AddAsset.ofVariantId(1, createAssetDraft("4", ofEnglish("4"), - targetAssetCustomType.getId())) - .withStaged(true).withPosition(0) - ); - } + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, + "3", + BOOLEAN_CUSTOM_FIELD_NAME, + customFieldsJsonMap.get(BOOLEAN_CUSTOM_FIELD_NAME), + true), + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, "3", LOCALISED_STRING_CUSTOM_FIELD_NAME, null, true), + ChangeAssetOrder.ofVariantId( + 1, asList(assetsKeyToIdMap.get("3"), assetsKeyToIdMap.get("2")), true), + AddAsset.ofVariantId( + 1, createAssetDraft("4", ofEnglish("4"), targetAssetCustomType.getId())) + .withStaged(true) + .withPosition(0)); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/templates/beforeupdatecallback/KeepOtherVariantsSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/templates/beforeupdatecallback/KeepOtherVariantsSyncIT.java index 14dd545f33..95f9af4f70 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/templates/beforeupdatecallback/KeepOtherVariantsSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/products/templates/beforeupdatecallback/KeepOtherVariantsSyncIT.java @@ -1,5 +1,20 @@ package com.commercetools.sync.integration.ctpprojectsource.products.templates.beforeupdatecallback; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_NO_VARS_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_WITH_VARS_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.producttypes.ProductType.referenceOfId; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -15,124 +30,115 @@ import io.sphere.sdk.products.queries.ProductQuery; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.queries.QueryPredicate; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_NO_VARS_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_WITH_VARS_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.producttypes.ProductType.referenceOfId; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; - class KeepOtherVariantsSyncIT { - private static ProductType productType; - private Product oldProduct; - private ProductSyncOptions syncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - - /** - * Delete all product related test data from target project. Then creates a productType for the products of the - * target CTP project. - */ - @BeforeAll - static void setupAllTests() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - /** - * 1. Clears all sync collections used for test assertions. - * 2. Deletes all products from target CTP project - * 3. Creates an instance for {@link ProductSyncOptions} that will be used in the test. - * 4. Creates a product in the target CTP project with 1 variant other than the master - * variant. - */ - @BeforeEach - void setupPerTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptions = getProductSyncOptions(); - final ProductDraft productDraft = - createProductDraftBuilder(PRODUCT_WITH_VARS_RESOURCE_PATH, productType.toReference()) - .build(); - oldProduct = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - } - - private ProductSyncOptions getProductSyncOptions() { - final QuadConsumer, Optional, List>> - errorCallBack = (exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }; - - final TriConsumer, Optional> warningCallBack = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallBack) - .warningCallback(warningCallBack) - .beforeUpdateCallback(KeepOtherVariantsSync::keepOtherVariants) - .build(); - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withRemovedVariants_shouldNotRemoveVariants() { - final ProductDraft productDraft = - createProductDraftBuilder(PRODUCT_NO_VARS_RESOURCE_PATH, referenceOfId(productType.getKey())) - .build(); - - // old product has 1 variant - assertThat(oldProduct.getMasterData().getStaged().getVariants()).hasSize(1); - // new product has no variants - assertThat(productDraft.getVariants()).isEmpty(); - - final ProductSync productSync = new ProductSync(syncOptions); - - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - // Assert that the variant wasn't removed - final Optional productOptional = CTP_TARGET_CLIENT - .execute(ProductQuery.of() - .withPredicates(QueryPredicate.of(format("key = \"%s\"", oldProduct.getKey())))) - .toCompletableFuture().join().head(); - - assertThat(productOptional).isNotEmpty(); - final Product fetchedProduct = productOptional.get(); - assertThat(fetchedProduct.getMasterData().getCurrent().getVariants()).hasSize(2); - } + private static ProductType productType; + private Product oldProduct; + private ProductSyncOptions syncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + /** + * Delete all product related test data from target project. Then creates a productType for the + * products of the target CTP project. + */ + @BeforeAll + static void setupAllTests() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + /** + * 1. Clears all sync collections used for test assertions. 2. Deletes all products from target + * CTP project 3. Creates an instance for {@link ProductSyncOptions} that will be used in the + * test. 4. Creates a product in the target CTP project with 1 variant other than the master + * variant. + */ + @BeforeEach + void setupPerTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptions = getProductSyncOptions(); + final ProductDraft productDraft = + createProductDraftBuilder(PRODUCT_WITH_VARS_RESOURCE_PATH, productType.toReference()) + .build(); + oldProduct = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + } + + private ProductSyncOptions getProductSyncOptions() { + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallBack = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }; + + final TriConsumer, Optional> warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback(errorCallBack) + .warningCallback(warningCallBack) + .beforeUpdateCallback(KeepOtherVariantsSync::keepOtherVariants) + .build(); + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withRemovedVariants_shouldNotRemoveVariants() { + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_NO_VARS_RESOURCE_PATH, referenceOfId(productType.getKey())) + .build(); + + // old product has 1 variant + assertThat(oldProduct.getMasterData().getStaged().getVariants()).hasSize(1); + // new product has no variants + assertThat(productDraft.getVariants()).isEmpty(); + + final ProductSync productSync = new ProductSync(syncOptions); + + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + // Assert that the variant wasn't removed + final Optional productOptional = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of() + .withPredicates(QueryPredicate.of(format("key = \"%s\"", oldProduct.getKey())))) + .toCompletableFuture() + .join() + .head(); + + assertThat(productOptional).isNotEmpty(); + final Product fetchedProduct = productOptional.get(); + assertThat(fetchedProduct.getMasterData().getCurrent().getVariants()).hasSize(2); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/producttypes/ProductTypeSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/producttypes/ProductTypeSyncIT.java index 169e3b4462..740fd9df9b 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/producttypes/ProductTypeSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/producttypes/ProductTypeSyncIT.java @@ -1,5 +1,13 @@ package com.commercetools.sync.integration.ctpprojectsource.producttypes; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypesFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateSourceProject; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateTargetProject; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.producttypes.ProductTypeSync; import com.commercetools.sync.producttypes.ProductTypeSyncOptions; import com.commercetools.sync.producttypes.ProductTypeSyncOptionsBuilder; @@ -10,138 +18,126 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.ProductTypeDraftBuilder; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypesFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateSourceProject; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateTargetProject; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductTypeSyncIT { - /** - * Deletes product types from source and target CTP projects. - * Populates source and target CTP projects with test data. - */ - @BeforeEach - void setup() { - deleteProductTypesFromTargetAndSource(); - populateSourceProject(); - populateTargetProject(); - } - - /** - * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} projects that - * were set up in this test class. - */ - @AfterAll - static void tearDown() { - deleteProductTypesFromTargetAndSource(); - } - - @Test - void sync_WithoutUpdates_ShouldReturnProperStatistics() { - // preparation - final List productTypes = CTP_SOURCE_CLIENT - .execute(ProductTypeQuery.of()) - .toCompletableFuture().join().getResults(); - - final List productTypeDrafts = productTypes - .stream() + /** + * Deletes product types from source and target CTP projects. Populates source and target CTP + * projects with test data. + */ + @BeforeEach + void setup() { + deleteProductTypesFromTargetAndSource(); + populateSourceProject(); + populateTargetProject(); + } + + /** + * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} + * projects that were set up in this test class. + */ + @AfterAll + static void tearDown() { + deleteProductTypesFromTargetAndSource(); + } + + @Test + void sync_WithoutUpdates_ShouldReturnProperStatistics() { + // preparation + final List productTypes = + CTP_SOURCE_CLIENT.execute(ProductTypeQuery.of()).toCompletableFuture().join().getResults(); + + final List productTypeDrafts = + productTypes.stream() .map(ProductTypeDraftBuilder::of) .map(ProductTypeDraftBuilder::build) .collect(Collectors.toList()); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(productTypeDrafts) - .toCompletableFuture().join(); + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(productTypeDrafts).toCompletableFuture().join(); - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(productTypeSyncStatistics).hasValues(2, 1, 0, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 product types were processed in total" + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(productTypeSyncStatistics).hasValues(2, 1, 0, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 product types were processed in total" + " (1 created, 0 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - } - - @Test - void sync_WithUpdates_ShouldReturnProperStatistics() { - // preparation - final List productTypes = CTP_SOURCE_CLIENT - .execute(ProductTypeQuery.of()) - .toCompletableFuture().join().getResults(); - - final List productTypeDrafts = productTypes - .stream() - .map(productType -> { - final List attributeDefinitionDrafts = productType - .getAttributes() - .stream() - .map(attribute -> AttributeDefinitionDraftBuilder.of(attribute).build()) - .collect(Collectors.toList()); - - return ProductTypeDraftBuilder - .of( - productType.getKey(), - "newName", - productType.getDescription(), - attributeDefinitionDrafts) - .build(); - }) + } + + @Test + void sync_WithUpdates_ShouldReturnProperStatistics() { + // preparation + final List productTypes = + CTP_SOURCE_CLIENT.execute(ProductTypeQuery.of()).toCompletableFuture().join().getResults(); + + final List productTypeDrafts = + productTypes.stream() + .map( + productType -> { + final List attributeDefinitionDrafts = + productType.getAttributes().stream() + .map(attribute -> AttributeDefinitionDraftBuilder.of(attribute).build()) + .collect(Collectors.toList()); + + return ProductTypeDraftBuilder.of( + productType.getKey(), + "newName", + productType.getDescription(), + attributeDefinitionDrafts) + .build(); + }) .collect(Collectors.toList()); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(productTypeDrafts) - .toCompletableFuture().join(); + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(productTypeDrafts).toCompletableFuture().join(); - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(productTypeSyncStatistics).hasValues(2, 1, 1, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 product types were processed in total" + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(productTypeSyncStatistics).hasValues(2, 1, 1, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 product types were processed in total" + " (1 created, 1 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - } + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/producttypes/ProductTypeWithNestedAttributeSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/producttypes/ProductTypeWithNestedAttributeSyncIT.java index b5df3b8835..7a43bace2d 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/producttypes/ProductTypeWithNestedAttributeSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/producttypes/ProductTypeWithNestedAttributeSyncIT.java @@ -1,5 +1,16 @@ package com.commercetools.sync.integration.ctpprojectsource.producttypes; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateSourcesProjectWithNestedAttributes; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateTargetProjectWithNestedAttributes; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.removeAttributeReferencesAndDeleteProductTypes; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.producttypes.utils.ProductTypeReferenceResolutionUtils.buildProductTypeQuery; +import static com.commercetools.sync.producttypes.utils.ProductTypeReferenceResolutionUtils.mapToProductTypeDrafts; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.producttypes.ProductTypeSync; import com.commercetools.sync.producttypes.ProductTypeSyncOptions; import com.commercetools.sync.producttypes.ProductTypeSyncOptionsBuilder; @@ -12,231 +23,217 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.ProductTypeDraftBuilder; import io.sphere.sdk.producttypes.commands.updateactions.ChangeAttributeDefinitionLabel; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateSourcesProjectWithNestedAttributes; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateTargetProjectWithNestedAttributes; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.removeAttributeReferencesAndDeleteProductTypes; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.producttypes.utils.ProductTypeReferenceResolutionUtils.buildProductTypeQuery; -import static com.commercetools.sync.producttypes.utils.ProductTypeReferenceResolutionUtils.mapToProductTypeDrafts; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductTypeWithNestedAttributeSyncIT { - private ProductTypeSyncOptions productTypeSyncOptions; - private List> builtUpdateActions; - private List errorMessages; - private List exceptions; - - /** - * Deletes product types from source and target CTP projects. - * Populates source and target CTP projects with test data. - */ - @BeforeEach - void setup() { - removeAttributeReferencesAndDeleteProductTypes(CTP_SOURCE_CLIENT); - removeAttributeReferencesAndDeleteProductTypes(CTP_TARGET_CLIENT); - populateSourcesProjectWithNestedAttributes(); - - builtUpdateActions = new ArrayList<>(); - errorMessages = new ArrayList<>(); - exceptions = new ArrayList<>(); - - productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .beforeUpdateCallback((actions, draft, oldProductType) -> { - builtUpdateActions.addAll(actions); - return actions; - }) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + private ProductTypeSyncOptions productTypeSyncOptions; + private List> builtUpdateActions; + private List errorMessages; + private List exceptions; + + /** + * Deletes product types from source and target CTP projects. Populates source and target CTP + * projects with test data. + */ + @BeforeEach + void setup() { + removeAttributeReferencesAndDeleteProductTypes(CTP_SOURCE_CLIENT); + removeAttributeReferencesAndDeleteProductTypes(CTP_TARGET_CLIENT); + populateSourcesProjectWithNestedAttributes(); + + builtUpdateActions = new ArrayList<>(); + errorMessages = new ArrayList<>(); + exceptions = new ArrayList<>(); + + productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .beforeUpdateCallback( + (actions, draft, oldProductType) -> { + builtUpdateActions.addAll(actions); + return actions; + }) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - } - - /** - * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} projects that - * were set up in this test class. - */ - @AfterAll - static void tearDown() { - removeAttributeReferencesAndDeleteProductTypes(CTP_SOURCE_CLIENT); - removeAttributeReferencesAndDeleteProductTypes(CTP_TARGET_CLIENT); - } - - @Test - void sync_WithEmptyTargetProject_ShouldReturnProperStatistics() { - // preparation - final List productTypes = CTP_SOURCE_CLIENT + } + + /** + * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} + * projects that were set up in this test class. + */ + @AfterAll + static void tearDown() { + removeAttributeReferencesAndDeleteProductTypes(CTP_SOURCE_CLIENT); + removeAttributeReferencesAndDeleteProductTypes(CTP_TARGET_CLIENT); + } + + @Test + void sync_WithEmptyTargetProject_ShouldReturnProperStatistics() { + // preparation + final List productTypes = + CTP_SOURCE_CLIENT .execute(buildProductTypeQuery(1)) .toCompletableFuture() .join() .getResults(); - final List productTypeDrafts = - mapToProductTypeDrafts(productTypes); + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(productTypeDrafts) - .toCompletableFuture() - .join(); - - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).isEmpty(); - assertThat(productTypeSyncStatistics).hasValues(4, 4, 0, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 4 product types were processed in total" + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(productTypeDrafts).toCompletableFuture().join(); + + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions).isEmpty(); + assertThat(productTypeSyncStatistics).hasValues(4, 4, 0, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 4 product types were processed in total" + " (4 created, 0 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - } + } - @Test - void sync_WithOneDraftPerBatchOnEmptyProject_ShouldReturnProperStatistics() { - // preparation - final List productTypes = CTP_SOURCE_CLIENT + @Test + void sync_WithOneDraftPerBatchOnEmptyProject_ShouldReturnProperStatistics() { + // preparation + final List productTypes = + CTP_SOURCE_CLIENT .execute(buildProductTypeQuery(1)) .toCompletableFuture() .join() .getResults(); - final List productTypeDrafts = - mapToProductTypeDrafts(productTypes); + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) .batchSize(1) - .beforeUpdateCallback((actions, draft, oldProductType) -> { - builtUpdateActions.addAll(actions); - return actions; - }) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + .beforeUpdateCallback( + (actions, draft, oldProductType) -> { + builtUpdateActions.addAll(actions); + return actions; + }) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(productTypeDrafts) - .toCompletableFuture() - .join(); - - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).isEmpty(); - assertThat(productTypeSyncStatistics).hasValues(4, 4, 0, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 4 product types were processed in total" + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(productTypeDrafts).toCompletableFuture().join(); + + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions).isEmpty(); + assertThat(productTypeSyncStatistics).hasValues(4, 4, 0, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 4 product types were processed in total" + " (4 created, 0 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - } - - @Test - void sync_WithoutUpdates_ShouldReturnProperStatistics() { - // preparation - populateTargetProjectWithNestedAttributes(); - final List productTypes = CTP_SOURCE_CLIENT - .execute(buildProductTypeQuery(1)) - .toCompletableFuture() - .join() - .getResults(); - - final List productTypeDrafts = - mapToProductTypeDrafts(productTypes); - - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(productTypeDrafts) + } + + @Test + void sync_WithoutUpdates_ShouldReturnProperStatistics() { + // preparation + populateTargetProjectWithNestedAttributes(); + final List productTypes = + CTP_SOURCE_CLIENT + .execute(buildProductTypeQuery(1)) .toCompletableFuture() - .join(); - - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).isEmpty(); - assertThat(productTypeSyncStatistics).hasValues(4, 1, 0, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 4 product types were processed in total" + .join() + .getResults(); + + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); + + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(productTypeDrafts).toCompletableFuture().join(); + + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions).isEmpty(); + assertThat(productTypeSyncStatistics).hasValues(4, 1, 0, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 4 product types were processed in total" + " (1 created, 0 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - } - - @Test - void sync_WithUpdates_ShouldReturnProperStatistics() { - // preparation - populateTargetProjectWithNestedAttributes(); - final List productTypes = CTP_SOURCE_CLIENT - .execute(buildProductTypeQuery(1)) - .toCompletableFuture() - .join() - .getResults(); - - //only update the nested types - final List productTypeDrafts = mapToProductTypeDrafts(productTypes) - .stream() - .map(productType -> { - final List attributeDefinitionDrafts = productType - .getAttributes() - .stream() - .map(attribute -> { + } + + @Test + void sync_WithUpdates_ShouldReturnProperStatistics() { + // preparation + populateTargetProjectWithNestedAttributes(); + final List productTypes = + CTP_SOURCE_CLIENT + .execute(buildProductTypeQuery(1)) + .toCompletableFuture() + .join() + .getResults(); + + // only update the nested types + final List productTypeDrafts = + mapToProductTypeDrafts(productTypes).stream() + .map( + productType -> { + final List attributeDefinitionDrafts = + productType.getAttributes().stream() + .map( + attribute -> { if (attribute.getAttributeType() instanceof NestedAttributeType) { - return AttributeDefinitionDraftBuilder.of(attribute) - .label(ofEnglish("new-label")) - .build(); + return AttributeDefinitionDraftBuilder.of(attribute) + .label(ofEnglish("new-label")) + .build(); } return AttributeDefinitionDraftBuilder.of(attribute).build(); - }) - .collect(Collectors.toList()); + }) + .collect(Collectors.toList()); - return ProductTypeDraftBuilder - .of(productType) - .attributes(attributeDefinitionDrafts) - .build(); + return ProductTypeDraftBuilder.of(productType) + .attributes(attributeDefinitionDrafts) + .build(); }) - .collect(Collectors.toList()); + .collect(Collectors.toList()); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(productTypeDrafts) - .toCompletableFuture().join(); + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(productTypeDrafts).toCompletableFuture().join(); - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).containsExactly( + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions) + .containsExactly( ChangeAttributeDefinitionLabel.of("nestedattr", ofEnglish("new-label")), - ChangeAttributeDefinitionLabel.of("nestedattr2", ofEnglish("new-label")) - ); - assertThat(productTypeSyncStatistics).hasValues(4, 1, 1, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()).isEqualTo( + ChangeAttributeDefinitionLabel.of("nestedattr2", ofEnglish("new-label"))); + assertThat(productTypeSyncStatistics).hasValues(4, 1, 1, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( "Summary: 4 product types were processed in total" + " (1 created, 1 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - } + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/shoppinglists/ShoppingListSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/shoppinglists/ShoppingListSyncIT.java index 986644d2c9..109dd028a0 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/shoppinglists/ShoppingListSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/shoppinglists/ShoppingListSyncIT.java @@ -1,5 +1,16 @@ package com.commercetools.sync.integration.ctpprojectsource.shoppinglists; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.createSampleCustomerJaneDoe; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.createSampleShoppingListCarrotCake; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.createShoppingList; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.deleteShoppingListTestData; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.buildShoppingListQuery; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.mapToShoppingListDrafts; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics; import com.commercetools.sync.shoppinglists.ShoppingListSync; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; @@ -16,153 +27,142 @@ import io.sphere.sdk.shoppinglists.commands.updateactions.ChangeName; import io.sphere.sdk.shoppinglists.commands.updateactions.SetAnonymousId; import io.sphere.sdk.shoppinglists.commands.updateactions.SetCustomer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.createSampleCustomerJaneDoe; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.createSampleShoppingListCarrotCake; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.createShoppingList; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.deleteShoppingListTestData; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.buildShoppingListQuery; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.mapToShoppingListDrafts; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ShoppingListSyncIT { - private List errorMessages; - private List warningMessages; - private List exceptions; - private List> updateActionList; - private ShoppingListSync shoppingListSync; - - @BeforeEach - void setup() { - deleteShoppingListSyncTestDataFromProjects(); - - createSampleShoppingListCarrotCake(CTP_SOURCE_CLIENT); - createShoppingList(CTP_SOURCE_CLIENT, "second-shopping-list", "second-shopping-list-key"); - - createSampleShoppingListCarrotCake(CTP_TARGET_CLIENT); - - setUpShoppingListSync(); - } - - @AfterAll - static void tearDown() { - deleteShoppingListSyncTestDataFromProjects(); - } - - private static void deleteShoppingListSyncTestDataFromProjects() { - deleteShoppingListTestData(CTP_SOURCE_CLIENT); - deleteShoppingListTestData(CTP_TARGET_CLIENT); - } - - private void setUpShoppingListSync() { - errorMessages = new ArrayList<>(); - warningMessages = new ArrayList<>(); - exceptions = new ArrayList<>(); - updateActionList = new ArrayList<>(); - - final ShoppingListSyncOptions shoppingListSyncOptions = ShoppingListSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .warningCallback((exception, oldResource, newResource) - -> warningMessages.add(exception.getMessage())) - .beforeUpdateCallback((updateActions, customerDraft, customer) -> { - updateActionList.addAll(Objects.requireNonNull(updateActions)); - return updateActions; - }) + private List errorMessages; + private List warningMessages; + private List exceptions; + private List> updateActionList; + private ShoppingListSync shoppingListSync; + + @BeforeEach + void setup() { + deleteShoppingListSyncTestDataFromProjects(); + + createSampleShoppingListCarrotCake(CTP_SOURCE_CLIENT); + createShoppingList(CTP_SOURCE_CLIENT, "second-shopping-list", "second-shopping-list-key"); + + createSampleShoppingListCarrotCake(CTP_TARGET_CLIENT); + + setUpShoppingListSync(); + } + + @AfterAll + static void tearDown() { + deleteShoppingListSyncTestDataFromProjects(); + } + + private static void deleteShoppingListSyncTestDataFromProjects() { + deleteShoppingListTestData(CTP_SOURCE_CLIENT); + deleteShoppingListTestData(CTP_TARGET_CLIENT); + } + + private void setUpShoppingListSync() { + errorMessages = new ArrayList<>(); + warningMessages = new ArrayList<>(); + exceptions = new ArrayList<>(); + updateActionList = new ArrayList<>(); + + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .warningCallback( + (exception, oldResource, newResource) -> + warningMessages.add(exception.getMessage())) + .beforeUpdateCallback( + (updateActions, customerDraft, customer) -> { + updateActionList.addAll(Objects.requireNonNull(updateActions)); + return updateActions; + }) .build(); - shoppingListSync = new ShoppingListSync(shoppingListSyncOptions); - } + shoppingListSync = new ShoppingListSync(shoppingListSyncOptions); + } - @Test - void sync_WithoutUpdates_ShouldReturnProperStatistics() { + @Test + void sync_WithoutUpdates_ShouldReturnProperStatistics() { - final List shoppingLists = CTP_SOURCE_CLIENT + final List shoppingLists = + CTP_SOURCE_CLIENT .execute(buildShoppingListQuery()) .toCompletableFuture() .join() .getResults(); - final List shoppingListDrafts = mapToShoppingListDrafts(shoppingLists); + final List shoppingListDrafts = mapToShoppingListDrafts(shoppingLists); - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(shoppingListDrafts) - .toCompletableFuture() - .join(); + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(shoppingListDrafts).toCompletableFuture().join(); - assertThat(errorMessages).isEmpty(); - assertThat(warningMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActionList).isEmpty(); + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActionList).isEmpty(); - assertThat(shoppingListSyncStatistics).hasValues(2, 1, 0, 0); - assertThat(shoppingListSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 shopping lists were processed in total " + assertThat(shoppingListSyncStatistics).hasValues(2, 1, 0, 0); + assertThat(shoppingListSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 shopping lists were processed in total " + "(1 created, 0 updated and 0 failed to sync)."); - } + } - @Test - void sync_WithUpdatedCustomerOnShoppingList_ShouldReturnProperStatistics() { - final List shoppingLists = CTP_SOURCE_CLIENT + @Test + void sync_WithUpdatedCustomerOnShoppingList_ShouldReturnProperStatistics() { + final List shoppingLists = + CTP_SOURCE_CLIENT .execute(buildShoppingListQuery()) .toCompletableFuture() .join() .getResults(); - createSampleCustomerJaneDoe(CTP_SOURCE_CLIENT); - final Customer sampleCustomerJaneDoe = createSampleCustomerJaneDoe(CTP_TARGET_CLIENT); + createSampleCustomerJaneDoe(CTP_SOURCE_CLIENT); + final Customer sampleCustomerJaneDoe = createSampleCustomerJaneDoe(CTP_TARGET_CLIENT); - final List updatedShoppingListDrafts = - mapToShoppingListDrafts(shoppingLists) - .stream() - .map(shoppingListDraft -> - ShoppingListDraftBuilder - .of(shoppingListDraft) + final List updatedShoppingListDrafts = + mapToShoppingListDrafts(shoppingLists).stream() + .map( + shoppingListDraft -> + ShoppingListDraftBuilder.of(shoppingListDraft) .name(LocalizedString.ofEnglish("second-shopping-list")) .anonymousId(null) .customer(ResourceIdentifier.ofKey(sampleCustomerJaneDoe.getKey())) .build()) - .collect(Collectors.toList()); - - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(updatedShoppingListDrafts) - .toCompletableFuture() - .join(); - - assertThat(errorMessages).isEmpty(); - assertThat(warningMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - - // order is important, otherwise the error below could occur: - //"message" : "The resource was already claimed by a customer.. - //"action" : { - // "action" : "setAnonymousId" - //} - assertThat(updateActionList).containsExactly( + .collect(Collectors.toList()); + + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(updatedShoppingListDrafts).toCompletableFuture().join(); + + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + + // order is important, otherwise the error below could occur: + // "message" : "The resource was already claimed by a customer.. + // "action" : { + // "action" : "setAnonymousId" + // } + assertThat(updateActionList) + .containsExactly( ChangeName.of(LocalizedString.ofEnglish("second-shopping-list")), SetAnonymousId.of(null), - SetCustomer.of(Reference.of(Customer.referenceTypeId(), sampleCustomerJaneDoe.getId())) - ); + SetCustomer.of( + Reference.of(Customer.referenceTypeId(), sampleCustomerJaneDoe.getId()))); - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(2, 1, 1, 0); - assertThat(shoppingListSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 shopping lists were processed in total " + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(2, 1, 1, 0); + assertThat(shoppingListSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 shopping lists were processed in total " + "(1 created, 1 updated and 0 failed to sync)."); - } + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/types/TypeSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/types/TypeSyncIT.java index ac42f0fcf2..e2aa3bef28 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/types/TypeSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/ctpprojectsource/types/TypeSyncIT.java @@ -1,5 +1,13 @@ package com.commercetools.sync.integration.ctpprojectsource.types; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.populateSourceProject; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.populateTargetProject; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.types.TypeSync; import com.commercetools.sync.types.TypeSyncOptions; import com.commercetools.sync.types.TypeSyncOptionsBuilder; @@ -9,130 +17,123 @@ import io.sphere.sdk.types.TypeDraft; import io.sphere.sdk.types.TypeDraftBuilder; import io.sphere.sdk.types.queries.TypeQuery; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.populateSourceProject; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.populateTargetProject; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class TypeSyncIT { - /** - * Deletes types from source and target CTP projects. - * Populates source and target CTP projects with test data. - */ - @BeforeEach - void setup() { - deleteTypesFromTargetAndSource(); - populateSourceProject(); - populateTargetProject(); - } - - /** - * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} projects that - * were set up in this test class. - */ - @AfterAll - static void tearDown() { - deleteTypesFromTargetAndSource(); - } - - @Test - void sync_WithoutUpdates_ShouldReturnProperStatistics() { - // preparation - final List types = CTP_SOURCE_CLIENT - .execute(TypeQuery.of()) - .toCompletableFuture().join().getResults(); - - final List typeDrafts = types - .stream() - .map(type -> TypeDraftBuilder.of(type.getKey(), type.getName(), type.getResourceTypeIds()) - .description(type.getDescription()) - .fieldDefinitions(type.getFieldDefinitions())) + /** + * Deletes types from source and target CTP projects. Populates source and target CTP projects + * with test data. + */ + @BeforeEach + void setup() { + deleteTypesFromTargetAndSource(); + populateSourceProject(); + populateTargetProject(); + } + + /** + * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} + * projects that were set up in this test class. + */ + @AfterAll + static void tearDown() { + deleteTypesFromTargetAndSource(); + } + + @Test + void sync_WithoutUpdates_ShouldReturnProperStatistics() { + // preparation + final List types = + CTP_SOURCE_CLIENT.execute(TypeQuery.of()).toCompletableFuture().join().getResults(); + + final List typeDrafts = + types.stream() + .map( + type -> + TypeDraftBuilder.of(type.getKey(), type.getName(), type.getResourceTypeIds()) + .description(type.getDescription()) + .fieldDefinitions(type.getFieldDefinitions())) .map(TypeDraftBuilder::build) .collect(Collectors.toList()); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final TypeSync typeSync = new TypeSync(typeSyncOptions); + final TypeSync typeSync = new TypeSync(typeSyncOptions); - // test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(typeDrafts) - .toCompletableFuture().join(); + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(typeDrafts).toCompletableFuture().join(); - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(typeSyncStatistics).hasValues(2, 1, 0, 0); - assertThat(typeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 types were processed in total" + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(typeSyncStatistics).hasValues(2, 1, 0, 0); + assertThat(typeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 types were processed in total" + " (1 created, 0 updated and 0 failed to sync)."); - - } - - @Test - void sync_WithUpdates_ShouldReturnProperStatistics() { - // preparation - final List types = CTP_SOURCE_CLIENT - .execute(TypeQuery.of()) - .toCompletableFuture().join().getResults(); - - final List typeDrafts = types - .stream() - .map(type -> TypeDraftBuilder.of(type.getKey(), - LocalizedString.ofEnglish("updated_name"), type.getResourceTypeIds()) - .description(type.getDescription()) - .fieldDefinitions(type.getFieldDefinitions())) + } + + @Test + void sync_WithUpdates_ShouldReturnProperStatistics() { + // preparation + final List types = + CTP_SOURCE_CLIENT.execute(TypeQuery.of()).toCompletableFuture().join().getResults(); + + final List typeDrafts = + types.stream() + .map( + type -> + TypeDraftBuilder.of( + type.getKey(), + LocalizedString.ofEnglish("updated_name"), + type.getResourceTypeIds()) + .description(type.getDescription()) + .fieldDefinitions(type.getFieldDefinitions())) .map(TypeDraftBuilder::build) .collect(Collectors.toList()); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final TypeSync typeSync = new TypeSync(typeSyncOptions); + final TypeSync typeSync = new TypeSync(typeSyncOptions); - // test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(typeDrafts) - .toCompletableFuture().join(); + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(typeDrafts).toCompletableFuture().join(); - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(typeSyncStatistics).hasValues(2, 1, 1, 0); - assertThat(typeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 types were processed in total" + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(typeSyncStatistics).hasValues(2, 1, 1, 0); + assertThat(typeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 types were processed in total" + " (1 created, 1 updated and 0 failed to sync)."); - } + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/cartdiscounts/CartDiscountSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/cartdiscounts/CartDiscountSyncIT.java index 78caf20f39..1a2cdcbae5 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/cartdiscounts/CartDiscountSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/cartdiscounts/CartDiscountSyncIT.java @@ -1,46 +1,5 @@ package com.commercetools.sync.integration.externalsource.cartdiscounts; -import com.commercetools.sync.cartdiscounts.CartDiscountSync; -import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; -import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; -import com.commercetools.sync.cartdiscounts.helpers.CartDiscountSyncStatistics; -import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; -import com.commercetools.sync.commons.exceptions.SyncException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import io.sphere.sdk.cartdiscounts.CartDiscount; -import io.sphere.sdk.cartdiscounts.CartDiscountDraft; -import io.sphere.sdk.cartdiscounts.CartDiscountDraftBuilder; -import io.sphere.sdk.cartdiscounts.commands.CartDiscountCreateCommand; -import io.sphere.sdk.cartdiscounts.commands.CartDiscountUpdateCommand; -import io.sphere.sdk.cartdiscounts.commands.updateactions.ChangeCartPredicate; -import io.sphere.sdk.cartdiscounts.commands.updateactions.ChangeTarget; -import io.sphere.sdk.cartdiscounts.commands.updateactions.ChangeValue; -import io.sphere.sdk.cartdiscounts.commands.updateactions.SetCustomField; -import io.sphere.sdk.cartdiscounts.commands.updateactions.SetCustomType; -import io.sphere.sdk.cartdiscounts.queries.CartDiscountQuery; -import io.sphere.sdk.client.BadGatewayException; -import io.sphere.sdk.client.ConcurrentModificationException; -import io.sphere.sdk.client.ErrorResponseException; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.queries.PagedQueryResult; -import io.sphere.sdk.types.CustomFieldsDraft; -import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletionException; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_CART_PREDICATE_1; import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_CART_PREDICATE_2; @@ -78,651 +37,726 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import com.commercetools.sync.cartdiscounts.CartDiscountSync; +import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; +import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; +import com.commercetools.sync.cartdiscounts.helpers.CartDiscountSyncStatistics; +import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; +import com.commercetools.sync.commons.exceptions.SyncException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import io.sphere.sdk.cartdiscounts.CartDiscount; +import io.sphere.sdk.cartdiscounts.CartDiscountDraft; +import io.sphere.sdk.cartdiscounts.CartDiscountDraftBuilder; +import io.sphere.sdk.cartdiscounts.commands.CartDiscountCreateCommand; +import io.sphere.sdk.cartdiscounts.commands.CartDiscountUpdateCommand; +import io.sphere.sdk.cartdiscounts.commands.updateactions.ChangeCartPredicate; +import io.sphere.sdk.cartdiscounts.commands.updateactions.ChangeTarget; +import io.sphere.sdk.cartdiscounts.commands.updateactions.ChangeValue; +import io.sphere.sdk.cartdiscounts.commands.updateactions.SetCustomField; +import io.sphere.sdk.cartdiscounts.commands.updateactions.SetCustomType; +import io.sphere.sdk.cartdiscounts.queries.CartDiscountQuery; +import io.sphere.sdk.client.BadGatewayException; +import io.sphere.sdk.client.ConcurrentModificationException; +import io.sphere.sdk.client.ErrorResponseException; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.queries.PagedQueryResult; +import io.sphere.sdk.types.CustomFieldsDraft; +import io.sphere.sdk.types.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class CartDiscountSyncIT { - /** - * Deletes cart discounts from the target CTP projects. - * Populates the target CTP project with test data. - */ - @BeforeEach - void setup() { - deleteCartDiscounts(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - populateTargetProject(); - } - - @AfterAll - static void tearDown() { - deleteCartDiscounts(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - } - - @Test - void sync_WithUpdatedCartDiscount_WithNewCartPredicate_ShouldUpdateCartDiscountWithNewCartPredicate() { - // preparation - final CartDiscountDraft newCartDiscountDraftWithExistingKey = - CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) - .cartPredicate(CART_DISCOUNT_CART_PREDICATE_2) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final List> updateActionsList = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .beforeUpdateCallback((updateActions, newCartDiscount, oldCartDiscount) -> { - updateActionsList.addAll(updateActions); - return updateActions; - }) + /** + * Deletes cart discounts from the target CTP projects. Populates the target CTP project with test + * data. + */ + @BeforeEach + void setup() { + deleteCartDiscounts(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + populateTargetProject(); + } + + @AfterAll + static void tearDown() { + deleteCartDiscounts(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + } + + @Test + void + sync_WithUpdatedCartDiscount_WithNewCartPredicate_ShouldUpdateCartDiscountWithNewCartPredicate() { + // preparation + final CartDiscountDraft newCartDiscountDraftWithExistingKey = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) + .cartPredicate(CART_DISCOUNT_CART_PREDICATE_2) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final List> updateActionsList = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .beforeUpdateCallback( + (updateActions, newCartDiscount, oldCartDiscount) -> { + updateActionsList.addAll(updateActions); + return updateActions; + }) + .build(); + + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithExistingKey)) .toCompletableFuture() .join(); - //assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActionsList).containsExactly(ChangeCartPredicate.of(CART_DISCOUNT_CART_PREDICATE_2)); - assertThat(cartDiscountSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 cart discounts were processed in total" + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActionsList) + .containsExactly(ChangeCartPredicate.of(CART_DISCOUNT_CART_PREDICATE_2)); + assertThat(cartDiscountSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 cart discounts were processed in total" + " (0 created, 1 updated and 0 failed to sync)."); - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); - } - - @Test - void sync_WithUpdatedCartDiscount_WithNewValue_ShouldUpdateCartDiscountWithNewValue() { - // preparation - final CartDiscountDraft newCartDiscountDraftWithExistingKey = - CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) - .value(CART_DISCOUNT_VALUE_2) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final List> updateActionsList = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .beforeUpdateCallback((updateActions, newCartDiscount, oldCartDiscount) -> { - updateActionsList.addAll(updateActions); - return updateActions; - }) + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); + } + + @Test + void sync_WithUpdatedCartDiscount_WithNewValue_ShouldUpdateCartDiscountWithNewValue() { + // preparation + final CartDiscountDraft newCartDiscountDraftWithExistingKey = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1).value(CART_DISCOUNT_VALUE_2).build(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final List> updateActionsList = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .beforeUpdateCallback( + (updateActions, newCartDiscount, oldCartDiscount) -> { + updateActionsList.addAll(updateActions); + return updateActions; + }) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithExistingKey)) .toCompletableFuture() .join(); - //assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActionsList).containsExactly(ChangeValue.of(CART_DISCOUNT_VALUE_2)); - assertThat(cartDiscountSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 cart discounts were processed in total" + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActionsList).containsExactly(ChangeValue.of(CART_DISCOUNT_VALUE_2)); + assertThat(cartDiscountSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 cart discounts were processed in total" + " (0 created, 1 updated and 0 failed to sync)."); - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); - } - - @Test - void sync_WithUpdatedCartDiscount_WithNewTarget_ShouldUpdateCartDiscountWithNewTarget() { - // preparation - final CartDiscountDraft newCartDiscountDraftWithExistingKey = - CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) - .target(CART_DISCOUNT_TARGET_2) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final List> updateActionsList = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .beforeUpdateCallback((updateActions, newCartDiscount, oldCartDiscount) -> { - updateActionsList.addAll(updateActions); - return updateActions; - }) + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); + } + + @Test + void sync_WithUpdatedCartDiscount_WithNewTarget_ShouldUpdateCartDiscountWithNewTarget() { + // preparation + final CartDiscountDraft newCartDiscountDraftWithExistingKey = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1).target(CART_DISCOUNT_TARGET_2).build(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final List> updateActionsList = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .beforeUpdateCallback( + (updateActions, newCartDiscount, oldCartDiscount) -> { + updateActionsList.addAll(updateActions); + return updateActions; + }) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithExistingKey)) .toCompletableFuture() .join(); - //assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActionsList).containsExactly(ChangeTarget.of(CART_DISCOUNT_TARGET_2)); - assertThat(cartDiscountSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 cart discounts were processed in total" + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActionsList).containsExactly(ChangeTarget.of(CART_DISCOUNT_TARGET_2)); + assertThat(cartDiscountSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 cart discounts were processed in total" + " (0 created, 1 updated and 0 failed to sync)."); - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); - } - - @Test - void sync_WithUpdatedCartDiscount_WithNewCustomType_ShouldUpdateCartDiscountWithNewCustomType() { - // preparation - final Type newCustomType = - createCartDiscountCustomType("new-type", Locale.ENGLISH, "new-type", CTP_TARGET_CLIENT); - - final CartDiscountDraft newCartDiscountDraftWithExistingKey = - CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(newCustomType.getKey(), emptyMap())) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final List> updateActionsList = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .beforeUpdateCallback((updateActions, newCartDiscount, oldCartDiscount) -> { - updateActionsList.addAll(updateActions); - return updateActions; - }) + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); + } + + @Test + void sync_WithUpdatedCartDiscount_WithNewCustomType_ShouldUpdateCartDiscountWithNewCustomType() { + // preparation + final Type newCustomType = + createCartDiscountCustomType("new-type", Locale.ENGLISH, "new-type", CTP_TARGET_CLIENT); + + final CartDiscountDraft newCartDiscountDraftWithExistingKey = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) + .custom(CustomFieldsDraft.ofTypeKeyAndJson(newCustomType.getKey(), emptyMap())) + .build(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final List> updateActionsList = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .beforeUpdateCallback( + (updateActions, newCartDiscount, oldCartDiscount) -> { + updateActionsList.addAll(updateActions); + return updateActions; + }) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithExistingKey)) .toCompletableFuture() .join(); - //assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActionsList) - .containsExactly(SetCustomType.ofTypeIdAndJson(newCustomType.getId(), emptyMap())); - assertThat(cartDiscountSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 cart discounts were processed in total" + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActionsList) + .containsExactly(SetCustomType.ofTypeIdAndJson(newCustomType.getId(), emptyMap())); + assertThat(cartDiscountSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 cart discounts were processed in total" + " (0 created, 1 updated and 0 failed to sync)."); - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); - } - - @Test - void sync_WithUpdatedCartDiscount_WithNonExistingResIdentifier_ShouldFailToResolveReference() { - // preparation - final CartDiscountDraft newCartDiscountDraftWithExistingKey = - CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) - .custom(CustomFieldsDraft.ofTypeKeyAndJson("not-existing-key", emptyMap())) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); + } + + @Test + void sync_WithUpdatedCartDiscount_WithNonExistingResIdentifier_ShouldFailToResolveReference() { + // preparation + final CartDiscountDraft newCartDiscountDraftWithExistingKey = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) + .custom(CustomFieldsDraft.ofTypeKeyAndJson("not-existing-key", emptyMap())) + .build(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithExistingKey)) .toCompletableFuture() .join(); - //assertions - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); - assertThat(errorMessages).containsExactly( + // assertions + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); + assertThat(errorMessages) + .containsExactly( "Failed to process the CartDiscountDraft with key:'key_1'. Reason: " - + ReferenceResolutionException.class.getCanonicalName() + ": " + + ReferenceResolutionException.class.getCanonicalName() + + ": " + "Failed to resolve custom type reference on CartDiscountDraft with key:'key_1'. " + "Reason: Type with key 'not-existing-key' doesn't exist."); - } - - @Test - void sync_WithUpdatedCartDiscount_WithNewCustomField_ShouldUpdateCartDiscountWithNewCustomField() { - // preparation - final Map customFieldsJsons = createCustomFieldsJsonMap(); - customFieldsJsons.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true)); - - final CartDiscountDraft newCartDiscountDraftWithExistingKey = - CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) - .custom(CustomFieldsDraft - .ofTypeKeyAndJson(OLD_CART_DISCOUNT_TYPE_KEY, customFieldsJsons)) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final List> updateActionsList = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .beforeUpdateCallback((updateActions, newCartDiscount, oldCartDiscount) -> { - updateActionsList.addAll(updateActions); - return updateActions; - }) + } + + @Test + void + sync_WithUpdatedCartDiscount_WithNewCustomField_ShouldUpdateCartDiscountWithNewCustomField() { + // preparation + final Map customFieldsJsons = createCustomFieldsJsonMap(); + customFieldsJsons.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true)); + + final CartDiscountDraft newCartDiscountDraftWithExistingKey = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_1) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson(OLD_CART_DISCOUNT_TYPE_KEY, customFieldsJsons)) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final List> updateActionsList = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .beforeUpdateCallback( + (updateActions, newCartDiscount, oldCartDiscount) -> { + updateActionsList.addAll(updateActions); + return updateActions; + }) + .build(); + + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithExistingKey)) .toCompletableFuture() .join(); - //assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActionsList).containsExactly(SetCustomField - .ofJson(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true))); - assertThat(cartDiscountSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 cart discounts were processed in total" + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActionsList) + .containsExactly( + SetCustomField.ofJson( + BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true))); + assertThat(cartDiscountSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 cart discounts were processed in total" + " (0 created, 1 updated and 0 failed to sync)."); - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); - } - - @Test - void sync_WithNewCartDiscount_ShouldCreateNewDiscount() { - //preparation - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync - .sync(singletonList(CART_DISCOUNT_DRAFT_2)) - .toCompletableFuture() - .join(); - - //assertions - assertThat(cartDiscountSyncStatistics).hasValues(1, 1, 0, 0); - - final Optional cartDiscountAfterCreation = - getCartDiscountByKey(CTP_TARGET_CLIENT, CART_DISCOUNT_KEY_2); - - assertThat(cartDiscountAfterCreation).hasValueSatisfying(cartDiscount -> { - assertThat(cartDiscount.getName()).isEqualTo(CART_DISCOUNT_DRAFT_2.getName()); - assertThat(cartDiscount.getDescription()).isEqualTo(CART_DISCOUNT_DRAFT_2.getDescription()); - assertThat(cartDiscount.getCartPredicate()).isEqualTo(CART_DISCOUNT_DRAFT_2.getCartPredicate()); - assertThat(cartDiscount.getValue()).isEqualTo(CART_DISCOUNT_DRAFT_2.getValue()); - }); - } - - @Test - void sync_WithoutCartPredicate_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //prepare - // Draft without "cartPredicate" throws a commercetools exception because "cartPredicate" is a required value - final CartDiscountDraft newCartDiscountDraftWithoutName = - CartDiscountDraftBuilder.of(CART_DISCOUNT_NAME_1, + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 1, 0); + } + + @Test + void sync_WithNewCartDiscount_ShouldCreateNewDiscount() { + // preparation + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync.sync(singletonList(CART_DISCOUNT_DRAFT_2)).toCompletableFuture().join(); + + // assertions + assertThat(cartDiscountSyncStatistics).hasValues(1, 1, 0, 0); + + final Optional cartDiscountAfterCreation = + getCartDiscountByKey(CTP_TARGET_CLIENT, CART_DISCOUNT_KEY_2); + + assertThat(cartDiscountAfterCreation) + .hasValueSatisfying( + cartDiscount -> { + assertThat(cartDiscount.getName()).isEqualTo(CART_DISCOUNT_DRAFT_2.getName()); + assertThat(cartDiscount.getDescription()) + .isEqualTo(CART_DISCOUNT_DRAFT_2.getDescription()); + assertThat(cartDiscount.getCartPredicate()) + .isEqualTo(CART_DISCOUNT_DRAFT_2.getCartPredicate()); + assertThat(cartDiscount.getValue()).isEqualTo(CART_DISCOUNT_DRAFT_2.getValue()); + }); + } + + @Test + void sync_WithoutCartPredicate_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // prepare + // Draft without "cartPredicate" throws a commercetools exception because "cartPredicate" is a + // required value + final CartDiscountDraft newCartDiscountDraftWithoutName = + CartDiscountDraftBuilder.of( + CART_DISCOUNT_NAME_1, (String) null, CART_DISCOUNT_VALUE_1, CART_DISCOUNT_TARGET_1, SORT_ORDER_1, false) - .key(CART_DISCOUNT_KEY_1) - .active(false) - .description(CART_DISCOUNT_DESC_1) - .validFrom(JANUARY_FROM) - .validUntil(JANUARY_UNTIL) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + .key(CART_DISCOUNT_KEY_1) + .active(false) + .description(CART_DISCOUNT_DESC_1) + .validFrom(JANUARY_FROM) + .validUntil(JANUARY_UNTIL) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); - //test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .build(); + + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithoutName)) .toCompletableFuture() .join(); - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to update cart discount with key: 'key_1'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(SyncException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); - assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(ErrorResponseException.class); - assertThat(throwable.getCause()).hasMessageContaining("cartPredicate: Missing required value"); + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message).contains("Failed to update cart discount with key: 'key_1'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(SyncException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); + assertThat(throwable.getCause()) + .hasCauseExactlyInstanceOf(ErrorResponseException.class); + assertThat(throwable.getCause()) + .hasMessageContaining("cartPredicate: Missing required value"); }); - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); - } + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); + } - @Test - void sync_WithoutValue_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //prepare - // Draft without "value" throws a commercetools exception because "value" is a required value - final CartDiscountDraft newCartDiscountDraftWithoutValue = - CartDiscountDraftBuilder.of(CART_DISCOUNT_NAME_1, + @Test + void sync_WithoutValue_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // prepare + // Draft without "value" throws a commercetools exception because "value" is a required value + final CartDiscountDraft newCartDiscountDraftWithoutValue = + CartDiscountDraftBuilder.of( + CART_DISCOUNT_NAME_1, CART_DISCOUNT_CART_PREDICATE_1, null, CART_DISCOUNT_TARGET_1, SORT_ORDER_1, false) - .key(CART_DISCOUNT_KEY_1) - .active(false) - .description(CART_DISCOUNT_DESC_1) - .validFrom(JANUARY_FROM) - .validUntil(JANUARY_UNTIL) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + .key(CART_DISCOUNT_KEY_1) + .active(false) + .description(CART_DISCOUNT_DESC_1) + .validFrom(JANUARY_FROM) + .validUntil(JANUARY_UNTIL) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); - //test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .build(); + + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithoutValue)) .toCompletableFuture() .join(); - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to update cart discount with key: 'key_1'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(SyncException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); - assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(ErrorResponseException.class); - assertThat(throwable.getCause()).hasMessageContaining("value: Missing required value"); + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message).contains("Failed to update cart discount with key: 'key_1'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(SyncException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); + assertThat(throwable.getCause()) + .hasCauseExactlyInstanceOf(ErrorResponseException.class); + assertThat(throwable.getCause()) + .hasMessageContaining("value: Missing required value"); }); - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithSeveralBatches_ShouldReturnProperStatistics() { - // preparation - final List sortOrders = getSortOrders(100); - // Default batch size is 50 (check CartDiscountSyncOptionsBuilder) so we have 2 batches of 50 - final List cartDiscountDrafts = IntStream - .range(0, 100) - .mapToObj(i -> - CartDiscountDraftBuilder.of(CART_DISCOUNT_NAME_2, - CART_DISCOUNT_CART_PREDICATE_2, - CART_DISCOUNT_VALUE_2, - CART_DISCOUNT_TARGET_2, - sortOrders.get(i), - false) - .key(format("key__%s", Integer.toString(i))) - .active(false) - .build()) + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithSeveralBatches_ShouldReturnProperStatistics() { + // preparation + final List sortOrders = getSortOrders(100); + // Default batch size is 50 (check CartDiscountSyncOptionsBuilder) so we have 2 batches of 50 + final List cartDiscountDrafts = + IntStream.range(0, 100) + .mapToObj( + i -> + CartDiscountDraftBuilder.of( + CART_DISCOUNT_NAME_2, + CART_DISCOUNT_CART_PREDICATE_2, + CART_DISCOUNT_VALUE_2, + CART_DISCOUNT_TARGET_2, + sortOrders.get(i), + false) + .key(format("key__%s", Integer.toString(i))) + .active(false) + .build()) .collect(Collectors.toList()); - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - //test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync - .sync(cartDiscountDrafts) - .toCompletableFuture() - .join(); - - - //assertion - assertThat(cartDiscountSyncStatistics).hasValues(100, 100, 0, 0); - } + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync.sync(cartDiscountDrafts).toCompletableFuture().join(); - @Test - void sync_WithConcurrentModificationException_ShouldRetryToUpdateNewCartDiscountWithSuccess() { - // Preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); + // assertion + assertThat(cartDiscountSyncStatistics).hasValues(100, 100, 0, 0); + } + @Test + void sync_WithConcurrentModificationException_ShouldRetryToUpdateNewCartDiscountWithSuccess() { + // Preparation + final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); - final CartDiscountDraft draft2 = CartDiscountDraftBuilder - .of(CART_DISCOUNT_DRAFT_2) - .custom(CustomFieldsDraft - .ofTypeKeyAndJson(OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) + final CartDiscountDraft draft2 = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft2)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft2)).toCompletableFuture().join(); - final CartDiscountDraft updatedDraft = - CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) - .description(CART_DISCOUNT_DESC_1) - .build(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(spyClient) + final CartDiscountDraft updatedDraft = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) + .description(CART_DISCOUNT_DESC_1) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(spyClient).build(); + + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - //test - final CartDiscountSyncStatistics statistics = cartDiscountSync.sync(singletonList(updatedDraft)) - .toCompletableFuture() - .join(); + // test + final CartDiscountSyncStatistics statistics = + cartDiscountSync.sync(singletonList(updatedDraft)).toCompletableFuture().join(); - // assertion - assertThat(statistics).hasValues(1, 0, 1, 0); + // assertion + assertThat(statistics).hasValues(1, 0, 1, 0); - // Assert CTP state. - final PagedQueryResult queryResult = - CTP_TARGET_CLIENT.execute(CartDiscountQuery.of().plusPredicates(queryModel -> - queryModel.key().is(CART_DISCOUNT_KEY_1))) - .toCompletableFuture() - .join(); + // Assert CTP state. + final PagedQueryResult queryResult = + CTP_TARGET_CLIENT + .execute( + CartDiscountQuery.of() + .plusPredicates(queryModel -> queryModel.key().is(CART_DISCOUNT_KEY_1))) + .toCompletableFuture() + .join(); - assertThat(queryResult.head()).hasValueSatisfying(cartDiscount -> - assertThat(cartDiscount.getKey()).isEqualTo(CART_DISCOUNT_KEY_1)); - } + assertThat(queryResult.head()) + .hasValueSatisfying( + cartDiscount -> assertThat(cartDiscount.getKey()).isEqualTo(CART_DISCOUNT_KEY_1)); + } - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdate() { + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdate() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final CartDiscountUpdateCommand anyCartDiscountUpdate = any(CartDiscountUpdateCommand.class); + final CartDiscountUpdateCommand anyCartDiscountUpdate = any(CartDiscountUpdateCommand.class); - when(spyClient.execute(anyCartDiscountUpdate)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); + when(spyClient.execute(anyCartDiscountUpdate)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); - return spyClient; - } + return spyClient; + } - @Test - void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { - //preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); + @Test + void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); - final CartDiscountDraft draft2 = CartDiscountDraftBuilder - .of(CART_DISCOUNT_DRAFT_2) - .custom(CustomFieldsDraft - .ofTypeKeyAndJson(OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) + final CartDiscountDraft draft2 = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft2)) - .toCompletableFuture() - .join(); - - final CartDiscountDraft updatedDraft = - CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) - .description(CART_DISCOUNT_DESC_1) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft2)).toCompletableFuture().join(); + + final CartDiscountDraft updatedDraft = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) + .description(CART_DISCOUNT_DESC_1) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .build(); - //test - final CartDiscountSyncStatistics statistics = cartDiscountSync.sync(singletonList(updatedDraft)) - .toCompletableFuture() - .join(); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - //assertion - assertThat(statistics).hasValues(1, 0, 0, 1); + // test + final CartDiscountSyncStatistics statistics = + cartDiscountSync.sync(singletonList(updatedDraft)).toCompletableFuture().join(); - assertThat(errorMessages).hasSize(1); - assertThat(exceptions).hasSize(1); + // assertion + assertThat(statistics).hasValues(1, 0, 0, 1); - assertThat(exceptions.get(0).getCause()).hasCauseExactlyInstanceOf(BadGatewayException.class); - assertThat(errorMessages.get(0)).contains( - format("Failed to update cart discount with key: '%s'. Reason: Failed to fetch from CTP while retrying " - + "after concurrency modification.", CART_DISCOUNT_KEY_2)); + assertThat(errorMessages).hasSize(1); + assertThat(exceptions).hasSize(1); - } + assertThat(exceptions.get(0).getCause()).hasCauseExactlyInstanceOf(BadGatewayException.class); + assertThat(errorMessages.get(0)) + .contains( + format( + "Failed to update cart discount with key: '%s'. Reason: Failed to fetch from CTP while retrying " + + "after concurrency modification.", + CART_DISCOUNT_KEY_2)); + } - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(CartDiscountQuery.class))) - .thenCallRealMethod() // Call real fetch on fetching matching cart discounts - .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(CartDiscountQuery.class))) + .thenCallRealMethod() // Call real fetch on fetching matching cart discounts + .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); - final CartDiscountUpdateCommand anyCartDiscountUpdate = any(CartDiscountUpdateCommand.class); + final CartDiscountUpdateCommand anyCartDiscountUpdate = any(CartDiscountUpdateCommand.class); - when(spyClient.execute(anyCartDiscountUpdate)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); + when(spyClient.execute(anyCartDiscountUpdate)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); - return spyClient; - } + return spyClient; + } - @Test - void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { - //preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); + @Test + void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); - final CartDiscountDraft draft2 = CartDiscountDraftBuilder - .of(CART_DISCOUNT_DRAFT_2) - .custom(CustomFieldsDraft - .ofTypeKeyAndJson(OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) + final CartDiscountDraft draft2 = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CART_DISCOUNT_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft2)) - .toCompletableFuture() - .join(); - - final CartDiscountDraft updatedDraft = - CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) - .description(CART_DISCOUNT_DESC_1) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + CTP_TARGET_CLIENT.execute(CartDiscountCreateCommand.of(draft2)).toCompletableFuture().join(); + + final CartDiscountDraft updatedDraft = + CartDiscountDraftBuilder.of(CART_DISCOUNT_DRAFT_2) + .description(CART_DISCOUNT_DESC_1) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .build(); - //test - final CartDiscountSyncStatistics statistics = cartDiscountSync.sync(singletonList(updatedDraft)) - .toCompletableFuture() - .join(); + final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions); - // Assertion - assertThat(statistics).hasValues(1, 0, 0, 1); + // test + final CartDiscountSyncStatistics statistics = + cartDiscountSync.sync(singletonList(updatedDraft)).toCompletableFuture().join(); - assertThat(errorMessages).hasSize(1); - assertThat(exceptions).hasSize(1); - assertThat(errorMessages.get(0)).contains( - format("Failed to update cart discount with key: '%s'. Reason: Not found when attempting to fetch while " - + "retrying after concurrency modification.", CART_DISCOUNT_KEY_2)); - } + // Assertion + assertThat(statistics).hasValues(1, 0, 0, 1); - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { + assertThat(errorMessages).hasSize(1); + assertThat(exceptions).hasSize(1); + assertThat(errorMessages.get(0)) + .contains( + format( + "Failed to update cart discount with key: '%s'. Reason: Not found when attempting to fetch while " + + "retrying after concurrency modification.", + CART_DISCOUNT_KEY_2)); + } - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final CartDiscountQuery anyCartDiscountQuery = any(CartDiscountQuery.class); + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { - when(spyClient.execute(anyCartDiscountQuery)) - .thenCallRealMethod() // Call real fetch on fetching matching cart discounts - .thenReturn(completedFuture(PagedQueryResult.empty())); + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final CartDiscountQuery anyCartDiscountQuery = any(CartDiscountQuery.class); - final CartDiscountUpdateCommand anyCartDiscountUpdate = any(CartDiscountUpdateCommand.class); + when(spyClient.execute(anyCartDiscountQuery)) + .thenCallRealMethod() // Call real fetch on fetching matching cart discounts + .thenReturn(completedFuture(PagedQueryResult.empty())); - when(spyClient.execute(anyCartDiscountUpdate)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); + final CartDiscountUpdateCommand anyCartDiscountUpdate = any(CartDiscountUpdateCommand.class); - return spyClient; - } + when(spyClient.execute(anyCartDiscountUpdate)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); + return spyClient; + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/categories/CategorySyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/categories/CategorySyncIT.java index 8d20fc9560..a582e757bb 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/categories/CategorySyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/categories/CategorySyncIT.java @@ -1,5 +1,23 @@ package com.commercetools.sync.integration.externalsource.categories; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCustomFieldsDraft; +import static com.commercetools.sync.integration.commons.utils.ITUtils.BOOLEAN_CUSTOM_FIELD_NAME; +import static com.commercetools.sync.integration.commons.utils.ITUtils.LOCALISED_STRING_CUSTOM_FIELD_NAME; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.CategorySync; import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; @@ -20,13 +38,6 @@ import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -36,679 +47,798 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCustomFieldsDraft; -import static com.commercetools.sync.integration.commons.utils.ITUtils.BOOLEAN_CUSTOM_FIELD_NAME; -import static com.commercetools.sync.integration.commons.utils.ITUtils.LOCALISED_STRING_CUSTOM_FIELD_NAME; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CategorySyncIT { - private CategorySync categorySync; - private static final String oldCategoryKey = "oldCategoryKey"; - private List errorCallBackMessages = new ArrayList<>(); - private List errorCallBackExceptions = new ArrayList<>(); - - /** - * Delete all categories and types from target project. Then create custom types for target CTP project categories. - */ - @BeforeAll - static void setup() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_TARGET_CLIENT); - } - - /** - * Deletes Categories and Types from target CTP project, then it populates it with category test data. - */ - @BeforeEach - void setupTest() { - deleteAllCategories(CTP_TARGET_CLIENT); - - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }).build(); - categorySync = new CategorySync(categorySyncOptions); - - // Create a mock in the target project. - final CategoryDraft oldCategoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "furniture")) + private CategorySync categorySync; + private static final String oldCategoryKey = "oldCategoryKey"; + private List errorCallBackMessages = new ArrayList<>(); + private List errorCallBackExceptions = new ArrayList<>(); + + /** + * Delete all categories and types from target project. Then create custom types for target CTP + * project categories. + */ + @BeforeAll + static void setup() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, + Locale.ENGLISH, + OLD_CATEGORY_CUSTOM_TYPE_NAME, + CTP_TARGET_CLIENT); + } + + /** + * Deletes Categories and Types from target CTP project, then it populates it with category test + * data. + */ + @BeforeEach + void setupTest() { + deleteAllCategories(CTP_TARGET_CLIENT); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); + categorySync = new CategorySync(categorySyncOptions); + + // Create a mock in the target project. + final CategoryDraft oldCategoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), + LocalizedString.of(Locale.ENGLISH, "furniture")) .key(oldCategoryKey) .custom(getCustomFieldsDraft()) .build(); - CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(oldCategoryDraft)) - .toCompletableFuture() - .join(); - } - - /** - * Cleans up the target test data that were built in each test. - */ - @AfterEach - void tearDownTest() { - deleteAllCategories(CTP_TARGET_CLIENT); - } - - /** - * Cleans up the entire target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - } - - @Test - void syncDrafts_WithANewCategoryWithNewSlug_ShouldCreateCategory() { - // Category draft coming from external source. - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "new-furniture")) + CTP_TARGET_CLIENT + .execute(CategoryCreateCommand.of(oldCategoryDraft)) + .toCompletableFuture() + .join(); + } + + /** Cleans up the target test data that were built in each test. */ + @AfterEach + void tearDownTest() { + deleteAllCategories(CTP_TARGET_CLIENT); + } + + /** Cleans up the entire target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + } + + @Test + void syncDrafts_WithANewCategoryWithNewSlug_ShouldCreateCategory() { + // Category draft coming from external source. + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), + LocalizedString.of(Locale.ENGLISH, "new-furniture")) .key("newCategoryKey") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategorySyncStatistics syncStatistics = categorySync.sync(Collections.singletonList(categoryDraft)) - .toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(Collections.singletonList(categoryDraft)).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - } + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + } - @Test - void syncDrafts_WithANewCategoryWithDuplicateSlug_ShouldNotCreateCategory() { - // Category draft coming from external source. - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "furniture")) + @Test + void syncDrafts_WithANewCategoryWithDuplicateSlug_ShouldNotCreateCategory() { + // Category draft coming from external source. + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), + LocalizedString.of(Locale.ENGLISH, "furniture")) .key("newCategoryKey") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategorySyncStatistics syncStatistics = categorySync.sync(Collections.singletonList(categoryDraft)) - .toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(Collections.singletonList(categoryDraft)).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - } + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + } - @Test - void syncDrafts_WithCategoryWithNoChanges_ShouldNotUpdateCategory() { - // Category draft coming from external source. - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), + @Test + void syncDrafts_WithCategoryWithNoChanges_ShouldNotUpdateCategory() { + // Category draft coming from external source. + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "furniture")) .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategorySyncStatistics syncStatistics = categorySync.sync(Collections.singletonList(categoryDraft)) - .toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(Collections.singletonList(categoryDraft)).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - } + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + } - @Test - void syncDrafts_WithChangedCategory_ShouldUpdateCategory() { - // Category draft coming from external source. - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), + @Test + void syncDrafts_WithChangedCategory_ShouldUpdateCategory() { + // Category draft coming from external source. + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategorySyncStatistics syncStatistics = categorySync.sync(Collections.singletonList(categoryDraft)) - .toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(Collections.singletonList(categoryDraft)).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - } + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + } - @Test - void syncDrafts_WithConcurrentModificationException_ShouldRetryToUpdateNewCategoryWithSuccess() { - // Preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); + @Test + void syncDrafts_WithConcurrentModificationException_ShouldRetryToUpdateNewCategoryWithSuccess() { + // Preparation + final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); - final LocalizedString newCategoryName = LocalizedString.of(Locale.ENGLISH, "Modern Furniture"); - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(newCategoryName, LocalizedString.of(Locale.ENGLISH, "modern-furniture")) + final LocalizedString newCategoryName = LocalizedString.of(Locale.ENGLISH, "Modern Furniture"); + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + newCategoryName, LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) - .build(); - - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(spyClient) - .build(); - - final CategorySync categorySync = new CategorySync(categorySyncOptions); - - // Test - final CategorySyncStatistics statistics = categorySync.sync(Collections.singletonList(categoryDraft)) - .toCompletableFuture() - .join(); - - // Assertion - assertThat(statistics).hasValues(1, 0, 1, 0); - - // Assert CTP state. - final PagedQueryResult queryResult = - CTP_TARGET_CLIENT.execute(CategoryQuery.of().plusPredicates(categoryQueryModel -> - categoryQueryModel.key().is(categoryDraft.getKey()))) - .toCompletableFuture() - .join(); - - assertThat(queryResult.head()).hasValueSatisfying(category -> - assertThat(category.getName()).isEqualTo(newCategoryName)); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdate() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final CategoryUpdateCommand anyCategoryUpdate = any(CategoryUpdateCommand.class); - when(spyClient.execute(anyCategoryUpdate)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - return spyClient; - } - - @Test - void syncDrafts_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { - // Preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); - - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), - LocalizedString.of(Locale.ENGLISH, "modern-furniture")) - .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) - .build(); - - final List errorMessages = new ArrayList<>(); - final List errors = new ArrayList<>(); - - final CategorySyncOptions categorySyncOptions = - CategorySyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errors.add(exception.getCause()); - }) - .build(); - - final CategorySync categorySync = new CategorySync(categorySyncOptions); - final CategorySyncStatistics statistics = categorySync.sync(Collections.singletonList(categoryDraft)) - .toCompletableFuture() - .join(); - - // Test and assertion - assertThat(statistics).hasValues(1, 0, 0, 1); - assertThat(errorMessages).hasSize(1); - assertThat(errors).hasSize(1); - - assertThat(errors.get(0).getCause()).isExactlyInstanceOf(BadGatewayException.class); - assertThat(errorMessages.get(0)).contains( - format("Failed to update Category with key: '%s'. Reason: Failed to fetch from CTP while retrying " - + "after concurrency modification.", categoryDraft.getKey())); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final CategoryQuery anyCategoryQuery = any(CategoryQuery.class); - - when(spyClient.execute(anyCategoryQuery)) - .thenCallRealMethod() // cache category keys - .thenCallRealMethod() // Call real fetch on fetching matching categories - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); - - - final CategoryUpdateCommand anyCategoryUpdate = any(CategoryUpdateCommand.class); - when(spyClient.execute(anyCategoryUpdate)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())); - - - return spyClient; - } - - @Test - void syncDrafts_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { - // Preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); - - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .build(); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(spyClient).build(); + + final CategorySync categorySync = new CategorySync(categorySyncOptions); + + // Test + final CategorySyncStatistics statistics = + categorySync.sync(Collections.singletonList(categoryDraft)).toCompletableFuture().join(); + + // Assertion + assertThat(statistics).hasValues(1, 0, 1, 0); + + // Assert CTP state. + final PagedQueryResult queryResult = + CTP_TARGET_CLIENT + .execute( + CategoryQuery.of() + .plusPredicates( + categoryQueryModel -> categoryQueryModel.key().is(categoryDraft.getKey()))) + .toCompletableFuture() + .join(); + + assertThat(queryResult.head()) + .hasValueSatisfying(category -> assertThat(category.getName()).isEqualTo(newCategoryName)); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdate() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final CategoryUpdateCommand anyCategoryUpdate = any(CategoryUpdateCommand.class); + when(spyClient.execute(anyCategoryUpdate)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + return spyClient; + } + + @Test + void syncDrafts_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { + // Preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); + + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) - .build(); - - final List errorMessages = new ArrayList<>(); - final List errors = new ArrayList<>(); - - final CategorySyncOptions categorySyncOptions = - CategorySyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errors.add(exception.getCause()); - }) - .build(); - - final CategorySync categorySync = new CategorySync(categorySyncOptions); - final CategorySyncStatistics statistics = categorySync.sync(Collections.singletonList(categoryDraft)) - .toCompletableFuture() - .join(); - - // Test and assertion - assertThat(statistics).hasValues(1, 0, 0, 1); - assertThat(errorMessages).hasSize(1); - assertThat(errors).hasSize(1); - - assertThat(errorMessages.get(0)).contains( - format("Failed to update Category with key: '%s'. Reason: Not found when attempting to fetch while" - + " retrying after concurrency modification.", categoryDraft.getKey())); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final CategoryQuery anyCategoryQuery = any(CategoryQuery.class); - - when(spyClient.execute(anyCategoryQuery)) - .thenCallRealMethod() // cache category keys - .thenCallRealMethod() // Call real fetch on fetching matching categories - .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); - - - final CategoryUpdateCommand anyCategoryUpdate = any(CategoryUpdateCommand.class); - when(spyClient.execute(anyCategoryUpdate)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())); - - - return spyClient; - } - - @Test - void syncDrafts_WithNewCategoryWithExistingParent_ShouldCreateCategory() { - // Category draft coming from external source. - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .build(); + + final List errorMessages = new ArrayList<>(); + final List errors = new ArrayList<>(); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errors.add(exception.getCause()); + }) + .build(); + + final CategorySync categorySync = new CategorySync(categorySyncOptions); + final CategorySyncStatistics statistics = + categorySync.sync(Collections.singletonList(categoryDraft)).toCompletableFuture().join(); + + // Test and assertion + assertThat(statistics).hasValues(1, 0, 0, 1); + assertThat(errorMessages).hasSize(1); + assertThat(errors).hasSize(1); + + assertThat(errors.get(0).getCause()).isExactlyInstanceOf(BadGatewayException.class); + assertThat(errorMessages.get(0)) + .contains( + format( + "Failed to update Category with key: '%s'. Reason: Failed to fetch from CTP while retrying " + + "after concurrency modification.", + categoryDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final CategoryQuery anyCategoryQuery = any(CategoryQuery.class); + + when(spyClient.execute(anyCategoryQuery)) + .thenCallRealMethod() // cache category keys + .thenCallRealMethod() // Call real fetch on fetching matching categories + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); + + final CategoryUpdateCommand anyCategoryUpdate = any(CategoryUpdateCommand.class); + when(spyClient.execute(anyCategoryUpdate)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())); + + return spyClient; + } + + @Test + void + syncDrafts_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { + // Preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); + + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), + LocalizedString.of(Locale.ENGLISH, "modern-furniture")) + .key(oldCategoryKey) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .build(); + + final List errorMessages = new ArrayList<>(); + final List errors = new ArrayList<>(); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errors.add(exception.getCause()); + }) + .build(); + + final CategorySync categorySync = new CategorySync(categorySyncOptions); + final CategorySyncStatistics statistics = + categorySync.sync(Collections.singletonList(categoryDraft)).toCompletableFuture().join(); + + // Test and assertion + assertThat(statistics).hasValues(1, 0, 0, 1); + assertThat(errorMessages).hasSize(1); + assertThat(errors).hasSize(1); + + assertThat(errorMessages.get(0)) + .contains( + format( + "Failed to update Category with key: '%s'. Reason: Not found when attempting to fetch while" + + " retrying after concurrency modification.", + categoryDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final CategoryQuery anyCategoryQuery = any(CategoryQuery.class); + + when(spyClient.execute(anyCategoryQuery)) + .thenCallRealMethod() // cache category keys + .thenCallRealMethod() // Call real fetch on fetching matching categories + .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); + + final CategoryUpdateCommand anyCategoryUpdate = any(CategoryUpdateCommand.class); + when(spyClient.execute(anyCategoryUpdate)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())); + + return spyClient; + } + + @Test + void syncDrafts_WithNewCategoryWithExistingParent_ShouldCreateCategory() { + // Category draft coming from external source. + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key("newCategory") .parent(ResourceIdentifier.ofKey(oldCategoryKey)) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategorySyncStatistics syncStatistics = categorySync.sync(Collections.singletonList(categoryDraft)) - .toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(Collections.singletonList(categoryDraft)).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - } + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + } - @Test - void syncDraft_withARemovedCustomType_ShouldUpdateCategory() { - // Category draft coming from external source. - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "furniture")) + @Test + void syncDraft_withARemovedCustomType_ShouldUpdateCategory() { + // Category draft coming from external source. + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), + LocalizedString.of(Locale.ENGLISH, "furniture")) .key(oldCategoryKey) .build(); - final CategorySyncStatistics syncStatistics = categorySync.sync(Collections.singletonList(categoryDraft)) - .toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(Collections.singletonList(categoryDraft)).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - } + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + } - @Test - void syncDrafts_WithMultipleBatchSyncing_ShouldSync() { - // Existing array of [1, 2, 3, oldCategoryKey] - final CategoryDraft oldCategoryDraft1 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat1"), LocalizedString.of(Locale.ENGLISH, "furniture1")) + @Test + void syncDrafts_WithMultipleBatchSyncing_ShouldSync() { + // Existing array of [1, 2, 3, oldCategoryKey] + final CategoryDraft oldCategoryDraft1 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat1"), + LocalizedString.of(Locale.ENGLISH, "furniture1")) .key("cat1") .custom(getCustomFieldsDraft()) .build(); - CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(oldCategoryDraft1)) - .toCompletableFuture() - .join(); - final CategoryDraft oldCategoryDraft2 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat2"), LocalizedString.of(Locale.ENGLISH, "furniture2")) + CTP_TARGET_CLIENT + .execute(CategoryCreateCommand.of(oldCategoryDraft1)) + .toCompletableFuture() + .join(); + final CategoryDraft oldCategoryDraft2 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat2"), + LocalizedString.of(Locale.ENGLISH, "furniture2")) .key("cat2") .custom(getCustomFieldsDraft()) .build(); - CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(oldCategoryDraft2)) - .toCompletableFuture() - .join(); - final CategoryDraft oldCategoryDraft3 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat3"), LocalizedString.of(Locale.ENGLISH, "furniture3")) + CTP_TARGET_CLIENT + .execute(CategoryCreateCommand.of(oldCategoryDraft2)) + .toCompletableFuture() + .join(); + final CategoryDraft oldCategoryDraft3 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat3"), + LocalizedString.of(Locale.ENGLISH, "furniture3")) .key("cat3") .custom(getCustomFieldsDraft()) .build(); - CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(oldCategoryDraft3)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(CategoryCreateCommand.of(oldCategoryDraft3)) + .toCompletableFuture() + .join(); + // _-----_-----_-----_-----_-----_PREPARE BATCHES FROM EXTERNAL + // SOURCE-----_-----_-----_-----_-----_----- + // _-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_----- - //_-----_-----_-----_-----_-----_PREPARE BATCHES FROM EXTERNAL SOURCE-----_-----_-----_-----_-----_----- - //_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_-----_----- - - // Category draft coming from external source. - final CategoryDraft categoryDraft1 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "oldCategoryKey"), + // Category draft coming from external source. + final CategoryDraft categoryDraft1 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "oldCategoryKey"), LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key(oldCategoryKey) .parent(ResourceIdentifier.ofKey("cat7")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final List batch1 = new ArrayList<>(); - batch1.add(categoryDraft1); + final List batch1 = new ArrayList<>(); + batch1.add(categoryDraft1); - final CategoryDraft categoryDraft2 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat7"), + final CategoryDraft categoryDraft2 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat7"), LocalizedString.of(Locale.ENGLISH, "modern-furniture1")) .key("cat7") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final List batch2 = new ArrayList<>(); - batch2.add(categoryDraft2); + final List batch2 = new ArrayList<>(); + batch2.add(categoryDraft2); - final CategoryDraft categoryDraft3 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat6"), + final CategoryDraft categoryDraft3 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat6"), LocalizedString.of(Locale.ENGLISH, "modern-furniture2")) .key("cat6") .parent(ResourceIdentifier.ofKey("cat5")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final List batch3 = new ArrayList<>(); - batch3.add(categoryDraft3); + final List batch3 = new ArrayList<>(); + batch3.add(categoryDraft3); - final CategoryDraft categoryDraft4 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat5"), + final CategoryDraft categoryDraft4 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat5"), LocalizedString.of(Locale.ENGLISH, "modern-furniture3")) .key("cat5") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) - .build(); - - final List batch4 = new ArrayList<>(); - batch4.add(categoryDraft4); - - final CategorySyncStatistics syncStatistics = categorySync.sync(batch1) - .thenCompose(result -> categorySync.sync(batch2)) - .thenCompose(result -> categorySync.sync(batch3)) - .thenCompose(result -> categorySync.sync(batch4)) - .toCompletableFuture() - .join(); - - assertThat(syncStatistics).hasValues(4, 3, 1, 0, 0); - } - - @Test - void syncDrafts_WithMultipleBatchSyncingWithAlreadyProcessedDrafts_ShouldSync() { - // Category draft coming from external source. - CategoryDraft categoryDraft1 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "new name"), + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .build(); + + final List batch4 = new ArrayList<>(); + batch4.add(categoryDraft4); + + final CategorySyncStatistics syncStatistics = + categorySync + .sync(batch1) + .thenCompose(result -> categorySync.sync(batch2)) + .thenCompose(result -> categorySync.sync(batch3)) + .thenCompose(result -> categorySync.sync(batch4)) + .toCompletableFuture() + .join(); + + assertThat(syncStatistics).hasValues(4, 3, 1, 0, 0); + } + + @Test + void syncDrafts_WithMultipleBatchSyncingWithAlreadyProcessedDrafts_ShouldSync() { + // Category draft coming from external source. + CategoryDraft categoryDraft1 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "new name"), LocalizedString.of(Locale.ENGLISH, "new-slug")) .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) - .build(); - - final List batch1 = new ArrayList<>(); - batch1.add(categoryDraft1); - - // Process same draft again in a different batch but with a different name. - final LocalizedString anotherNewName = LocalizedString.of(Locale.ENGLISH, "another new name"); - categoryDraft1 = CategoryDraftBuilder.of(categoryDraft1) - .name(anotherNewName) - .build(); - - final List batch2 = new ArrayList<>(); - batch2.add(categoryDraft1); - - final CategorySyncStatistics syncStatistics = categorySync.sync(batch1) - .thenCompose(result -> categorySync.sync(batch2)) - .toCompletableFuture() - .join(); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - - final Optional optionalResult = CTP_TARGET_CLIENT.execute(CategoryQuery.of().bySlug(Locale.ENGLISH, - categoryDraft1.getSlug().get(Locale.ENGLISH))).toCompletableFuture().join().head(); - assertThat(optionalResult).isNotEmpty(); - assertThat(optionalResult.get().getName()).isEqualTo(anotherNewName); - } - - @Test - void syncDrafts_WithOneBatchSyncing_ShouldSync() { - final List newCategoryDrafts = new ArrayList<>(); - - // Category draft coming from external source. - final CategoryDraft categoryDraft1 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .build(); + + final List batch1 = new ArrayList<>(); + batch1.add(categoryDraft1); + + // Process same draft again in a different batch but with a different name. + final LocalizedString anotherNewName = LocalizedString.of(Locale.ENGLISH, "another new name"); + categoryDraft1 = CategoryDraftBuilder.of(categoryDraft1).name(anotherNewName).build(); + + final List batch2 = new ArrayList<>(); + batch2.add(categoryDraft1); + + final CategorySyncStatistics syncStatistics = + categorySync + .sync(batch1) + .thenCompose(result -> categorySync.sync(batch2)) + .toCompletableFuture() + .join(); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + + final Optional optionalResult = + CTP_TARGET_CLIENT + .execute( + CategoryQuery.of() + .bySlug(Locale.ENGLISH, categoryDraft1.getSlug().get(Locale.ENGLISH))) + .toCompletableFuture() + .join() + .head(); + assertThat(optionalResult).isNotEmpty(); + assertThat(optionalResult.get().getName()).isEqualTo(anotherNewName); + } + + @Test + void syncDrafts_WithOneBatchSyncing_ShouldSync() { + final List newCategoryDrafts = new ArrayList<>(); + + // Category draft coming from external source. + final CategoryDraft categoryDraft1 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - - final CategoryDraft categoryDraft2 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat1"), + final CategoryDraft categoryDraft2 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat1"), LocalizedString.of(Locale.ENGLISH, "modern-furniture1")) .key("cat1") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategoryDraft categoryDraft3 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat2"), + final CategoryDraft categoryDraft3 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat2"), LocalizedString.of(Locale.ENGLISH, "modern-furniture2")) .key("cat2") .parent(ResourceIdentifier.ofKey("cat1")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategoryDraft categoryDraft4 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat3"), + final CategoryDraft categoryDraft4 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat3"), LocalizedString.of(Locale.ENGLISH, "modern-furniture3")) .key("cat3") .parent(ResourceIdentifier.ofKey("cat1")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategoryDraft categoryDraft5 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat4"), + final CategoryDraft categoryDraft5 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat4"), LocalizedString.of(Locale.ENGLISH, "modern-furniture4")) .key("cat4") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategoryDraft categoryDraft6 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat5"), + final CategoryDraft categoryDraft6 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat5"), LocalizedString.of(Locale.ENGLISH, "modern-furniture5")) .key("cat5") .parent(ResourceIdentifier.ofKey("cat4")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - newCategoryDrafts.add(categoryDraft1); - newCategoryDrafts.add(categoryDraft2); - newCategoryDrafts.add(categoryDraft3); - newCategoryDrafts.add(categoryDraft4); - newCategoryDrafts.add(categoryDraft5); - newCategoryDrafts.add(categoryDraft6); + newCategoryDrafts.add(categoryDraft1); + newCategoryDrafts.add(categoryDraft2); + newCategoryDrafts.add(categoryDraft3); + newCategoryDrafts.add(categoryDraft4); + newCategoryDrafts.add(categoryDraft5); + newCategoryDrafts.add(categoryDraft6); - final CategorySyncStatistics syncStatistics = categorySync.sync(newCategoryDrafts).toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(newCategoryDrafts).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(6, 5, 1, 0, 0); - } + assertThat(syncStatistics).hasValues(6, 5, 1, 0, 0); + } - @Test - void syncDrafts_WithSameSlugDraft_ShouldNotSyncIt() { - final List newCategoryDrafts = new ArrayList<>(); + @Test + void syncDrafts_WithSameSlugDraft_ShouldNotSyncIt() { + final List newCategoryDrafts = new ArrayList<>(); - // Category draft coming from external source. - final CategoryDraft categoryDraft1 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), + // Category draft coming from external source. + final CategoryDraft categoryDraft1 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - // Same slug draft - final CategoryDraft categoryDraft2 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat1"), + // Same slug draft + final CategoryDraft categoryDraft2 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat1"), LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key("cat1") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategoryDraft categoryDraft3 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat2"), + final CategoryDraft categoryDraft3 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat2"), LocalizedString.of(Locale.ENGLISH, "modern-furniture2")) .key("cat2") .parent(ResourceIdentifier.ofKey("cat1")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategoryDraft categoryDraft4 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat3"), + final CategoryDraft categoryDraft4 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat3"), LocalizedString.of(Locale.ENGLISH, "modern-furniture3")) .key("cat3") .parent(ResourceIdentifier.ofKey("cat1")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategoryDraft categoryDraft5 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat4"), + final CategoryDraft categoryDraft5 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat4"), LocalizedString.of(Locale.ENGLISH, "modern-furniture4")) .key("cat4") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final CategoryDraft categoryDraft6 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "cat5"), + final CategoryDraft categoryDraft6 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "cat5"), LocalizedString.of(Locale.ENGLISH, "modern-furniture5")) .key("cat5") .parent(ResourceIdentifier.ofKey("cat4")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) - .build(); - - newCategoryDrafts.add(categoryDraft1); - newCategoryDrafts.add(categoryDraft2); - newCategoryDrafts.add(categoryDraft3); - newCategoryDrafts.add(categoryDraft4); - newCategoryDrafts.add(categoryDraft5); - newCategoryDrafts.add(categoryDraft6); - - final CategorySyncStatistics syncStatistics = categorySync.sync(newCategoryDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(6, 5, 0, 1, 0); - } - - @Test - void syncDrafts_WithValidAndInvalidCustomTypeKeys_ShouldSyncCorrectly() { - final List newCategoryDrafts = new ArrayList<>(); - final String newCustomTypeKey = "newKey"; - createCategoriesCustomType(newCustomTypeKey, Locale.ENGLISH, "newCustomTypeName", CTP_TARGET_CLIENT); - - final CategoryDraft categoryDraft1 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .build(); + + newCategoryDrafts.add(categoryDraft1); + newCategoryDrafts.add(categoryDraft2); + newCategoryDrafts.add(categoryDraft3); + newCategoryDrafts.add(categoryDraft4); + newCategoryDrafts.add(categoryDraft5); + newCategoryDrafts.add(categoryDraft6); + + final CategorySyncStatistics syncStatistics = + categorySync.sync(newCategoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(6, 5, 0, 1, 0); + } + + @Test + void syncDrafts_WithValidAndInvalidCustomTypeKeys_ShouldSyncCorrectly() { + final List newCategoryDrafts = new ArrayList<>(); + final String newCustomTypeKey = "newKey"; + createCategoriesCustomType( + newCustomTypeKey, Locale.ENGLISH, "newCustomTypeName", CTP_TARGET_CLIENT); + + final CategoryDraft categoryDraft1 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson("nonExistingKey", createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson("nonExistingKey", createCustomFieldsJsonMap())) .build(); - final CategoryDraft categoryDraft2 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "Modern Furniture-2"), + final CategoryDraft categoryDraft2 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "Modern Furniture-2"), LocalizedString.of(Locale.ENGLISH, "modern-furniture-2")) .key("newCategoryKey") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(newCustomTypeKey, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson(newCustomTypeKey, createCustomFieldsJsonMap())) .build(); - newCategoryDrafts.add(categoryDraft1); - newCategoryDrafts.add(categoryDraft2); + newCategoryDrafts.add(categoryDraft1); + newCategoryDrafts.add(categoryDraft2); - final CategorySyncStatistics syncStatistics = categorySync.sync(newCategoryDrafts).toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(newCategoryDrafts).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(2, 1, 0, 1, 0); + assertThat(syncStatistics).hasValues(2, 1, 0, 1, 0); - final String expectedMessage = format(TYPE_DOES_NOT_EXIST, "nonExistingKey"); - assertThat(errorCallBackMessages.get(0)).contains(expectedMessage); - } + final String expectedMessage = format(TYPE_DOES_NOT_EXIST, "nonExistingKey"); + assertThat(errorCallBackMessages.get(0)).contains(expectedMessage); + } - @Test - void syncDrafts_WithValidCustomFieldsChange_ShouldSyncIt() { - final List newCategoryDrafts = new ArrayList<>(); + @Test + void syncDrafts_WithValidCustomFieldsChange_ShouldSyncIt() { + final List newCategoryDrafts = new ArrayList<>(); - final Map customFieldsJsons = new HashMap<>(); - customFieldsJsons.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(false)); - customFieldsJsons - .put(LOCALISED_STRING_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.objectNode() - .put("de", "rot") - .put("en", "red") - .put("it", "rosso")); + final Map customFieldsJsons = new HashMap<>(); + customFieldsJsons.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(false)); + customFieldsJsons.put( + LOCALISED_STRING_CUSTOM_FIELD_NAME, + JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red").put("it", "rosso")); - final CategoryDraft categoryDraft1 = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), + final CategoryDraft categoryDraft1 = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "Modern Furniture"), LocalizedString.of(Locale.ENGLISH, "modern-furniture")) .key(oldCategoryKey) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, customFieldsJsons)) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, customFieldsJsons)) .build(); - newCategoryDrafts.add(categoryDraft1); + newCategoryDrafts.add(categoryDraft1); - final CategorySyncStatistics syncStatistics = categorySync.sync(newCategoryDrafts).toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(newCategoryDrafts).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - } + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + } - @Test - void syncDrafts_WithDraftWithAMissingParentKey_ShouldNotSyncIt() { - // Category draft coming from external source. - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "new-furniture")) + @Test + void syncDrafts_WithDraftWithAMissingParentKey_ShouldNotSyncIt() { + // Category draft coming from external source. + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), + LocalizedString.of(Locale.ENGLISH, "new-furniture")) .key("newCategoryKey") - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final String nonExistingParentKey = "nonExistingParent"; - final CategoryDraft categoryDraftWithMissingParent = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "new-furniture1")) + final String nonExistingParentKey = "nonExistingParent"; + final CategoryDraft categoryDraftWithMissingParent = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), + LocalizedString.of(Locale.ENGLISH, "new-furniture1")) .key("cat1") .parent(ResourceIdentifier.ofKey(nonExistingParentKey)) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + OLD_CATEGORY_CUSTOM_TYPE_KEY, createCustomFieldsJsonMap())) .build(); - final List categoryDrafts = new ArrayList<>(); - categoryDrafts.add(categoryDraft); - categoryDrafts.add(categoryDraftWithMissingParent); - + final List categoryDrafts = new ArrayList<>(); + categoryDrafts.add(categoryDraft); + categoryDrafts.add(categoryDraftWithMissingParent); - final CategorySyncStatistics syncStatistics = categorySync.sync(categoryDrafts) - .toCompletableFuture().join(); + final CategorySyncStatistics syncStatistics = + categorySync.sync(categoryDrafts).toCompletableFuture().join(); - assertThat(syncStatistics).hasValues(2, 2, 0, 0); + assertThat(syncStatistics).hasValues(2, 2, 0, 0); - final Map> categoryKeysWithMissingParents = syncStatistics - .getCategoryKeysWithMissingParents(); - assertThat(categoryKeysWithMissingParents).hasSize(1); + final Map> categoryKeysWithMissingParents = + syncStatistics.getCategoryKeysWithMissingParents(); + assertThat(categoryKeysWithMissingParents).hasSize(1); - final Set missingParentsChildren = categoryKeysWithMissingParents.get(nonExistingParentKey); - assertThat(missingParentsChildren).hasSize(1); + final Set missingParentsChildren = + categoryKeysWithMissingParents.get(nonExistingParentKey); + assertThat(missingParentsChildren).hasSize(1); - final String childKey = missingParentsChildren.iterator().next(); - assertThat(childKey).isEqualTo(categoryDraftWithMissingParent.getKey()); - } + final String childKey = missingParentsChildren.iterator().next(); + assertThat(childKey).isEqualTo(categoryDraftWithMissingParent.getKey()); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/categories/utils/CategoryReferenceResolutionUtilsIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/categories/utils/CategoryReferenceResolutionUtilsIT.java index 17bbb951f5..86c69e4974 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/categories/utils/CategoryReferenceResolutionUtilsIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/categories/utils/CategoryReferenceResolutionUtilsIT.java @@ -1,18 +1,5 @@ package com.commercetools.sync.integration.externalsource.categories.utils; -import io.sphere.sdk.categories.Category; -import io.sphere.sdk.categories.CategoryDraft; -import io.sphere.sdk.categories.CategoryDraftBuilder; -import io.sphere.sdk.categories.commands.CategoryCreateCommand; -import io.sphere.sdk.models.Asset; -import io.sphere.sdk.models.AssetDraft; -import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.util.List; - import static com.commercetools.sync.categories.utils.CategoryReferenceResolutionUtils.buildCategoryQuery; import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; @@ -28,68 +15,80 @@ import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; -class CategoryReferenceResolutionUtilsIT { - - /** - * Delete all categories and types from source and target project. Then create custom types for source and target - * CTP project categories. - */ - @BeforeAll - static void setup() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteTypesFromTargetAndSource(); - } - - /** - * Cleans up the target and source test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteTypesFromTargetAndSource(); - } - - @Test - void buildCategoryQuery_Always_ShouldFetchProductWithAllExpandedReferences() { - final CategoryDraft parentDraft = CategoryDraftBuilder.of(ofEnglish("parent"), ofEnglish("parent")) - .build(); - final Category parentCategory = - executeBlocking(CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(parentDraft))); - - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, ENGLISH, "anyName", CTP_TARGET_CLIENT); - - final Type assetsCustomType = createAssetsCustomType("assetsCustomTypeKey", ENGLISH, - "assetsCustomTypeName", CTP_TARGET_CLIENT); - final List assetDrafts = singletonList( - createAssetDraft("1", ofEnglish("1"), assetsCustomType.getId())); - - final CategoryDraft categoryDraft = CategoryDraftBuilder.of(ofEnglish("name"), ofEnglish("slug")) - .parent(parentCategory.toResourceIdentifier()) - .custom(getCustomFieldsDraft()) - .assets(assetDrafts) - .build(); - - executeBlocking(CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(categoryDraft))); - - final List categories = - executeBlocking(CTP_TARGET_CLIENT.execute(buildCategoryQuery().bySlug(ENGLISH, "slug"))).getResults(); - - assertThat(categories).hasSize(1); - final Category fetchedCategory = categories.get(0); - - // Assert category parent references are expanded. - assertThat(fetchedCategory.getParent()).isNotNull(); - assertThat(fetchedCategory.getParent().getObj()).isNotNull(); - - // Assert category custom type references are expanded. - assertThat(fetchedCategory.getCustom()).isNotNull(); - assertThat(fetchedCategory.getCustom().getType().getObj()).isNotNull(); +import io.sphere.sdk.categories.Category; +import io.sphere.sdk.categories.CategoryDraft; +import io.sphere.sdk.categories.CategoryDraftBuilder; +import io.sphere.sdk.categories.commands.CategoryCreateCommand; +import io.sphere.sdk.models.Asset; +import io.sphere.sdk.models.AssetDraft; +import io.sphere.sdk.types.Type; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; - // Assert category assets custom type references are expanded. - assertThat(fetchedCategory.getAssets()).hasSize(1); - final Asset masterVariantAsset = fetchedCategory.getAssets().get(0); - assertThat(masterVariantAsset.getCustom()).isNotNull(); - assertThat(masterVariantAsset.getCustom().getType().getObj()).isNotNull(); +class CategoryReferenceResolutionUtilsIT { - } + /** + * Delete all categories and types from source and target project. Then create custom types for + * source and target CTP project categories. + */ + @BeforeAll + static void setup() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteTypesFromTargetAndSource(); + } + + /** Cleans up the target and source test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteTypesFromTargetAndSource(); + } + + @Test + void buildCategoryQuery_Always_ShouldFetchProductWithAllExpandedReferences() { + final CategoryDraft parentDraft = + CategoryDraftBuilder.of(ofEnglish("parent"), ofEnglish("parent")).build(); + final Category parentCategory = + executeBlocking(CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(parentDraft))); + + createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, ENGLISH, "anyName", CTP_TARGET_CLIENT); + + final Type assetsCustomType = + createAssetsCustomType( + "assetsCustomTypeKey", ENGLISH, "assetsCustomTypeName", CTP_TARGET_CLIENT); + final List assetDrafts = + singletonList(createAssetDraft("1", ofEnglish("1"), assetsCustomType.getId())); + + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of(ofEnglish("name"), ofEnglish("slug")) + .parent(parentCategory.toResourceIdentifier()) + .custom(getCustomFieldsDraft()) + .assets(assetDrafts) + .build(); + + executeBlocking(CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(categoryDraft))); + + final List categories = + executeBlocking(CTP_TARGET_CLIENT.execute(buildCategoryQuery().bySlug(ENGLISH, "slug"))) + .getResults(); + + assertThat(categories).hasSize(1); + final Category fetchedCategory = categories.get(0); + + // Assert category parent references are expanded. + assertThat(fetchedCategory.getParent()).isNotNull(); + assertThat(fetchedCategory.getParent().getObj()).isNotNull(); + + // Assert category custom type references are expanded. + assertThat(fetchedCategory.getCustom()).isNotNull(); + assertThat(fetchedCategory.getCustom().getType().getObj()).isNotNull(); + + // Assert category assets custom type references are expanded. + assertThat(fetchedCategory.getAssets()).hasSize(1); + final Asset masterVariantAsset = fetchedCategory.getAssets().get(0); + assertThat(masterVariantAsset.getCustom()).isNotNull(); + assertThat(masterVariantAsset.getCustom().getType().getObj()).isNotNull(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/customers/CustomerSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/customers/CustomerSyncIT.java index 5d0bf163d5..e4b07a7e65 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/customers/CustomerSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/customers/CustomerSyncIT.java @@ -1,5 +1,21 @@ package com.commercetools.sync.integration.externalsource.customers; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.CUSTOMER_NUMBER_EXISTS_WARNING; +import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.createCustomerGroup; +import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.createSampleCustomerJohnDoe; +import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.deleteCustomerSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ITUtils.BOOLEAN_CUSTOM_FIELD_NAME; +import static com.commercetools.sync.integration.commons.utils.ITUtils.LOCALISED_STRING_CUSTOM_FIELD_NAME; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.StoreITUtils.createStore; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toMap; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.customers.CustomerSync; import com.commercetools.sync.customers.CustomerSyncOptions; import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; @@ -35,225 +51,209 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.stores.Store; import io.sphere.sdk.types.CustomFieldsDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.CUSTOMER_NUMBER_EXISTS_WARNING; -import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.createCustomerGroup; -import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.createSampleCustomerJohnDoe; -import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.deleteCustomerSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ITUtils.BOOLEAN_CUSTOM_FIELD_NAME; -import static com.commercetools.sync.integration.commons.utils.ITUtils.LOCALISED_STRING_CUSTOM_FIELD_NAME; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createCustomFieldsJsonMap; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.StoreITUtils.createStore; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toMap; -import static org.assertj.core.api.Assertions.assertThat; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomerSyncIT { - private CustomerDraft customerDraftJohnDoe; - private Customer customerJohnDoe; + private CustomerDraft customerDraftJohnDoe; + private Customer customerJohnDoe; - private List errorMessages; - private List warningMessages; - private List exceptions; - private List> updateActionList; - private CustomerSync customerSync; + private List errorMessages; + private List warningMessages; + private List exceptions; + private List> updateActionList; + private CustomerSync customerSync; - @BeforeEach - void setup() { - deleteCustomerSyncTestData(CTP_TARGET_CLIENT); - final ImmutablePair sampleCustomerJohnDoe = - createSampleCustomerJohnDoe(CTP_TARGET_CLIENT); - customerJohnDoe = sampleCustomerJohnDoe.getLeft(); - customerDraftJohnDoe = sampleCustomerJohnDoe.getRight(); - setUpCustomerSync(); - } + @BeforeEach + void setup() { + deleteCustomerSyncTestData(CTP_TARGET_CLIENT); + final ImmutablePair sampleCustomerJohnDoe = + createSampleCustomerJohnDoe(CTP_TARGET_CLIENT); + customerJohnDoe = sampleCustomerJohnDoe.getLeft(); + customerDraftJohnDoe = sampleCustomerJohnDoe.getRight(); + setUpCustomerSync(); + } - private void setUpCustomerSync() { - errorMessages = new ArrayList<>(); - exceptions = new ArrayList<>(); - warningMessages = new ArrayList<>(); - updateActionList = new ArrayList<>(); + private void setUpCustomerSync() { + errorMessages = new ArrayList<>(); + exceptions = new ArrayList<>(); + warningMessages = new ArrayList<>(); + updateActionList = new ArrayList<>(); - CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .warningCallback((exception, oldResource, newResource) - -> warningMessages.add(exception.getMessage())) - .beforeUpdateCallback((updateActions, customerDraft, customer) -> { - updateActionList.addAll(Objects.requireNonNull(updateActions)); - return updateActions; - }) + CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .warningCallback( + (exception, oldResource, newResource) -> + warningMessages.add(exception.getMessage())) + .beforeUpdateCallback( + (updateActions, customerDraft, customer) -> { + updateActionList.addAll(Objects.requireNonNull(updateActions)); + return updateActions; + }) .build(); - customerSync = new CustomerSync(customerSyncOptions); - } - - @AfterAll - static void tearDown() { - deleteCustomerSyncTestData(CTP_TARGET_CLIENT); - } + customerSync = new CustomerSync(customerSyncOptions); + } - @Test - void sync_WithSameCustomer_ShouldNotUpdateCustomer() { - final CustomerDraft sameCustomerDraft = CustomerDraftBuilder.of(customerDraftJohnDoe).build(); + @AfterAll + static void tearDown() { + deleteCustomerSyncTestData(CTP_TARGET_CLIENT); + } - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(sameCustomerDraft)) - .toCompletableFuture() - .join(); + @Test + void sync_WithSameCustomer_ShouldNotUpdateCustomer() { + final CustomerDraft sameCustomerDraft = CustomerDraftBuilder.of(customerDraftJohnDoe).build(); - assertThat(errorMessages).isEmpty(); - assertThat(warningMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(customerSyncStatistics).hasValues(1, 0, 0, 0); - assertThat(customerSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 customers were processed in total (0 created, 0 updated and 0 failed to sync)."); - } + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(sameCustomerDraft)).toCompletableFuture().join(); - @Test - void sync_WithNewCustomer_ShouldCreateCustomer() { - final CustomerDraft newCustomerDraft = - CustomerDraftBuilder.of(customerDraftJohnDoe) - .emailVerified(false) - .email("john-2@example.com") - .customerNumber("gold-2") - .key("customer-key-john-doe-2") - .build(); + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(customerSyncStatistics).hasValues(1, 0, 0, 0); + assertThat(customerSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 customers were processed in total (0 created, 0 updated and 0 failed to sync)."); + } - final CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + @Test + void sync_WithNewCustomer_ShouldCreateCustomer() { + final CustomerDraft newCustomerDraft = + CustomerDraftBuilder.of(customerDraftJohnDoe) + .emailVerified(false) + .email("john-2@example.com") + .customerNumber("gold-2") + .key("customer-key-john-doe-2") .build(); - final CustomerSync customerSync = new CustomerSync(customerSyncOptions); + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(newCustomerDraft)) - .toCompletableFuture() - .join(); + final CustomerSync customerSync = new CustomerSync(customerSyncOptions); - assertThat(errorMessages).isEmpty(); - assertThat(warningMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActionList).isEmpty(); + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(newCustomerDraft)).toCompletableFuture().join(); - assertThat(customerSyncStatistics).hasValues(1, 1, 0, 0); - assertThat(customerSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 customers were processed in total (1 created, 0 updated and 0 failed to sync)."); - } + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActionList).isEmpty(); - @Test - void sync_WithUpdatedCustomer_ShouldUpdateCustomer() { - final Store storeCologne = createStore(CTP_TARGET_CLIENT, "store-cologne"); - final CustomerDraft updatedCustomerDraft = - CustomerDraftBuilder.of(customerDraftJohnDoe) - .customerNumber("gold-new") // from gold-1, but can not be changed. - .email("john-new@example.com") //from john@example.com - .stores(asList( // store-cologne is added, store-munich is removed - ResourceIdentifier.ofKey(storeCologne.getKey()), - ResourceIdentifier.ofKey("store-hamburg"), - ResourceIdentifier.ofKey("store-berlin"))) - .build(); + assertThat(customerSyncStatistics).hasValues(1, 1, 0, 0); + assertThat(customerSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 customers were processed in total (1 created, 0 updated and 0 failed to sync)."); + } - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(updatedCustomerDraft)) - .toCompletableFuture() - .join(); + @Test + void sync_WithUpdatedCustomer_ShouldUpdateCustomer() { + final Store storeCologne = createStore(CTP_TARGET_CLIENT, "store-cologne"); + final CustomerDraft updatedCustomerDraft = + CustomerDraftBuilder.of(customerDraftJohnDoe) + .customerNumber("gold-new") // from gold-1, but can not be changed. + .email("john-new@example.com") // from john@example.com + .stores( + asList( // store-cologne is added, store-munich is removed + ResourceIdentifier.ofKey(storeCologne.getKey()), + ResourceIdentifier.ofKey("store-hamburg"), + ResourceIdentifier.ofKey("store-berlin"))) + .build(); - assertThat(errorMessages).isEmpty(); - assertThat(warningMessages) - .containsExactly(format(CUSTOMER_NUMBER_EXISTS_WARNING, updatedCustomerDraft.getKey(), "gold-1")); - assertThat(exceptions).isEmpty(); - assertThat(updateActionList).containsExactly( + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(updatedCustomerDraft)).toCompletableFuture().join(); + + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages) + .containsExactly( + format(CUSTOMER_NUMBER_EXISTS_WARNING, updatedCustomerDraft.getKey(), "gold-1")); + assertThat(exceptions).isEmpty(); + assertThat(updateActionList) + .containsExactly( ChangeEmail.of("john-new@example.com"), - SetStores.of(asList( - ResourceIdentifier.ofKey("store-cologne"), - ResourceIdentifier.ofKey("store-hamburg"), - ResourceIdentifier.ofKey("store-berlin"))) - ); + SetStores.of( + asList( + ResourceIdentifier.ofKey("store-cologne"), + ResourceIdentifier.ofKey("store-hamburg"), + ResourceIdentifier.ofKey("store-berlin")))); - assertThat(customerSyncStatistics).hasValues(1, 0, 1, 0); - assertThat(customerSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 customers were processed in total (0 created, 1 updated and 0 failed to sync)."); - } + assertThat(customerSyncStatistics).hasValues(1, 0, 1, 0); + assertThat(customerSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 customers were processed in total (0 created, 1 updated and 0 failed to sync)."); + } - @Test - void sync_WithUpdatedAllFieldsOfCustomer_ShouldUpdateCustomerWithAllExpectedActions() { - final Store storeCologne = createStore(CTP_TARGET_CLIENT, "store-cologne"); - final CustomerGroup customerGroupSilverMember = - createCustomerGroup(CTP_TARGET_CLIENT, "silver members", "silver"); + @Test + void sync_WithUpdatedAllFieldsOfCustomer_ShouldUpdateCustomerWithAllExpectedActions() { + final Store storeCologne = createStore(CTP_TARGET_CLIENT, "store-cologne"); + final CustomerGroup customerGroupSilverMember = + createCustomerGroup(CTP_TARGET_CLIENT, "silver members", "silver"); - final CustomerDraft updatedCustomerDraft = - CustomerDraftBuilder - .of("jane@example.com", "54321") - .customerNumber("gold-1") // can not be changed after it set. - .key("customer-key-john-doe") - .stores(asList( + final CustomerDraft updatedCustomerDraft = + CustomerDraftBuilder.of("jane@example.com", "54321") + .customerNumber("gold-1") // can not be changed after it set. + .key("customer-key-john-doe") + .stores( + asList( ResourceIdentifier.ofKey(storeCologne.getKey()), // new store ResourceIdentifier.ofKey("store-munich"), ResourceIdentifier.ofKey("store-hamburg"), ResourceIdentifier.ofKey("store-berlin"))) - .firstName("Jane") - .lastName("Doe") - .middleName("") - .title("Miss") - .salutation("") - .dateOfBirth(LocalDate.now().minusYears(26)) - .companyName("Acme Corporation 2") - .vatId("DE000000000") - .emailVerified(true) - .customerGroup(ResourceIdentifier.ofKey(customerGroupSilverMember.getKey())) - .addresses(asList( // address2 is removed, address4 is added + .firstName("Jane") + .lastName("Doe") + .middleName("") + .title("Miss") + .salutation("") + .dateOfBirth(LocalDate.now().minusYears(26)) + .companyName("Acme Corporation 2") + .vatId("DE000000000") + .emailVerified(true) + .customerGroup(ResourceIdentifier.ofKey(customerGroupSilverMember.getKey())) + .addresses( + asList( // address2 is removed, address4 is added Address.of(CountryCode.DE).withCity("berlin").withKey("address1"), Address.of(CountryCode.DE).withCity("munich").withKey("address3"), Address.of(CountryCode.DE).withCity("cologne").withKey("address4"))) - .defaultBillingAddress(2) // 0 becomes 2 -> berlin to cologne. - .billingAddresses(singletonList(2)) // 0, 1 becomes 2 -> berlin, hamburg to cologne. - .defaultShippingAddress(1) // 2 becomes 1 -> munich to munich. - .shippingAddresses(asList(0, 1)) // 2 become 0, 1 -> munich to berlin, munich. - .custom(CustomFieldsDraft.ofTypeKeyAndJson("customer-type-gold", - createCustomFieldsJsonMap())) - .locale(Locale.FRENCH) - .build(); + .defaultBillingAddress(2) // 0 becomes 2 -> berlin to cologne. + .billingAddresses(singletonList(2)) // 0, 1 becomes 2 -> berlin, hamburg to cologne. + .defaultShippingAddress(1) // 2 becomes 1 -> munich to munich. + .shippingAddresses(asList(0, 1)) // 2 become 0, 1 -> munich to berlin, munich. + .custom( + CustomFieldsDraft.ofTypeKeyAndJson( + "customer-type-gold", createCustomFieldsJsonMap())) + .locale(Locale.FRENCH) + .build(); - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(updatedCustomerDraft)) - .toCompletableFuture() - .join(); + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(updatedCustomerDraft)).toCompletableFuture().join(); - assertThat(errorMessages).isEmpty(); - assertThat(warningMessages).isEmpty(); - assertThat(exceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages).isEmpty(); + assertThat(exceptions).isEmpty(); - final Map addressKeyToIdMap = - customerJohnDoe.getAddresses().stream().collect(toMap(Address::getKey, Address::getId)); + final Map addressKeyToIdMap = + customerJohnDoe.getAddresses().stream().collect(toMap(Address::getKey, Address::getId)); - assertThat(updateActionList).containsExactly( + assertThat(updateActionList) + .containsExactly( ChangeEmail.of("jane@example.com"), SetFirstName.of("Jane"), SetMiddleName.of(""), SetTitle.of("Miss"), SetSalutation.of(""), - SetCustomerGroup.of(Reference.of(CustomerGroup.referenceTypeId(), customerGroupSilverMember.getId())), + SetCustomerGroup.of( + Reference.of(CustomerGroup.referenceTypeId(), customerGroupSilverMember.getId())), SetCompanyName.of("Acme Corporation 2"), SetDateOfBirth.of(LocalDate.now().minusYears(26)), SetVatId.of("DE000000000"), @@ -264,15 +264,16 @@ void sync_WithUpdatedAllFieldsOfCustomer_ShouldUpdateCustomerWithAllExpectedActi SetDefaultBillingAddress.ofKey("address4"), AddShippingAddressId.ofKey("address1"), AddBillingAddressId.ofKey("address4"), - SetCustomField.ofJson(LOCALISED_STRING_CUSTOM_FIELD_NAME, + SetCustomField.ofJson( + LOCALISED_STRING_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")), - SetCustomField.ofJson(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(false)), - AddStore.of(ResourceIdentifier.ofKey(storeCologne.getKey())) - ); + SetCustomField.ofJson( + BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(false)), + AddStore.of(ResourceIdentifier.ofKey(storeCologne.getKey()))); - assertThat(customerSyncStatistics).hasValues(1, 0, 1, 0); - assertThat(customerSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 customers were processed in total (0 created, 1 updated and 0 failed to sync)."); - } + assertThat(customerSyncStatistics).hasValues(1, 0, 1, 0); + assertThat(customerSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 customers were processed in total (0 created, 1 updated and 0 failed to sync)."); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/customobjects/CustomObjectSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/customobjects/CustomObjectSyncIT.java index 1727b99c36..093ba16a0f 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/customobjects/CustomObjectSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/customobjects/CustomObjectSyncIT.java @@ -1,5 +1,14 @@ package com.commercetools.sync.integration.externalsource.customobjects; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.createCustomObject; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteCustomObject; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static java.lang.String.format; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customobjects.CustomObjectSync; import com.commercetools.sync.customobjects.CustomObjectSyncOptions; import com.commercetools.sync.customobjects.CustomObjectSyncOptionsBuilder; @@ -16,270 +25,301 @@ import io.sphere.sdk.customobjects.commands.CustomObjectUpsertCommand; import io.sphere.sdk.customobjects.queries.CustomObjectQuery; import io.sphere.sdk.queries.PagedQueryResult; - import io.sphere.sdk.utils.CompletableFutureUtils; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.createCustomObject; -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteCustomObject; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static java.lang.String.format; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class CustomObjectSyncIT { - private ObjectNode customObject1Value; + private ObjectNode customObject1Value; - @BeforeEach - void setup() { - deleteCustomObject(CTP_TARGET_CLIENT, "key1", "container1"); - deleteCustomObject(CTP_TARGET_CLIENT, "key2", "container2"); - customObject1Value = - JsonNodeFactory.instance.objectNode().put("name", "value1"); + @BeforeEach + void setup() { + deleteCustomObject(CTP_TARGET_CLIENT, "key1", "container1"); + deleteCustomObject(CTP_TARGET_CLIENT, "key2", "container2"); + customObject1Value = JsonNodeFactory.instance.objectNode().put("name", "value1"); - createCustomObject(CTP_TARGET_CLIENT, "key1", "container1", customObject1Value); - } + createCustomObject(CTP_TARGET_CLIENT, "key1", "container1", customObject1Value); + } - @AfterAll - static void tearDown() { - deleteCustomObject(CTP_TARGET_CLIENT, "key1", "container1"); - deleteCustomObject(CTP_TARGET_CLIENT, "key2", "container2"); - } + @AfterAll + static void tearDown() { + deleteCustomObject(CTP_TARGET_CLIENT, "key1", "container1"); + deleteCustomObject(CTP_TARGET_CLIENT, "key2", "container2"); + } - @Test - void sync_withNewCustomObject_shouldCreateCustomObject() { + @Test + void sync_withNewCustomObject_shouldCreateCustomObject() { - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of( - CTP_TARGET_CLIENT).build(); + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final CustomObjectSync customObjectSync = new CustomObjectSync(customObjectSyncOptions); + final CustomObjectSync customObjectSync = new CustomObjectSync(customObjectSyncOptions); - final ObjectNode customObject2Value = - JsonNodeFactory.instance.objectNode().put("name", "value1"); + final ObjectNode customObject2Value = + JsonNodeFactory.instance.objectNode().put("name", "value1"); - final CustomObjectDraft customObjectDraft = CustomObjectDraft.ofUnversionedUpsert("container2", - "key2", customObject2Value); + final CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert("container2", "key2", customObject2Value); - final CustomObjectSyncStatistics customObjectSyncStatistics = customObjectSync + final CustomObjectSyncStatistics customObjectSyncStatistics = + customObjectSync .sync(Collections.singletonList(customObjectDraft)) - .toCompletableFuture().join(); + .toCompletableFuture() + .join(); - assertThat(customObjectSyncStatistics).hasValues(1, 1, 0, 0); - } + assertThat(customObjectSyncStatistics).hasValues(1, 1, 0, 0); + } - @Test - void sync_withExistingCustomObjectThatHasDifferentValue_shouldUpdateCustomObject() { - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of( - CTP_TARGET_CLIENT).build(); - final CustomObjectSync customObjectSync = new CustomObjectSync(customObjectSyncOptions); + @Test + void sync_withExistingCustomObjectThatHasDifferentValue_shouldUpdateCustomObject() { + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + final CustomObjectSync customObjectSync = new CustomObjectSync(customObjectSyncOptions); - final ObjectNode customObject2Value = - JsonNodeFactory.instance.objectNode().put("name", "value2"); + final ObjectNode customObject2Value = + JsonNodeFactory.instance.objectNode().put("name", "value2"); - final CustomObjectDraft customObjectDraft = CustomObjectDraft.ofUnversionedUpsert("container1", - "key1", customObject2Value); + final CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert("container1", "key1", customObject2Value); - final CustomObjectSyncStatistics customObjectSyncStatistics = customObjectSync + final CustomObjectSyncStatistics customObjectSyncStatistics = + customObjectSync .sync(Collections.singletonList(customObjectDraft)) - .toCompletableFuture().join(); + .toCompletableFuture() + .join(); - assertThat(customObjectSyncStatistics).hasValues(1, 0, 1, 0); - } + assertThat(customObjectSyncStatistics).hasValues(1, 0, 1, 0); + } - @Test - void sync_withExistingCustomObjectThatHasSameValue_shouldNotUpdateCustomObject() { + @Test + void sync_withExistingCustomObjectThatHasSameValue_shouldNotUpdateCustomObject() { - final CustomObjectDraft customObjectDraft = CustomObjectDraft.ofUnversionedUpsert("container1", - "key1", customObject1Value); + final CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert("container1", "key1", customObject1Value); - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of( - CTP_TARGET_CLIENT).build(); + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final CustomObjectSync customObjectSync = new CustomObjectSync(customObjectSyncOptions); + final CustomObjectSync customObjectSync = new CustomObjectSync(customObjectSyncOptions); - final CustomObjectSyncStatistics customObjectSyncStatistics = customObjectSync + final CustomObjectSyncStatistics customObjectSyncStatistics = + customObjectSync .sync(Collections.singletonList(customObjectDraft)) - .toCompletableFuture().join(); - - assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 0); - } - - @Test - void sync_withChangedCustomObjectAndConcurrentModificationException_shouldRetryAndUpdateCustomObject() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final CustomObjectUpsertCommand customObjectUpsertCommand = any(CustomObjectUpsertCommand.class); - when(spyClient.execute(customObjectUpsertCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - final ObjectNode newCustomObjectValue = JsonNodeFactory.instance.objectNode().put("name", "value2"); - List errorCallBackMessages = new ArrayList<>(); - List warningCallBackMessages = new ArrayList<>(); - List errorCallBackExceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyOptions = CustomObjectSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + .toCompletableFuture() + .join(); + + assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 0); + } + + @Test + void + sync_withChangedCustomObjectAndConcurrentModificationException_shouldRetryAndUpdateCustomObject() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final CustomObjectUpsertCommand customObjectUpsertCommand = + any(CustomObjectUpsertCommand.class); + when(spyClient.execute(customObjectUpsertCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + final ObjectNode newCustomObjectValue = + JsonNodeFactory.instance.objectNode().put("name", "value2"); + List errorCallBackMessages = new ArrayList<>(); + List warningCallBackMessages = new ArrayList<>(); + List errorCallBackExceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyOptions = + CustomObjectSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final CustomObjectSync customObjectSync = new CustomObjectSync(spyOptions); + final CustomObjectSync customObjectSync = new CustomObjectSync(spyOptions); - final CustomObjectDraft customObjectDraft = CustomObjectDraft.ofUnversionedUpsert("container1", - "key1", newCustomObjectValue); + final CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert("container1", "key1", newCustomObjectValue); - //test - final CustomObjectSyncStatistics customObjectSyncStatistics = customObjectSync + // test + final CustomObjectSyncStatistics customObjectSyncStatistics = + customObjectSync .sync(Collections.singletonList(customObjectDraft)) - .toCompletableFuture().join(); - - assertThat(customObjectSyncStatistics).hasValues(1, 0, 1, 0); - Assertions.assertThat(errorCallBackExceptions).isEmpty(); - Assertions.assertThat(errorCallBackMessages).isEmpty(); - Assertions.assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withChangedCustomObjectWithBadGatewayExceptionInsideUpdateRetry_shouldFailToUpdate() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final CustomObjectUpsertCommand upsertCommand = any(CustomObjectUpsertCommand.class); - when(spyClient.execute(upsertCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - final CustomObjectQuery customObjectQuery = any(CustomObjectQuery.class); - when(spyClient.execute(customObjectQuery)) - .thenCallRealMethod() - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); - - final ObjectNode newCustomObjectValue = JsonNodeFactory.instance.objectNode().put("name", "value2"); - List errorCallBackMessages = new ArrayList<>(); - List errorCallBackExceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyOptions = CustomObjectSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + .toCompletableFuture() + .join(); + + assertThat(customObjectSyncStatistics).hasValues(1, 0, 1, 0); + Assertions.assertThat(errorCallBackExceptions).isEmpty(); + Assertions.assertThat(errorCallBackMessages).isEmpty(); + Assertions.assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withChangedCustomObjectWithBadGatewayExceptionInsideUpdateRetry_shouldFailToUpdate() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final CustomObjectUpsertCommand upsertCommand = any(CustomObjectUpsertCommand.class); + when(spyClient.execute(upsertCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + final CustomObjectQuery customObjectQuery = any(CustomObjectQuery.class); + when(spyClient.execute(customObjectQuery)) + .thenCallRealMethod() + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); + + final ObjectNode newCustomObjectValue = + JsonNodeFactory.instance.objectNode().put("name", "value2"); + List errorCallBackMessages = new ArrayList<>(); + List errorCallBackExceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyOptions = + CustomObjectSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final CustomObjectSync customObjectSync = new CustomObjectSync(spyOptions); + final CustomObjectSync customObjectSync = new CustomObjectSync(spyOptions); - final CustomObjectDraft customObjectDraft = CustomObjectDraft.ofUnversionedUpsert("container1", - "key1", newCustomObjectValue); + final CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert("container1", "key1", newCustomObjectValue); - final CustomObjectSyncStatistics customObjectSyncStatistics = customObjectSync + final CustomObjectSyncStatistics customObjectSyncStatistics = + customObjectSync .sync(Collections.singletonList(customObjectDraft)) - .toCompletableFuture().join(); - - assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 1); - Assertions.assertThat(errorCallBackMessages).hasSize(1); - Assertions.assertThat(errorCallBackExceptions).hasSize(1); - Assertions.assertThat(errorCallBackMessages.get(0)).contains( - format("Failed to update custom object with key: '%s'. Reason: Failed to fetch from CTP while retrying " - + "after concurrency modification.", CustomObjectCompositeIdentifier.of(customObjectDraft))); - } - - @Test - void sync_withConcurrentModificationExceptionAndUnexpectedDelete_shouldFailToReFetchAndUpdate() { - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final CustomObjectUpsertCommand customObjectUpsertCommand = any(CustomObjectUpsertCommand.class); - when(spyClient.execute(customObjectUpsertCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - final CustomObjectQuery customObjectQuery = any(CustomObjectQuery.class); - - when(spyClient.execute(customObjectQuery)) - .thenCallRealMethod() - .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); - - final ObjectNode newCustomObjectValue = JsonNodeFactory.instance.objectNode().put("name", "value2"); - List errorCallBackMessages = new ArrayList<>(); - List errorCallBackExceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyOptions = CustomObjectSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + .toCompletableFuture() + .join(); + + assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 1); + Assertions.assertThat(errorCallBackMessages).hasSize(1); + Assertions.assertThat(errorCallBackExceptions).hasSize(1); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to update custom object with key: '%s'. Reason: Failed to fetch from CTP while retrying " + + "after concurrency modification.", + CustomObjectCompositeIdentifier.of(customObjectDraft))); + } + + @Test + void sync_withConcurrentModificationExceptionAndUnexpectedDelete_shouldFailToReFetchAndUpdate() { + + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final CustomObjectUpsertCommand customObjectUpsertCommand = + any(CustomObjectUpsertCommand.class); + when(spyClient.execute(customObjectUpsertCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + final CustomObjectQuery customObjectQuery = any(CustomObjectQuery.class); + + when(spyClient.execute(customObjectQuery)) + .thenCallRealMethod() + .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); + + final ObjectNode newCustomObjectValue = + JsonNodeFactory.instance.objectNode().put("name", "value2"); + List errorCallBackMessages = new ArrayList<>(); + List errorCallBackExceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyOptions = + CustomObjectSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final CustomObjectSync customObjectSync = new CustomObjectSync(spyOptions); + final CustomObjectSync customObjectSync = new CustomObjectSync(spyOptions); - final CustomObjectDraft customObjectDraft = CustomObjectDraft.ofUnversionedUpsert("container1", - "key1", newCustomObjectValue); + final CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert("container1", "key1", newCustomObjectValue); - final CustomObjectSyncStatistics customObjectSyncStatistics = customObjectSync + final CustomObjectSyncStatistics customObjectSyncStatistics = + customObjectSync .sync(Collections.singletonList(customObjectDraft)) - .toCompletableFuture().join(); - - assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 1); - Assertions.assertThat(errorCallBackMessages).hasSize(1); - Assertions.assertThat(errorCallBackExceptions).hasSize(1); - - Assertions.assertThat(errorCallBackMessages.get(0)).contains( - format("Failed to update custom object with key: '%s'. Reason: Not found when attempting to fetch while" - + " retrying after concurrency modification.", CustomObjectCompositeIdentifier.of(customObjectDraft))); - } - - @Test - void sync_withNewCustomObjectAndBadRequest_shouldNotCreateButHandleError() { - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final CustomObjectUpsertCommand upsertCommand = any(CustomObjectUpsertCommand.class); - when(spyClient.execute(upsertCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadRequestException("bad request"))) - .thenCallRealMethod(); - - final ObjectNode newCustomObjectValue = JsonNodeFactory.instance.objectNode().put("name", "value2"); - final CustomObjectDraft newCustomObjectDraft = CustomObjectDraft.ofUnversionedUpsert("container2", - "key2", newCustomObjectValue); - - List errorCallBackMessages = new ArrayList<>(); - List errorCallBackExceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyOptions = CustomObjectSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); + .toCompletableFuture() + .join(); + + assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 1); + Assertions.assertThat(errorCallBackMessages).hasSize(1); + Assertions.assertThat(errorCallBackExceptions).hasSize(1); + + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to update custom object with key: '%s'. Reason: Not found when attempting to fetch while" + + " retrying after concurrency modification.", + CustomObjectCompositeIdentifier.of(customObjectDraft))); + } + + @Test + void sync_withNewCustomObjectAndBadRequest_shouldNotCreateButHandleError() { + + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final CustomObjectUpsertCommand upsertCommand = any(CustomObjectUpsertCommand.class); + when(spyClient.execute(upsertCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new BadRequestException("bad request"))) + .thenCallRealMethod(); + + final ObjectNode newCustomObjectValue = + JsonNodeFactory.instance.objectNode().put("name", "value2"); + final CustomObjectDraft newCustomObjectDraft = + CustomObjectDraft.ofUnversionedUpsert("container2", "key2", newCustomObjectValue); + + List errorCallBackMessages = new ArrayList<>(); + List errorCallBackExceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyOptions = + CustomObjectSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }) - .build(); - - final CustomObjectSync customObjectSync = new CustomObjectSync(spyOptions); - - final CustomObjectSyncStatistics customObjectSyncStatistics = customObjectSync - .sync(Collections.singletonList(newCustomObjectDraft)) - .toCompletableFuture().join(); - - assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 1); - Assertions.assertThat(errorCallBackMessages).hasSize(1); - Assertions.assertThat(errorCallBackExceptions).hasSize(1); - Assertions.assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); - Assertions.assertThat(errorCallBackExceptions.get(0).getCause()).isExactlyInstanceOf(BadRequestException.class); - Assertions.assertThat(errorCallBackMessages.get(0)).contains( - format("Failed to create custom object with key: '%s'.", - CustomObjectCompositeIdentifier.of(newCustomObjectDraft))); - } -} \ No newline at end of file + .build(); + + final CustomObjectSync customObjectSync = new CustomObjectSync(spyOptions); + + final CustomObjectSyncStatistics customObjectSyncStatistics = + customObjectSync + .sync(Collections.singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + + assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 1); + Assertions.assertThat(errorCallBackMessages).hasSize(1); + Assertions.assertThat(errorCallBackExceptions).hasSize(1); + Assertions.assertThat(errorCallBackExceptions.get(0)) + .isExactlyInstanceOf(CompletionException.class); + Assertions.assertThat(errorCallBackExceptions.get(0).getCause()) + .isExactlyInstanceOf(BadRequestException.class); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to create custom object with key: '%s'.", + CustomObjectCompositeIdentifier.of(newCustomObjectDraft))); + } +} diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductReferenceResolverIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductReferenceResolverIT.java index b875276940..be46dd0101 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductReferenceResolverIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductReferenceResolverIT.java @@ -1,31 +1,5 @@ package com.commercetools.sync.integration.externalsource.products; -import com.commercetools.sync.commons.exceptions.SyncException; -import com.commercetools.sync.commons.utils.QuadConsumer; -import com.commercetools.sync.products.ProductSync; -import com.commercetools.sync.products.ProductSyncOptions; -import com.commercetools.sync.products.ProductSyncOptionsBuilder; -import com.commercetools.sync.products.helpers.ProductSyncStatistics; -import io.sphere.sdk.categories.Category; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.products.Product; -import io.sphere.sdk.products.ProductDraft; -import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.CompletionException; - import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; @@ -48,89 +22,132 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import com.commercetools.sync.commons.exceptions.SyncException; +import com.commercetools.sync.commons.utils.QuadConsumer; +import com.commercetools.sync.products.ProductSync; +import com.commercetools.sync.products.ProductSyncOptions; +import com.commercetools.sync.products.ProductSyncOptionsBuilder; +import com.commercetools.sync.products.helpers.ProductSyncStatistics; +import io.sphere.sdk.categories.Category; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.products.Product; +import io.sphere.sdk.products.ProductDraft; +import io.sphere.sdk.producttypes.ProductType; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletionException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class ProductReferenceResolverIT { - private static ProductType productType; - private static List categories; - private List errorCallBackExceptions; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_TARGET_CLIENT); - categories = createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - @BeforeEach - void setupPerTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - } - - private void clearSyncTestCollections() { - errorCallBackExceptions = new ArrayList<>(); - } - - private ProductSyncOptions getProductSyncOptions() { - final QuadConsumer, Optional, List>> - errorCallBack = (exception, newResource, oldResource, updateActions) -> { - errorCallBackExceptions.add(exception.getCause()); - }; - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallBack) - .build(); - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withNewProductWithInvalidCategoryReferences_ShouldFailCreatingTheProduct() { - // Create a list of category references that contains one valid and one invalid reference. - final Set> invalidCategoryReferences = new HashSet<>(); - invalidCategoryReferences.add(ResourceIdentifier.ofId(categories.get(0).getId())); - invalidCategoryReferences.add(ResourceIdentifier.ofId(null)); - - // Create a product with the invalid category references. (i.e. not ready for reference resolution). - final ProductDraft productDraft = - createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, referenceOfId(productType.getKey()), null, - null, invalidCategoryReferences, createRandomCategoryOrderHints(invalidCategoryReferences)); - - final ProductSync productSync = new ProductSync(getProductSyncOptions()); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions).hasSize(1); - final Throwable exception = errorCallBackExceptions.get(0); - assertThat(exception).isExactlyInstanceOf(CompletionException.class) - .hasMessageContaining("Failed to resolve 'category' resource identifier on ProductDraft " - + "with key:'productKey1'") - .hasMessageContaining(format("Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void sync_withNewProductWithNonExistingProductTypeReference_ShouldFailCreatingTheProduct() { - final ProductDraft productDraft = - createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, ResourceIdentifier.ofKey("non-existing-key"), null, - null, Collections.emptySet(), null); - - final ProductSync productSync = new ProductSync(getProductSyncOptions()); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions).hasSize(1); - final Throwable exception = errorCallBackExceptions.get(0); - assertThat(exception) - .isExactlyInstanceOf(CompletionException.class) - .hasMessageContaining("Failed to resolve 'product-type' resource identifier on " + private static ProductType productType; + private static List categories; + private List errorCallBackExceptions; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, + Locale.ENGLISH, + OLD_CATEGORY_CUSTOM_TYPE_NAME, + CTP_TARGET_CLIENT); + categories = createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + @BeforeEach + void setupPerTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + } + + private void clearSyncTestCollections() { + errorCallBackExceptions = new ArrayList<>(); + } + + private ProductSyncOptions getProductSyncOptions() { + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallBack = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackExceptions.add(exception.getCause()); + }; + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT).errorCallback(errorCallBack).build(); + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withNewProductWithInvalidCategoryReferences_ShouldFailCreatingTheProduct() { + // Create a list of category references that contains one valid and one invalid reference. + final Set> invalidCategoryReferences = new HashSet<>(); + invalidCategoryReferences.add(ResourceIdentifier.ofId(categories.get(0).getId())); + invalidCategoryReferences.add(ResourceIdentifier.ofId(null)); + + // Create a product with the invalid category references. (i.e. not ready for reference + // resolution). + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + referenceOfId(productType.getKey()), + null, + null, + invalidCategoryReferences, + createRandomCategoryOrderHints(invalidCategoryReferences)); + + final ProductSync productSync = new ProductSync(getProductSyncOptions()); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions).hasSize(1); + final Throwable exception = errorCallBackExceptions.get(0); + assertThat(exception) + .isExactlyInstanceOf(CompletionException.class) + .hasMessageContaining( + "Failed to resolve 'category' resource identifier on ProductDraft " + + "with key:'productKey1'") + .hasMessageContaining(format("Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void sync_withNewProductWithNonExistingProductTypeReference_ShouldFailCreatingTheProduct() { + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + ResourceIdentifier.ofKey("non-existing-key"), + null, + null, + Collections.emptySet(), + null); + + final ProductSync productSync = new ProductSync(getProductSyncOptions()); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions).hasSize(1); + final Throwable exception = errorCallBackExceptions.get(0); + assertThat(exception) + .isExactlyInstanceOf(CompletionException.class) + .hasMessageContaining( + "Failed to resolve 'product-type' resource identifier on " + "ProductDraft with key:'productKey1'") - .hasMessageContaining(format("Reason: %s", format(PRODUCT_TYPE_DOES_NOT_EXIST, "non-existing-key"))); - } + .hasMessageContaining( + format("Reason: %s", format(PRODUCT_TYPE_DOES_NOT_EXIST, "non-existing-key"))); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncFilterIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncFilterIT.java index c9c1c86b36..947da10d14 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncFilterIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncFilterIT.java @@ -1,5 +1,31 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategories; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.geResourceIdentifiersWithKeys; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCategoryDrafts; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getReferencesWithIds; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ActionGroup.CATEGORIES; +import static com.commercetools.sync.products.ActionGroup.NAME; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_CHANGED_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraft; +import static com.commercetools.sync.products.ProductSyncMockUtils.createRandomCategoryOrderHints; +import static com.commercetools.sync.products.SyncFilter.ofBlackList; +import static com.commercetools.sync.products.SyncFilter.ofWhiteList; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.producttypes.ProductType.referenceOfId; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -22,168 +48,170 @@ import io.sphere.sdk.products.commands.updateactions.RemoveFromCategory; import io.sphere.sdk.products.commands.updateactions.SetCategoryOrderHint; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategories; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCategoryDrafts; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getReferencesWithIds; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.geResourceIdentifiersWithKeys; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ActionGroup.CATEGORIES; -import static com.commercetools.sync.products.ActionGroup.NAME; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_CHANGED_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraft; -import static com.commercetools.sync.products.ProductSyncMockUtils.createRandomCategoryOrderHints; -import static com.commercetools.sync.products.SyncFilter.ofBlackList; -import static com.commercetools.sync.products.SyncFilter.ofWhiteList; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.producttypes.ProductType.referenceOfId; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncFilterIT { - private static ProductType productType; - private static List> categoryReferencesWithIds; - private static Set> categoryResourceIdentifiersWithKeys; - private ProductSyncOptionsBuilder syncOptionsBuilder; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> updateActionsFromSync; - - /** - * Delete all product related test data from target project. Then create custom types for the categories and a - * productType for the products of the target CTP project. - */ - @BeforeAll - static void setupAllTests() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_TARGET_CLIENT); - final List categories = createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); - categoryReferencesWithIds = getReferencesWithIds(categories); - categoryResourceIdentifiersWithKeys = geResourceIdentifiersWithKeys(categories); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - /** - * 1. Deletes all products from target CTP project - * 2. Clears all sync collections used for test assertions. - * 3. Creates an instance for {@link ProductSyncOptionsBuilder} that will be used in the tests to build - * {@link ProductSyncOptions} instances. - * 4. Create a product in the target CTP project. - */ - @BeforeEach - void setupPerTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptionsBuilder = getProductSyncOptionsBuilder(); - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, productType.toReference(), - null, null, categoryReferencesWithIds, createRandomCategoryOrderHints(categoryReferencesWithIds)); - executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); - } - - private void clearSyncTestCollections() { - updateActionsFromSync = new ArrayList<>(); - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - } - - private ProductSyncOptionsBuilder getProductSyncOptionsBuilder() { - final QuadConsumer, Optional, List>> - errorCallBack = (exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }; - final TriConsumer, Optional> warningCallBack = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - - final TriFunction>, ProductDraft, Product, List>> - actionsCallBack = (updateActions, newDraft, oldProduct) -> { - updateActionsFromSync.addAll(updateActions); - return updateActions; + private static ProductType productType; + private static List> categoryReferencesWithIds; + private static Set> categoryResourceIdentifiersWithKeys; + private ProductSyncOptionsBuilder syncOptionsBuilder; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> updateActionsFromSync; + + /** + * Delete all product related test data from target project. Then create custom types for the + * categories and a productType for the products of the target CTP project. + */ + @BeforeAll + static void setupAllTests() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, + Locale.ENGLISH, + OLD_CATEGORY_CUSTOM_TYPE_NAME, + CTP_TARGET_CLIENT); + final List categories = + createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); + categoryReferencesWithIds = getReferencesWithIds(categories); + categoryResourceIdentifiersWithKeys = geResourceIdentifiersWithKeys(categories); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + /** + * 1. Deletes all products from target CTP project 2. Clears all sync collections used for test + * assertions. 3. Creates an instance for {@link ProductSyncOptionsBuilder} that will be used in + * the tests to build {@link ProductSyncOptions} instances. 4. Create a product in the target CTP + * project. + */ + @BeforeEach + void setupPerTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptionsBuilder = getProductSyncOptionsBuilder(); + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + productType.toReference(), + null, + null, + categoryReferencesWithIds, + createRandomCategoryOrderHints(categoryReferencesWithIds)); + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); + } + + private void clearSyncTestCollections() { + updateActionsFromSync = new ArrayList<>(); + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + } + + private ProductSyncOptionsBuilder getProductSyncOptionsBuilder() { + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallBack = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }; + final TriConsumer, Optional> warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + + final TriFunction< + List>, ProductDraft, Product, List>> + actionsCallBack = + (updateActions, newDraft, oldProduct) -> { + updateActionsFromSync.addAll(updateActions); + return updateActions; }; - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallBack) - .warningCallback(warningCallBack) - .beforeUpdateCallback(actionsCallBack); - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withChangedProductBlackListingCategories_shouldUpdateProductWithoutCategories() { - final ProductDraft productDraft = - createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, referenceOfId(productType.getKey()), null, - null, categoryResourceIdentifiersWithKeys, createRandomCategoryOrderHints( - categoryResourceIdentifiersWithKeys)); - - final ProductSyncOptions syncOptions = syncOptionsBuilder.syncFilter(ofBlackList(CATEGORIES)) - .build(); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(updateActionsFromSync.stream() - .noneMatch(updateAction -> updateAction instanceof RemoveFromCategory)).isTrue(); - assertThat(updateActionsFromSync.stream() - .noneMatch(updateAction -> updateAction instanceof AddToCategory)).isTrue(); - assertThat(updateActionsFromSync.stream() - .noneMatch(updateAction -> updateAction instanceof SetCategoryOrderHint)).isTrue(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withChangedProductWhiteListingName_shouldOnlyUpdateProductName() { - final ProductDraft productDraft = - createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, referenceOfId(productType.getKey()), null, - null, categoryResourceIdentifiersWithKeys, createRandomCategoryOrderHints( - categoryResourceIdentifiersWithKeys)); - - final ProductSyncOptions syncOptions = syncOptionsBuilder.syncFilter(ofWhiteList(NAME)).build(); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(updateActionsFromSync).hasSize(2); - final UpdateAction updateAction = updateActionsFromSync.get(0); - assertThat(updateAction.getAction()).isEqualTo("changeName"); - assertThat(updateAction).isExactlyInstanceOf(ChangeName.class); - assertThat(((ChangeName) updateAction).getName()).isEqualTo(LocalizedString.of(Locale.ENGLISH, "new name")); - final UpdateAction updateAction2 = updateActionsFromSync.get(1); - assertThat(updateAction2).isEqualTo(Publish.of()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback(errorCallBack) + .warningCallback(warningCallBack) + .beforeUpdateCallback(actionsCallBack); + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withChangedProductBlackListingCategories_shouldUpdateProductWithoutCategories() { + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + referenceOfId(productType.getKey()), + null, + null, + categoryResourceIdentifiersWithKeys, + createRandomCategoryOrderHints(categoryResourceIdentifiersWithKeys)); + + final ProductSyncOptions syncOptions = + syncOptionsBuilder.syncFilter(ofBlackList(CATEGORIES)).build(); + + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat( + updateActionsFromSync.stream() + .noneMatch(updateAction -> updateAction instanceof RemoveFromCategory)) + .isTrue(); + assertThat( + updateActionsFromSync.stream() + .noneMatch(updateAction -> updateAction instanceof AddToCategory)) + .isTrue(); + assertThat( + updateActionsFromSync.stream() + .noneMatch(updateAction -> updateAction instanceof SetCategoryOrderHint)) + .isTrue(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withChangedProductWhiteListingName_shouldOnlyUpdateProductName() { + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + referenceOfId(productType.getKey()), + null, + null, + categoryResourceIdentifiersWithKeys, + createRandomCategoryOrderHints(categoryResourceIdentifiersWithKeys)); + + final ProductSyncOptions syncOptions = syncOptionsBuilder.syncFilter(ofWhiteList(NAME)).build(); + + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(updateActionsFromSync).hasSize(2); + final UpdateAction updateAction = updateActionsFromSync.get(0); + assertThat(updateAction.getAction()).isEqualTo("changeName"); + assertThat(updateAction).isExactlyInstanceOf(ChangeName.class); + assertThat(((ChangeName) updateAction).getName()) + .isEqualTo(LocalizedString.of(Locale.ENGLISH, "new name")); + final UpdateAction updateAction2 = updateActionsFromSync.get(1); + assertThat(updateAction2).isEqualTo(Publish.of()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncIT.java index b96d74d45c..28f894c819 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncIT.java @@ -1,5 +1,40 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategories; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.geResourceIdentifiersWithKeys; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCategoryDrafts; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getReferencesWithIds; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.replaceCategoryOrderHintCategoryIdsWithKeys; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.createState; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.createTaxCategory; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_CHANGED_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_2_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraft; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; +import static com.commercetools.sync.products.ProductSyncMockUtils.createRandomCategoryOrderHints; +import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.TriConsumer; import com.commercetools.sync.products.ProductSync; @@ -49,12 +84,6 @@ import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.utils.CompletableFutureUtils; import io.sphere.sdk.utils.MoneyImpl; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -64,474 +93,523 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategories; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.geResourceIdentifiersWithKeys; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCategoryDrafts; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getReferencesWithIds; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.replaceCategoryOrderHintCategoryIdsWithKeys; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.createState; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.createTaxCategory; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_CHANGED_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_2_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraft; -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; -import static com.commercetools.sync.products.ProductSyncMockUtils.createRandomCategoryOrderHints; -import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncIT { - private static ProductType productType; - private static TaxCategory targetTaxCategory; - private static State targetProductState; - private static List> categoryReferencesWithIds; - private static Set> categoryResourceIdentifiersWithKeys; - private static CategoryOrderHints categoryOrderHintsWithIds; - private static CategoryOrderHints categoryOrderHintsWithKeys; - private ProductSyncOptions syncOptions; - private Product product; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - /** - * Delete all product related test data from the target project. Then creates for the target CTP project price - * a product type, a tax category, 2 categories, custom types for the categories and a product state. - */ - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_TARGET_CLIENT); - - - final List categoryDrafts = getCategoryDrafts(null, 2); - final List categories = createCategories(CTP_TARGET_CLIENT, categoryDrafts); - categoryReferencesWithIds = getReferencesWithIds(categories); - categoryResourceIdentifiersWithKeys = geResourceIdentifiersWithKeys(categories); - categoryOrderHintsWithIds = createRandomCategoryOrderHints(categoryReferencesWithIds); - categoryOrderHintsWithKeys = replaceCategoryOrderHintCategoryIdsWithKeys(categoryOrderHintsWithIds, - categories); - - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - targetTaxCategory = createTaxCategory(CTP_TARGET_CLIENT); - targetProductState = createState(CTP_TARGET_CLIENT, StateType.PRODUCT_STATE); - } - - /** - * Deletes Products and Types from the target CTP project, then it populates target CTP project with product test - * data. - */ - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, productType.toReference(), - targetTaxCategory.toReference(), targetProductState.toReference(), categoryReferencesWithIds, + private static ProductType productType; + private static TaxCategory targetTaxCategory; + private static State targetProductState; + private static List> categoryReferencesWithIds; + private static Set> categoryResourceIdentifiersWithKeys; + private static CategoryOrderHints categoryOrderHintsWithIds; + private static CategoryOrderHints categoryOrderHintsWithKeys; + private ProductSyncOptions syncOptions; + private Product product; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + /** + * Delete all product related test data from the target project. Then creates for the target CTP + * project price a product type, a tax category, 2 categories, custom types for the categories and + * a product state. + */ + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, + Locale.ENGLISH, + OLD_CATEGORY_CUSTOM_TYPE_NAME, + CTP_TARGET_CLIENT); + + final List categoryDrafts = getCategoryDrafts(null, 2); + final List categories = createCategories(CTP_TARGET_CLIENT, categoryDrafts); + categoryReferencesWithIds = getReferencesWithIds(categories); + categoryResourceIdentifiersWithKeys = geResourceIdentifiersWithKeys(categories); + categoryOrderHintsWithIds = createRandomCategoryOrderHints(categoryReferencesWithIds); + categoryOrderHintsWithKeys = + replaceCategoryOrderHintCategoryIdsWithKeys(categoryOrderHintsWithIds, categories); + + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + targetTaxCategory = createTaxCategory(CTP_TARGET_CLIENT); + targetProductState = createState(CTP_TARGET_CLIENT, StateType.PRODUCT_STATE); + } + + /** + * Deletes Products and Types from the target CTP project, then it populates target CTP project + * with product test data. + */ + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); + + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + productType.toReference(), + targetTaxCategory.toReference(), + targetProductState.toReference(), + categoryReferencesWithIds, categoryOrderHintsWithIds); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final TriConsumer, Optional> warningCallBack = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) - -> collectErrors(exception.getMessage(), exception.getCause())) - .warningCallback(warningCallBack) - .build(); - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withNewProduct_shouldCreateProduct() { - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final TriConsumer, Optional> warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + collectErrors(exception.getMessage(), exception.getCause())) + .warningCallback(warningCallBack) + .build(); + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withNewProduct_shouldCreateProduct() { + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(null) .state(null) .build(); - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withMissingPriceChannel_shouldCreateProductDistributionPriceChannel() { - PriceDraftDsl priceDraftWithMissingChannelRef = PriceDraftBuilder.of(MoneyImpl.of("20", "EUR")) - .channel(ResourceIdentifier.ofKey("missingKey")).build(); + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withMissingPriceChannel_shouldCreateProductDistributionPriceChannel() { + PriceDraftDsl priceDraftWithMissingChannelRef = + PriceDraftBuilder.of(MoneyImpl.of("20", "EUR")) + .channel(ResourceIdentifier.ofKey("missingKey")) + .build(); - ProductVariantDraftDsl masterVariantDraft = ProductVariantDraftBuilder.of( + ProductVariantDraftDsl masterVariantDraft = + ProductVariantDraftBuilder.of( ProductVariantDraftDsl.of() - .withKey("v2") - .withSku("1065833") - .withPrices(Collections.singletonList(priceDraftWithMissingChannelRef))) - .build(); - - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) - .masterVariant(masterVariantDraft) - .taxCategory(null) - .state(null) - .build(); - - - final TriConsumer, Optional> warningCallBack - = (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - - - ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) - -> collectErrors(exception.getMessage(), exception.getCause())) - .warningCallback(warningCallBack) - .ensurePriceChannels(true) - .build(); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - Product productFromTargetProject = executeBlocking( - CTP_TARGET_CLIENT.execute(ProductByKeyGet.of(productDraft.getKey()))); - List prices = productFromTargetProject.getMasterData().getStaged().getMasterVariant().getPrices(); - assertThat(prices.size()).isEqualTo(1); - - Channel channel = executeBlocking(CTP_TARGET_CLIENT.execute(ChannelByIdGet.of( - Objects.requireNonNull(Objects.requireNonNull(prices.get(0).getChannel()).getId())))); - assertThat(channel.getRoles()).containsOnly(ChannelRole.PRODUCT_DISTRIBUTION); - } - - @Test - void sync_withNewProductAndBeforeCreateCallback_shouldCreateProduct() { - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) - .taxCategory(null) - .state(null) - .build(); - - final String keyPrefix = "callback_"; - final ProductSyncOptions options = ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) - -> collectErrors(exception.getMessage(), exception.getCause())) - .warningCallback((exception, oldResource, newResource) - -> warningCallBackMessages.add(exception.getMessage())) - .beforeCreateCallback(draft -> prefixDraftKey(draft, keyPrefix)) - .build(); - - final ProductSync productSync = new ProductSync(options); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - //Query for a product with key prefixed with "callback_" added by the callback - - final String keyWithCallbackPrefix = format("%s%s", keyPrefix, productDraft.getKey()); - final Optional productOptional = CTP_TARGET_CLIENT - .execute(ProductQuery.of() - .withPredicates(QueryPredicate.of(format("key = \"%s\"", keyWithCallbackPrefix)))) - .toCompletableFuture().join().head(); - - assertThat(productOptional).isNotEmpty(); - final Product fetchedProduct = productOptional.get(); - assertThat(fetchedProduct.getKey()).isEqualTo(keyWithCallbackPrefix); - assertThat(fetchedProduct.getMasterData().getCurrent().getName()).isEqualTo(productDraft.getName()); - } - - @Nonnull - private static ProductDraft prefixDraftKey(@Nonnull final ProductDraft productDraft, @Nonnull final String prefix) { - final String newKey = format("%s%s", prefix, productDraft.getKey()); - return ProductDraftBuilder.of(productDraft) - .key(newKey) - .build(); - } - - @Test - void sync_withNewProductWithExistingSlug_shouldNotCreateProduct() { - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + .withKey("v2") + .withSku("1065833") + .withPrices(Collections.singletonList(priceDraftWithMissingChannelRef))) + .build(); + + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) + .masterVariant(masterVariantDraft) .taxCategory(null) .state(null) - .slug(product.getMasterData().getStaged().getSlug()) .build(); - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - - final String duplicatedSlug = product.getMasterData().getStaged().getSlug().get(Locale.ENGLISH); - assertThat(errorCallBackExceptions) - .hasSize(1) - .allSatisfy(exception -> { - assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponse = ((ErrorResponseException) exception); - - final List fieldErrors = errorResponse - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - assertThat(fieldErrors).allSatisfy(error -> { - assertThat(error.getField()).isEqualTo("slug.en"); - assertThat(error.getDuplicateValue()).isEqualTo(duplicatedSlug); - }); - }); + final TriConsumer, Optional> warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); - assertThat(errorCallBackMessages) - .hasSize(1) - .allSatisfy(errorMessage -> { - assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); - assertThat(errorMessage).contains("\"field\" : \"slug.en\""); - assertThat(errorMessage).contains(format("\"duplicateValue\" : \"%s\"", duplicatedSlug)); - }); + ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> + collectErrors(exception.getMessage(), exception.getCause())) + .warningCallback(warningCallBack) + .ensurePriceChannels(true) + .build(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withEqualProduct_shouldNotUpdateProduct() { - final ProductDraft productDraft = - createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, ProductType.referenceOfId(productType.getKey()), - ResourceIdentifier.ofKey(targetTaxCategory.getKey()), - ResourceIdentifier.ofKey(targetProductState.getKey()), - categoryResourceIdentifiersWithKeys, categoryOrderHintsWithKeys); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withChangedProduct_shouldUpdateProduct() { - final ProductDraft productDraft = - createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, ProductType.referenceOfId(productType.getKey()), - ResourceIdentifier.ofKey(targetTaxCategory.getKey()), - ResourceIdentifier.ofKey(targetProductState.getKey()), - categoryResourceIdentifiersWithKeys, categoryOrderHintsWithKeys); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withChangedProductButConcurrentModificationException_shouldRetryAndUpdateProduct() { - // preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); - - final ProductSyncOptions spyOptions = ProductSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) - -> collectErrors(exception.getMessage(), exception.getCause())) - .warningCallback((exception, oldResource, newResource) - -> warningCallBackMessages.add(exception.getMessage())) + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + Product productFromTargetProject = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductByKeyGet.of(productDraft.getKey()))); + List prices = + productFromTargetProject.getMasterData().getStaged().getMasterVariant().getPrices(); + assertThat(prices.size()).isEqualTo(1); + + Channel channel = + executeBlocking( + CTP_TARGET_CLIENT.execute( + ChannelByIdGet.of( + Objects.requireNonNull( + Objects.requireNonNull(prices.get(0).getChannel()).getId())))); + assertThat(channel.getRoles()).containsOnly(ChannelRole.PRODUCT_DISTRIBUTION); + } + + @Test + void sync_withNewProductAndBeforeCreateCallback_shouldCreateProduct() { + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) + .taxCategory(null) + .state(null) .build(); - final ProductSync spyProductSync = new ProductSync(spyOptions); - - final ProductDraft productDraft = - createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey()), - ResourceIdentifier.ofKey(targetTaxCategory.getKey()), - ResourceIdentifier.ofKey(targetProductState.getKey()), - categoryResourceIdentifiersWithKeys, categoryOrderHintsWithKeys); - - final ProductSyncStatistics syncStatistics = - executeBlocking(spyProductSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdate() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final ProductUpdateCommand anyProductUpdateCommand = any(ProductUpdateCommand.class); - when(spyClient.execute(anyProductUpdateCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - return spyClient; - } - - @Test - void syncDrafts_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { - // preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); - - final ProductSyncOptions spyOptions = ProductSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) - -> collectErrors(exception.getMessage(), exception.getCause())) - .warningCallback((exception, oldResource, newResource) - -> warningCallBackMessages.add(exception.getMessage())) + final String keyPrefix = "callback_"; + final ProductSyncOptions options = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + collectErrors(exception.getMessage(), exception.getCause())) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .beforeCreateCallback(draft -> prefixDraftKey(draft, keyPrefix)) .build(); - final ProductSync spyProductSync = new ProductSync(spyOptions); - - final ProductDraft productDraft = - createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - ProductType.referenceOfId(productType.getKey()), - ResourceIdentifier.ofKey(targetTaxCategory.getKey()), - ResourceIdentifier.ofKey(targetProductState.getKey()), - categoryResourceIdentifiersWithKeys, - categoryOrderHintsWithKeys); - - final ProductSyncStatistics syncStatistics = - executeBlocking(spyProductSync.sync(singletonList(productDraft))); - - // Test and assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackExceptions).hasSize(1); - - assertThat(errorCallBackExceptions.get(0).getCause()).isExactlyInstanceOf(BadGatewayException.class); - assertThat(errorCallBackMessages.get(0)).contains( - format("Failed to update Product with key: '%s'. Reason: Failed to fetch from CTP while retrying " - + "after concurrency modification.", productDraft.getKey())); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final ProductUpdateCommand anyProductUpdateCommand = any(ProductUpdateCommand.class); - when(spyClient.execute(anyProductUpdateCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - final ProductQuery anyProductQuery = any(ProductQuery.class); - when(spyClient.execute(anyProductQuery)) - .thenCallRealMethod() // Call real fetch on fetching matching products - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); - - return spyClient; - } - - @Test - void syncDrafts_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { - // preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); - - final ProductSyncOptions spyOptions = ProductSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) - -> collectErrors(exception.getMessage(), exception.getCause())) - .warningCallback((exception, oldResource, newResource) - -> warningCallBackMessages.add(exception.getMessage())) + final ProductSync productSync = new ProductSync(options); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + // Query for a product with key prefixed with "callback_" added by the callback + + final String keyWithCallbackPrefix = format("%s%s", keyPrefix, productDraft.getKey()); + final Optional productOptional = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of() + .withPredicates( + QueryPredicate.of(format("key = \"%s\"", keyWithCallbackPrefix)))) + .toCompletableFuture() + .join() + .head(); + + assertThat(productOptional).isNotEmpty(); + final Product fetchedProduct = productOptional.get(); + assertThat(fetchedProduct.getKey()).isEqualTo(keyWithCallbackPrefix); + assertThat(fetchedProduct.getMasterData().getCurrent().getName()) + .isEqualTo(productDraft.getName()); + } + + @Nonnull + private static ProductDraft prefixDraftKey( + @Nonnull final ProductDraft productDraft, @Nonnull final String prefix) { + final String newKey = format("%s%s", prefix, productDraft.getKey()); + return ProductDraftBuilder.of(productDraft).key(newKey).build(); + } + + @Test + void sync_withNewProductWithExistingSlug_shouldNotCreateProduct() { + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) + .taxCategory(null) + .state(null) + .slug(product.getMasterData().getStaged().getSlug()) .build(); - final ProductSync spyProductSync = new ProductSync(spyOptions); + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + + final String duplicatedSlug = product.getMasterData().getStaged().getSlug().get(Locale.ENGLISH); + assertThat(errorCallBackExceptions) + .hasSize(1) + .allSatisfy( + exception -> { + assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponse = ((ErrorResponseException) exception); + + final List fieldErrors = + errorResponse.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + assertThat(fieldErrors) + .allSatisfy( + error -> { + assertThat(error.getField()).isEqualTo("slug.en"); + assertThat(error.getDuplicateValue()).isEqualTo(duplicatedSlug); + }); + }); - final ProductDraft productDraft = - createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - ProductType.referenceOfId(productType.getKey()), - ResourceIdentifier.ofKey(targetTaxCategory.getKey()), - ResourceIdentifier.ofKey(targetProductState.getKey()), - categoryResourceIdentifiersWithKeys, - categoryOrderHintsWithKeys); + assertThat(errorCallBackMessages) + .hasSize(1) + .allSatisfy( + errorMessage -> { + assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); + assertThat(errorMessage).contains("\"field\" : \"slug.en\""); + assertThat(errorMessage) + .contains(format("\"duplicateValue\" : \"%s\"", duplicatedSlug)); + }); - final ProductSyncStatistics syncStatistics = - executeBlocking(spyProductSync.sync(singletonList(productDraft))); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withEqualProduct_shouldNotUpdateProduct() { + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + ProductType.referenceOfId(productType.getKey()), + ResourceIdentifier.ofKey(targetTaxCategory.getKey()), + ResourceIdentifier.ofKey(targetProductState.getKey()), + categoryResourceIdentifiersWithKeys, + categoryOrderHintsWithKeys); - // Test and assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackExceptions).hasSize(1); + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withChangedProduct_shouldUpdateProduct() { + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ProductType.referenceOfId(productType.getKey()), + ResourceIdentifier.ofKey(targetTaxCategory.getKey()), + ResourceIdentifier.ofKey(targetProductState.getKey()), + categoryResourceIdentifiersWithKeys, + categoryOrderHintsWithKeys); - assertThat(errorCallBackMessages.get(0)).contains( - format("Failed to update Product with key: '%s'. Reason: Not found when attempting to fetch while" - + " retrying after concurrency modification.", productDraft.getKey())); - } + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withChangedProductButConcurrentModificationException_shouldRetryAndUpdateProduct() { + // preparation + final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); + + final ProductSyncOptions spyOptions = + ProductSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + collectErrors(exception.getMessage(), exception.getCause())) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .build(); - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final ProductSync spyProductSync = new ProductSync(spyOptions); - final ProductUpdateCommand anyProductUpdateCommand = any(ProductUpdateCommand.class); - when(spyClient.execute(anyProductUpdateCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ResourceIdentifier.ofKey(productType.getKey()), + ResourceIdentifier.ofKey(targetTaxCategory.getKey()), + ResourceIdentifier.ofKey(targetProductState.getKey()), + categoryResourceIdentifiersWithKeys, + categoryOrderHintsWithKeys); - final ProductQuery anyProductQuery = any(ProductQuery.class); + final ProductSyncStatistics syncStatistics = + executeBlocking(spyProductSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdate() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final ProductUpdateCommand anyProductUpdateCommand = any(ProductUpdateCommand.class); + when(spyClient.execute(anyProductUpdateCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + return spyClient; + } + + @Test + void syncDrafts_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); + + final ProductSyncOptions spyOptions = + ProductSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + collectErrors(exception.getMessage(), exception.getCause())) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .build(); - when(spyClient.execute(anyProductQuery)) - .thenCallRealMethod() // Call real fetch on fetching matching products - .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); + final ProductSync spyProductSync = new ProductSync(spyOptions); - return spyClient; - } + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ProductType.referenceOfId(productType.getKey()), + ResourceIdentifier.ofKey(targetTaxCategory.getKey()), + ResourceIdentifier.ofKey(targetProductState.getKey()), + categoryResourceIdentifiersWithKeys, + categoryOrderHintsWithKeys); - @Test - void sync_withMultipleBatchSyncing_ShouldSync() { - // Prepare existing products with keys: productKey1, productKey2, productKey3. - final ProductDraft key2Draft = createProductDraft(PRODUCT_KEY_2_RESOURCE_PATH, - productType.toReference(), targetTaxCategory.toReference(), targetProductState.toReference(), - categoryReferencesWithIds, product.getMasterData().getStaged().getCategoryOrderHints()); - executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(key2Draft))); + final ProductSyncStatistics syncStatistics = + executeBlocking(spyProductSync.sync(singletonList(productDraft))); + + // Test and assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackExceptions).hasSize(1); + + assertThat(errorCallBackExceptions.get(0).getCause()) + .isExactlyInstanceOf(BadGatewayException.class); + assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to update Product with key: '%s'. Reason: Failed to fetch from CTP while retrying " + + "after concurrency modification.", + productDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final ProductUpdateCommand anyProductUpdateCommand = any(ProductUpdateCommand.class); + when(spyClient.execute(anyProductUpdateCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + final ProductQuery anyProductQuery = any(ProductQuery.class); + when(spyClient.execute(anyProductQuery)) + .thenCallRealMethod() // Call real fetch on fetching matching products + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); + + return spyClient; + } + + @Test + void + syncDrafts_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); + + final ProductSyncOptions spyOptions = + ProductSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + collectErrors(exception.getMessage(), exception.getCause())) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .build(); + + final ProductSync spyProductSync = new ProductSync(spyOptions); + + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ProductType.referenceOfId(productType.getKey()), + ResourceIdentifier.ofKey(targetTaxCategory.getKey()), + ResourceIdentifier.ofKey(targetProductState.getKey()), + categoryResourceIdentifiersWithKeys, + categoryOrderHintsWithKeys); - final ProductDraft key3Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, productType.toReference()) + final ProductSyncStatistics syncStatistics = + executeBlocking(spyProductSync.sync(singletonList(productDraft))); + + // Test and assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackExceptions).hasSize(1); + + assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to update Product with key: '%s'. Reason: Not found when attempting to fetch while" + + " retrying after concurrency modification.", + productDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final ProductUpdateCommand anyProductUpdateCommand = any(ProductUpdateCommand.class); + when(spyClient.execute(anyProductUpdateCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + final ProductQuery anyProductQuery = any(ProductQuery.class); + + when(spyClient.execute(anyProductQuery)) + .thenCallRealMethod() // Call real fetch on fetching matching products + .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); + + return spyClient; + } + + @Test + void sync_withMultipleBatchSyncing_ShouldSync() { + // Prepare existing products with keys: productKey1, productKey2, productKey3. + final ProductDraft key2Draft = + createProductDraft( + PRODUCT_KEY_2_RESOURCE_PATH, + productType.toReference(), + targetTaxCategory.toReference(), + targetProductState.toReference(), + categoryReferencesWithIds, + product.getMasterData().getStaged().getCategoryOrderHints()); + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(key2Draft))); + + final ProductDraft key3Draft = + createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, productType.toReference()) .categories(new ArrayList<>()) .categoryOrderHints(CategoryOrderHints.of(new HashMap<>())) .key("productKey3") @@ -539,20 +617,24 @@ void sync_withMultipleBatchSyncing_ShouldSync() { .masterVariant(ProductVariantDraftBuilder.of().key("v3").build()) .taxCategory(TaxCategory.referenceOfId(targetTaxCategory.getId())) .build(); - executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(key3Draft))); - - - // Prepare batches from external source - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey()), ResourceIdentifier.ofKey(targetTaxCategory.getKey()), - ResourceIdentifier.ofKey(targetProductState.getKey()), categoryResourceIdentifiersWithKeys, + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(key3Draft))); + + // Prepare batches from external source + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ResourceIdentifier.ofKey(productType.getKey()), + ResourceIdentifier.ofKey(targetTaxCategory.getKey()), + ResourceIdentifier.ofKey(targetProductState.getKey()), + categoryResourceIdentifiersWithKeys, categoryOrderHintsWithKeys); - final List batch1 = new ArrayList<>(); - batch1.add(productDraft); + final List batch1 = new ArrayList<>(); + batch1.add(productDraft); - final ProductDraft key4Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key4Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(null) .state(null) .categories(new ArrayList<>()) @@ -562,11 +644,12 @@ void sync_withMultipleBatchSyncing_ShouldSync() { .masterVariant(ProductVariantDraftBuilder.of().key("v4").sku("sku4").build()) .build(); - final List batch2 = new ArrayList<>(); - batch2.add(key4Draft); + final List batch2 = new ArrayList<>(); + batch2.add(key4Draft); - final ProductDraft key3DraftNewSlug = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key3DraftNewSlug = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(null) .state(null) .categories(new ArrayList<>()) @@ -576,31 +659,38 @@ void sync_withMultipleBatchSyncing_ShouldSync() { .masterVariant(ProductVariantDraftBuilder.of().key("v3").sku("sku3").build()) .build(); - final List batch3 = new ArrayList<>(); - batch3.add(key3DraftNewSlug); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(batch1) - .thenCompose(result -> productSync.sync(batch2)) - .thenCompose(result -> productSync.sync(batch3))); - - assertThat(syncStatistics).hasValues(3, 1, 2, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withSingleBatchSyncing_ShouldSync() { - // Prepare batches from external source - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey()), ResourceIdentifier.ofKey(targetTaxCategory.getKey()), - ResourceIdentifier.ofKey(targetProductState.getKey()), categoryResourceIdentifiersWithKeys, + final List batch3 = new ArrayList<>(); + batch3.add(key3DraftNewSlug); + + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + executeBlocking( + productSync + .sync(batch1) + .thenCompose(result -> productSync.sync(batch2)) + .thenCompose(result -> productSync.sync(batch3))); + + assertThat(syncStatistics).hasValues(3, 1, 2, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withSingleBatchSyncing_ShouldSync() { + // Prepare batches from external source + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ResourceIdentifier.ofKey(productType.getKey()), + ResourceIdentifier.ofKey(targetTaxCategory.getKey()), + ResourceIdentifier.ofKey(targetProductState.getKey()), + categoryResourceIdentifiersWithKeys, categoryOrderHintsWithKeys); - final ProductDraft key3Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key3Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(ResourceIdentifier.ofKey(targetTaxCategory.getKey())) .state(ResourceIdentifier.ofKey(targetProductState.getKey())) .categories(new ArrayList<>()) @@ -610,8 +700,9 @@ void sync_withSingleBatchSyncing_ShouldSync() { .masterVariant(ProductVariantDraftBuilder.of().key("mv3").sku("sku3").build()) .build(); - final ProductDraft key4Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key4Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(ResourceIdentifier.ofKey(targetTaxCategory.getKey())) .state(ResourceIdentifier.ofKey(targetProductState.getKey())) .categories(new ArrayList<>()) @@ -621,8 +712,9 @@ void sync_withSingleBatchSyncing_ShouldSync() { .masterVariant(ProductVariantDraftBuilder.of().key("mv4").sku("sku4").build()) .build(); - final ProductDraft key5Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key5Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(ResourceIdentifier.ofKey(targetTaxCategory.getKey())) .state(ResourceIdentifier.ofKey(targetProductState.getKey())) .categories(new ArrayList<>()) @@ -632,8 +724,9 @@ void sync_withSingleBatchSyncing_ShouldSync() { .masterVariant(ProductVariantDraftBuilder.of().key("mv5").sku("sku5").build()) .build(); - final ProductDraft key6Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key6Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(ResourceIdentifier.ofKey(targetTaxCategory.getKey())) .state(ResourceIdentifier.ofKey(targetProductState.getKey())) .categories(new ArrayList<>()) @@ -643,32 +736,37 @@ void sync_withSingleBatchSyncing_ShouldSync() { .masterVariant(ProductVariantDraftBuilder.of().key("mv6").sku("sku6").build()) .build(); - final List batch = new ArrayList<>(); - batch.add(productDraft); - batch.add(key3Draft); - batch.add(key4Draft); - batch.add(key5Draft); - batch.add(key6Draft); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); - - assertThat(syncStatistics).hasValues(5, 4, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withSameSlugInSingleBatch_ShouldNotSyncIt() { - // Prepare batches from external source - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey()), ResourceIdentifier.ofKey(targetTaxCategory.getKey()), - ResourceIdentifier.ofKey(targetProductState.getKey()), categoryResourceIdentifiersWithKeys, + final List batch = new ArrayList<>(); + batch.add(productDraft); + batch.add(key3Draft); + batch.add(key4Draft); + batch.add(key5Draft); + batch.add(key6Draft); + + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); + + assertThat(syncStatistics).hasValues(5, 4, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withSameSlugInSingleBatch_ShouldNotSyncIt() { + // Prepare batches from external source + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ResourceIdentifier.ofKey(productType.getKey()), + ResourceIdentifier.ofKey(targetTaxCategory.getKey()), + ResourceIdentifier.ofKey(targetProductState.getKey()), + categoryResourceIdentifiersWithKeys, categoryOrderHintsWithKeys); - final ProductDraft key3Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key3Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(null) .state(null) .categories(new ArrayList<>()) @@ -677,8 +775,9 @@ void sync_withSameSlugInSingleBatch_ShouldNotSyncIt() { .masterVariant(ProductVariantDraftBuilder.of().key("k3").sku("s3").build()) .build(); - final ProductDraft key4Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key4Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(null) .state(null) .categories(new ArrayList<>()) @@ -687,8 +786,9 @@ void sync_withSameSlugInSingleBatch_ShouldNotSyncIt() { .masterVariant(ProductVariantDraftBuilder.of().key("k4").sku("s4").build()) .build(); - final ProductDraft key5Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key5Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(null) .state(null) .categories(new ArrayList<>()) @@ -697,8 +797,9 @@ void sync_withSameSlugInSingleBatch_ShouldNotSyncIt() { .masterVariant(ProductVariantDraftBuilder.of().key("k5").sku("s5").build()) .build(); - final ProductDraft key6Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey())) + final ProductDraft key6Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey(productType.getKey())) .taxCategory(null) .state(null) .categories(new ArrayList<>()) @@ -707,60 +808,71 @@ void sync_withSameSlugInSingleBatch_ShouldNotSyncIt() { .masterVariant(ProductVariantDraftBuilder.of().key("k6").sku("s6").build()) .build(); - final List batch = new ArrayList<>(); - batch.add(productDraft); - batch.add(key3Draft); - batch.add(key4Draft); - batch.add(key5Draft); - batch.add(key6Draft); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); - - assertThat(syncStatistics).hasValues(5, 1, 1, 3, 0); - - final String duplicatedSlug = key3Draft.getSlug().get(Locale.ENGLISH); - assertThat(errorCallBackExceptions).hasSize(3); - assertThat(errorCallBackExceptions).allSatisfy(exception -> { - assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponse = ((ErrorResponseException)exception); - - final List fieldErrors = errorResponse - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - assertThat(fieldErrors).allSatisfy(error -> { - assertThat(error.getField()).isEqualTo("slug.en"); - assertThat(error.getDuplicateValue()).isEqualTo(duplicatedSlug); + final List batch = new ArrayList<>(); + batch.add(productDraft); + batch.add(key3Draft); + batch.add(key4Draft); + batch.add(key5Draft); + batch.add(key6Draft); + + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); + + assertThat(syncStatistics).hasValues(5, 1, 1, 3, 0); + + final String duplicatedSlug = key3Draft.getSlug().get(Locale.ENGLISH); + assertThat(errorCallBackExceptions).hasSize(3); + assertThat(errorCallBackExceptions) + .allSatisfy( + exception -> { + assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponse = ((ErrorResponseException) exception); + + final List fieldErrors = + errorResponse.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + assertThat(fieldErrors) + .allSatisfy( + error -> { + assertThat(error.getField()).isEqualTo("slug.en"); + assertThat(error.getDuplicateValue()).isEqualTo(duplicatedSlug); + }); }); - }); - - assertThat(errorCallBackMessages) - .hasSize(3) - .allSatisfy(errorMessage -> { - assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); - assertThat(errorMessage).contains("\"field\" : \"slug.en\""); - assertThat(errorMessage).contains(format("\"duplicateValue\" : \"%s\"", duplicatedSlug)); + + assertThat(errorCallBackMessages) + .hasSize(3) + .allSatisfy( + errorMessage -> { + assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); + assertThat(errorMessage).contains("\"field\" : \"slug.en\""); + assertThat(errorMessage) + .contains(format("\"duplicateValue\" : \"%s\"", duplicatedSlug)); }); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withADraftsWithBlankKeysInBatch_ShouldNotSyncItAndTriggerErrorCallBack() { - // Prepare batches from external source - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - ProductType.reference(productType.getKey()), ResourceIdentifier.ofKey(targetTaxCategory.getKey()), - ResourceIdentifier.ofKey(targetProductState.getKey()), categoryResourceIdentifiersWithKeys, + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withADraftsWithBlankKeysInBatch_ShouldNotSyncItAndTriggerErrorCallBack() { + // Prepare batches from external source + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ProductType.reference(productType.getKey()), + ResourceIdentifier.ofKey(targetTaxCategory.getKey()), + ResourceIdentifier.ofKey(targetProductState.getKey()), + categoryResourceIdentifiersWithKeys, categoryOrderHintsWithKeys); - // Draft with null key - final ProductDraft key3Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ProductType.reference(productType.getKey())) + // Draft with null key + final ProductDraft key3Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ProductType.reference(productType.getKey())) .taxCategory(null) .state(null) .categories(new ArrayList<>()) @@ -770,9 +882,10 @@ void sync_withADraftsWithBlankKeysInBatch_ShouldNotSyncItAndTriggerErrorCallBack .productType(ResourceIdentifier.ofKey(productType.getKey())) .build(); - // Draft with empty key - final ProductDraft key4Draft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ProductType.reference(productType.getKey())) + // Draft with empty key + final ProductDraft key4Draft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ProductType.reference(productType.getKey())) .taxCategory(null) .state(null) .categories(new ArrayList<>()) @@ -782,164 +895,188 @@ void sync_withADraftsWithBlankKeysInBatch_ShouldNotSyncItAndTriggerErrorCallBack .productType(ResourceIdentifier.ofKey(productType.getKey())) .build(); - final List batch = new ArrayList<>(); - batch.add(productDraft); - batch.add(key3Draft); - batch.add(key4Draft); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); - - assertThat(syncStatistics).hasValues(3, 0, 1, 2, 0); - assertThat(errorCallBackExceptions).hasSize(2); - assertThat(errorCallBackMessages).hasSize(2); - assertThat(errorCallBackMessages.get(0)) - .containsIgnoringCase(format("ProductDraft with name: %s doesn't have a key.", key3Draft.getName())); - assertThat(errorCallBackMessages.get(1)) - .containsIgnoringCase(format("ProductDraft with name: %s doesn't have a key.", key4Draft.getName())); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withANullDraftInBatch_ShouldNotSyncItAndTriggerErrorCallBack() { - // Prepare batches from external source - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - ResourceIdentifier.ofKey(productType.getKey()), null, null, - categoryResourceIdentifiersWithKeys, categoryOrderHintsWithKeys); - - final List batch = new ArrayList<>(); - batch.add(productDraft); - batch.add(null); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); - - assertThat(syncStatistics).hasValues(2, 0, 1, 1, 0); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualToIgnoringCase("ProductDraft is null."); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withSameDraftsWithChangesInBatch_ShouldRetryUpdateBecauseOfConcurrentModificationExceptions() { - // Prepare batches from external source - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, - ProductType.reference(productType.getKey()), null, null, - categoryResourceIdentifiersWithKeys, categoryOrderHintsWithKeys); - - // Draft with same key - final ProductDraft draftWithSameKey = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ProductType.reference(productType.getKey())) + final List batch = new ArrayList<>(); + batch.add(productDraft); + batch.add(key3Draft); + batch.add(key4Draft); + + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); + + assertThat(syncStatistics).hasValues(3, 0, 1, 2, 0); + assertThat(errorCallBackExceptions).hasSize(2); + assertThat(errorCallBackMessages).hasSize(2); + assertThat(errorCallBackMessages.get(0)) + .containsIgnoringCase( + format("ProductDraft with name: %s doesn't have a key.", key3Draft.getName())); + assertThat(errorCallBackMessages.get(1)) + .containsIgnoringCase( + format("ProductDraft with name: %s doesn't have a key.", key4Draft.getName())); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withANullDraftInBatch_ShouldNotSyncItAndTriggerErrorCallBack() { + // Prepare batches from external source + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ResourceIdentifier.ofKey(productType.getKey()), + null, + null, + categoryResourceIdentifiersWithKeys, + categoryOrderHintsWithKeys); + + final List batch = new ArrayList<>(); + batch.add(productDraft); + batch.add(null); + + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); + + assertThat(syncStatistics).hasValues(2, 0, 1, 1, 0); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualToIgnoringCase("ProductDraft is null."); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void + sync_withSameDraftsWithChangesInBatch_ShouldRetryUpdateBecauseOfConcurrentModificationExceptions() { + // Prepare batches from external source + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_CHANGED_RESOURCE_PATH, + ProductType.reference(productType.getKey()), + null, + null, + categoryResourceIdentifiersWithKeys, + categoryOrderHintsWithKeys); + + // Draft with same key + final ProductDraft draftWithSameKey = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ProductType.reference(productType.getKey())) .taxCategory(null) .state(null) .categories(new ArrayList<>()) .categoryOrderHints(CategoryOrderHints.of(new HashMap<>())) .key(productDraft.getKey()) - .masterVariant(ProductVariantDraftBuilder.of(product.getMasterData().getStaged().getMasterVariant()) - .build()) + .masterVariant( + ProductVariantDraftBuilder.of( + product.getMasterData().getStaged().getMasterVariant()) + .build()) .build(); - final List batch = new ArrayList<>(); - batch.add(productDraft); - batch.add(draftWithSameKey); - - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); - - assertThat(syncStatistics).hasValues(2, 0, 2, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_withProductContainingAttributeChanges_shouldSyncProductCorrectly() { - // preparation - final List> updateActions = new ArrayList<>(); - final TriConsumer, Optional> warningCallBack = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - - final ProductSyncOptions customOptions = ProductSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) - -> collectErrors(exception.getMessage(), exception.getCause())) + final List batch = new ArrayList<>(); + batch.add(productDraft); + batch.add(draftWithSameKey); + + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = executeBlocking(productSync.sync(batch)); + + assertThat(syncStatistics).hasValues(2, 0, 2, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_withProductContainingAttributeChanges_shouldSyncProductCorrectly() { + // preparation + final List> updateActions = new ArrayList<>(); + final TriConsumer, Optional> warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + + final ProductSyncOptions customOptions = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> + collectErrors(exception.getMessage(), exception.getCause())) .warningCallback(warningCallBack) .beforeUpdateCallback( (actions, draft, old) -> { - updateActions.addAll(actions); - return actions; + updateActions.addAll(actions); + return actions; }) .build(); - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_1_RESOURCE_PATH, - ProductType.referenceOfId(productType.getKey())) + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_RESOURCE_PATH, ProductType.referenceOfId(productType.getKey())) .categories(emptyList()) .taxCategory(null) .state(null) .build(); - // Creating the attribute draft with the changes - final AttributeDraft priceInfoAttrDraft = - AttributeDraft.of("priceInfo", JsonNodeFactory.instance.textNode("100/kg")); - final AttributeDraft angebotAttrDraft = - AttributeDraft.of("angebot", JsonNodeFactory.instance.textNode("big discount")); - final AttributeDraft unknownAttrDraft = - AttributeDraft.of("unknown", JsonNodeFactory.instance.textNode("unknown")); - - // Creating the product variant draft with the product reference attribute - final List attributes = asList(priceInfoAttrDraft, angebotAttrDraft, unknownAttrDraft); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder.of(productDraft.getMasterVariant()) - .attributes(attributes) - .build(); - - final ProductDraft productDraftWithChangedAttributes = ProductDraftBuilder.of(productDraft) - .masterVariant(masterVariant) - .build(); - - - // test - final ProductSync productSync = new ProductSync(customOptions); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraftWithChangedAttributes))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - - final String causeErrorMessage = format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, unknownAttrDraft.getName()); - final String expectedErrorMessage = format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, unknownAttrDraft.getName(), - productDraft.getMasterVariant().getKey(), productDraft.getKey(), causeErrorMessage); - - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0).getMessage()).isEqualTo(expectedErrorMessage); - assertThat(errorCallBackExceptions.get(0).getCause().getMessage()).isEqualTo(causeErrorMessage); - assertThat(errorCallBackMessages).containsExactly(expectedErrorMessage); - assertThat(warningCallBackMessages).isEmpty(); - - assertThat(updateActions) - .filteredOn(updateAction -> ! (updateAction instanceof SetTaxCategory)) - .filteredOn(updateAction -> ! (updateAction instanceof RemoveFromCategory)) - .containsExactlyInAnyOrder( - SetAttributeInAllVariants.of(priceInfoAttrDraft, true), - SetAttribute.of(1, angebotAttrDraft, true), - SetAttributeInAllVariants.ofUnsetAttribute("size", true), - SetAttributeInAllVariants.ofUnsetAttribute("rinderrasse", true), - SetAttributeInAllVariants.ofUnsetAttribute("herkunft", true), - SetAttributeInAllVariants.ofUnsetAttribute("teilstueck", true), - SetAttributeInAllVariants.ofUnsetAttribute("fuetterung", true), - SetAttributeInAllVariants.ofUnsetAttribute("reifung", true), - SetAttributeInAllVariants.ofUnsetAttribute("haltbarkeit", true), - SetAttributeInAllVariants.ofUnsetAttribute("verpackung", true), - SetAttributeInAllVariants.ofUnsetAttribute("anlieferung", true), - SetAttributeInAllVariants.ofUnsetAttribute("zubereitung", true), - SetAttribute.ofUnsetAttribute(1, "localisedText", true), - Publish.of() - ); - } - - private void collectErrors(final String errorMessage, final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } + // Creating the attribute draft with the changes + final AttributeDraft priceInfoAttrDraft = + AttributeDraft.of("priceInfo", JsonNodeFactory.instance.textNode("100/kg")); + final AttributeDraft angebotAttrDraft = + AttributeDraft.of("angebot", JsonNodeFactory.instance.textNode("big discount")); + final AttributeDraft unknownAttrDraft = + AttributeDraft.of("unknown", JsonNodeFactory.instance.textNode("unknown")); + + // Creating the product variant draft with the product reference attribute + final List attributes = + asList(priceInfoAttrDraft, angebotAttrDraft, unknownAttrDraft); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of(productDraft.getMasterVariant()) + .attributes(attributes) + .build(); + + final ProductDraft productDraftWithChangedAttributes = + ProductDraftBuilder.of(productDraft).masterVariant(masterVariant).build(); + + // test + final ProductSync productSync = new ProductSync(customOptions); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraftWithChangedAttributes))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + + final String causeErrorMessage = + format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, unknownAttrDraft.getName()); + final String expectedErrorMessage = + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + unknownAttrDraft.getName(), + productDraft.getMasterVariant().getKey(), + productDraft.getKey(), + causeErrorMessage); + + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0).getMessage()).isEqualTo(expectedErrorMessage); + assertThat(errorCallBackExceptions.get(0).getCause().getMessage()).isEqualTo(causeErrorMessage); + assertThat(errorCallBackMessages).containsExactly(expectedErrorMessage); + assertThat(warningCallBackMessages).isEmpty(); + + assertThat(updateActions) + .filteredOn(updateAction -> !(updateAction instanceof SetTaxCategory)) + .filteredOn(updateAction -> !(updateAction instanceof RemoveFromCategory)) + .containsExactlyInAnyOrder( + SetAttributeInAllVariants.of(priceInfoAttrDraft, true), + SetAttribute.of(1, angebotAttrDraft, true), + SetAttributeInAllVariants.ofUnsetAttribute("size", true), + SetAttributeInAllVariants.ofUnsetAttribute("rinderrasse", true), + SetAttributeInAllVariants.ofUnsetAttribute("herkunft", true), + SetAttributeInAllVariants.ofUnsetAttribute("teilstueck", true), + SetAttributeInAllVariants.ofUnsetAttribute("fuetterung", true), + SetAttributeInAllVariants.ofUnsetAttribute("reifung", true), + SetAttributeInAllVariants.ofUnsetAttribute("haltbarkeit", true), + SetAttributeInAllVariants.ofUnsetAttribute("verpackung", true), + SetAttributeInAllVariants.ofUnsetAttribute("anlieferung", true), + SetAttributeInAllVariants.ofUnsetAttribute("zubereitung", true), + SetAttribute.ofUnsetAttribute(1, "localisedText", true), + Publish.of()); + } + + private void collectErrors(final String errorMessage, final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithAssetsIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithAssetsIT.java index d3b9998b68..f18bdccfdd 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithAssetsIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithAssetsIT.java @@ -1,5 +1,26 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ITUtils.BOOLEAN_CUSTOM_FIELD_NAME; +import static com.commercetools.sync.integration.commons.utils.ITUtils.LOCALISED_STRING_CUSTOM_FIELD_NAME; +import static com.commercetools.sync.integration.commons.utils.ITUtils.assertAssetsAreEqual; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetDraft; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetDraftWithKey; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetsCustomType; +import static com.commercetools.sync.integration.commons.utils.ITUtils.createVariantDraft; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.producttypes.ProductType.referenceOfId; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toMap; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import com.commercetools.sync.commons.exceptions.SyncException; @@ -33,352 +54,393 @@ import io.sphere.sdk.products.queries.ProductProjectionByKeyGet; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ITUtils.BOOLEAN_CUSTOM_FIELD_NAME; -import static com.commercetools.sync.integration.commons.utils.ITUtils.LOCALISED_STRING_CUSTOM_FIELD_NAME; -import static com.commercetools.sync.integration.commons.utils.ITUtils.assertAssetsAreEqual; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetDraft; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetDraftWithKey; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createAssetsCustomType; -import static com.commercetools.sync.integration.commons.utils.ITUtils.createVariantDraft; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.producttypes.ProductType.referenceOfId; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toMap; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncWithAssetsIT { - private static final String ASSETS_CUSTOM_TYPE_KEY = "assetsCustomTypeKey"; - - private static ProductType productType; - private static Type assetsCustomType; - private Product product; - private ProductSync productSync; - private List assetDraftsToCreateOnExistingProduct; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> updateActionsFromSync; - - /** - * Delete all product related test data from the target project. Then creates for the target CTP project a product - * type and an asset custom type. - */ - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - assetsCustomType = createAssetsCustomType(ASSETS_CUSTOM_TYPE_KEY, Locale.ENGLISH, - "assetsCustomTypeName", CTP_TARGET_CLIENT); - - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - /** - * Deletes Products and Types from the target CTP project, then it populates target CTP project with product test - * data. - */ - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - productSync = new ProductSync(buildSyncOptions()); - - - assetDraftsToCreateOnExistingProduct = asList( + private static final String ASSETS_CUSTOM_TYPE_KEY = "assetsCustomTypeKey"; + + private static ProductType productType; + private static Type assetsCustomType; + private Product product; + private ProductSync productSync; + private List assetDraftsToCreateOnExistingProduct; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> updateActionsFromSync; + + /** + * Delete all product related test data from the target project. Then creates for the target CTP + * project a product type and an asset custom type. + */ + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + assetsCustomType = + createAssetsCustomType( + ASSETS_CUSTOM_TYPE_KEY, Locale.ENGLISH, "assetsCustomTypeName", CTP_TARGET_CLIENT); + + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + /** + * Deletes Products and Types from the target CTP project, then it populates target CTP project + * with product test data. + */ + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + productSync = new ProductSync(buildSyncOptions()); + + assetDraftsToCreateOnExistingProduct = + asList( createAssetDraft("1", ofEnglish("1"), assetsCustomType.getId()), createAssetDraft("2", ofEnglish("2"), assetsCustomType.getId()), createAssetDraft("3", ofEnglish("3"), assetsCustomType.getId())); - final ProductDraft productDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("draftName"), ofEnglish("existingSlug"), + final ProductDraft productDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("draftName"), + ofEnglish("existingSlug"), createVariantDraft("v1", assetDraftsToCreateOnExistingProduct, null)) .key("existingProduct") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - updateActionsFromSync = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final QuadConsumer, Optional, List>> - errorCallBack = (exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }; - final TriConsumer, Optional> warningCallBack = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - final TriFunction>, ProductDraft, Product, List>> - actionsCallBack = (updateActions, newDraft, oldProduct) -> { - updateActionsFromSync.addAll(updateActions); - return updateActions; + product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + updateActionsFromSync = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallBack = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }; + final TriConsumer, Optional> warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + final TriFunction< + List>, ProductDraft, Product, List>> + actionsCallBack = + (updateActions, newDraft, oldProduct) -> { + updateActionsFromSync.addAll(updateActions); + return updateActions; }; - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallBack) - .warningCallback(warningCallBack) - .beforeUpdateCallback(actionsCallBack) - .build(); - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withNewProductWithAssets_shouldCreateProduct() { - final List assetDrafts = - singletonList(createAssetDraftWithKey("4", ofEnglish("4"), ASSETS_CUSTOM_TYPE_KEY)); - - final ProductDraft productDraft = ProductDraftBuilder - .of(ResourceIdentifier.ofKey(productType.getKey()), ofEnglish("draftName"), ofEnglish("slug"), + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback(errorCallBack) + .warningCallback(warningCallBack) + .beforeUpdateCallback(actionsCallBack) + .build(); + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withNewProductWithAssets_shouldCreateProduct() { + final List assetDrafts = + singletonList(createAssetDraftWithKey("4", ofEnglish("4"), ASSETS_CUSTOM_TYPE_KEY)); + + final ProductDraft productDraft = + ProductDraftBuilder.of( + ResourceIdentifier.ofKey(productType.getKey()), + ofEnglish("draftName"), + ofEnglish("slug"), createVariantDraft("masterVariant", assetDrafts, null)) .key("draftKey") .build(); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); - - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(updateActionsFromSync).isEmpty(); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - final List createdAssets = productProjection.getMasterVariant().getAssets(); - assertAssetsAreEqual(createdAssets, assetDrafts); - } - - @Test - void sync_withNewProductWithAssetsWithDuplicateKeys_shouldNotCreateProductAndTriggerErrorCallback() { - final List assetDrafts = asList( + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(updateActionsFromSync).isEmpty(); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + final List createdAssets = productProjection.getMasterVariant().getAssets(); + assertAssetsAreEqual(createdAssets, assetDrafts); + } + + @Test + void + sync_withNewProductWithAssetsWithDuplicateKeys_shouldNotCreateProductAndTriggerErrorCallback() { + final List assetDrafts = + asList( createAssetDraftWithKey("4", ofEnglish("4"), ASSETS_CUSTOM_TYPE_KEY), createAssetDraftWithKey("4", ofEnglish("duplicate asset"), ASSETS_CUSTOM_TYPE_KEY)); - final ProductDraft productDraft = ProductDraftBuilder - .of(ResourceIdentifier.ofKey(productType.getKey()), ofEnglish("draftName"), ofEnglish("slug"), + final ProductDraft productDraft = + ProductDraftBuilder.of( + ResourceIdentifier.ofKey(productType.getKey()), + ofEnglish("draftName"), + ofEnglish("slug"), createVariantDraft("masterVariant", assetDrafts, null)) .key("draftKey") .build(); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); - - // Assert results of sync - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(updateActionsFromSync).isEmpty(); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).contains("The key '4' was used by multiple assets in product variant" - + " 'masterVariant'."); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(ErrorResponseException.class); - assertThat(errorCallBackExceptions.get(0).getMessage()).contains("The key '4' was used by multiple assets in" - + " product variant 'masterVariant'."); - - - // Assert that the product wasn't created. - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNull(); - } - - @Test - void sync_withMatchingProductWithAssetChanges_shouldUpdateProduct() { - - final Map customFieldsJsonMap = new HashMap<>(); - customFieldsJsonMap.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true)); - - // new asset drafts with different kind of asset actions (change order, add asset, remove asset, change asset - // name, set asset custom fields, change - final List assetDrafts = asList( + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + // Assert results of sync + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(updateActionsFromSync).isEmpty(); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .contains( + "The key '4' was used by multiple assets in product variant" + " 'masterVariant'."); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(ErrorResponseException.class); + assertThat(errorCallBackExceptions.get(0).getMessage()) + .contains( + "The key '4' was used by multiple assets in" + " product variant 'masterVariant'."); + + // Assert that the product wasn't created. + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNull(); + } + + @Test + void sync_withMatchingProductWithAssetChanges_shouldUpdateProduct() { + + final Map customFieldsJsonMap = new HashMap<>(); + customFieldsJsonMap.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true)); + + // new asset drafts with different kind of asset actions (change order, add asset, remove asset, + // change asset + // name, set asset custom fields, change + final List assetDrafts = + asList( createAssetDraftWithKey("4", ofEnglish("4"), ASSETS_CUSTOM_TYPE_KEY), - createAssetDraftWithKey("3", ofEnglish("3"), ASSETS_CUSTOM_TYPE_KEY, customFieldsJsonMap), + createAssetDraftWithKey( + "3", ofEnglish("3"), ASSETS_CUSTOM_TYPE_KEY, customFieldsJsonMap), createAssetDraft("2", ofEnglish("new name"))); - - final ProductDraft productDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("draftName"), ofEnglish("existingSlug"), + final ProductDraft productDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("draftName"), + ofEnglish("existingSlug"), createVariantDraft("v1", assetDrafts, null)) .key(product.getKey()) .build(); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); - // Assert results of sync - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(updateActionsFromSync).isNotEmpty(); + // Assert results of sync + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(updateActionsFromSync).isNotEmpty(); - final Map assetsKeyToIdMap = product.getMasterData() - .getStaged() - .getMasterVariant() - .getAssets() - .stream().collect(toMap(Asset::getKey, Asset::getId)); + final Map assetsKeyToIdMap = + product.getMasterData().getStaged().getMasterVariant().getAssets().stream() + .collect(toMap(Asset::getKey, Asset::getId)); - assertThat(updateActionsFromSync).containsExactly( + assertThat(updateActionsFromSync) + .containsExactly( RemoveAsset.ofVariantIdWithKey(1, "1", true), ChangeAssetName.ofAssetKeyAndVariantId(1, "2", ofEnglish("new name"), true), SetAssetCustomType.ofVariantIdAndAssetKey(1, "2", null, true), - SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey(1, "3", BOOLEAN_CUSTOM_FIELD_NAME, - customFieldsJsonMap.get(BOOLEAN_CUSTOM_FIELD_NAME), true), - SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey(1, "3", LOCALISED_STRING_CUSTOM_FIELD_NAME, - null, true), - ChangeAssetOrder.ofVariantId(1, asList(assetsKeyToIdMap.get("3"), assetsKeyToIdMap.get("2")), true), + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, + "3", + BOOLEAN_CUSTOM_FIELD_NAME, + customFieldsJsonMap.get(BOOLEAN_CUSTOM_FIELD_NAME), + true), + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, "3", LOCALISED_STRING_CUSTOM_FIELD_NAME, null, true), + ChangeAssetOrder.ofVariantId( + 1, asList(assetsKeyToIdMap.get("3"), assetsKeyToIdMap.get("2")), true), AddAsset.ofVariantId(1, createAssetDraft("4", ofEnglish("4"), assetsCustomType.getId())) - .withStaged(true).withPosition(0) - ); - - // Assert that assets got updated correctly - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - assertAssetsAreEqual(productProjection.getMasterVariant().getAssets(), assetDrafts); - } - - @Test - void sync_withMatchingProductWithNewVariantWithAssets_shouldUpdateAddAssetsToNewVariant() { - - final Map customFieldsJsonMap = new HashMap<>(); - customFieldsJsonMap.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true)); - - // new asset drafts with different kind of asset actions (change order, add asset, remove asset, change asset - // name, set asset custom fields, change - final List assetDrafts = asList( + .withStaged(true) + .withPosition(0)); + + // Assert that assets got updated correctly + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + assertAssetsAreEqual(productProjection.getMasterVariant().getAssets(), assetDrafts); + } + + @Test + void sync_withMatchingProductWithNewVariantWithAssets_shouldUpdateAddAssetsToNewVariant() { + + final Map customFieldsJsonMap = new HashMap<>(); + customFieldsJsonMap.put(BOOLEAN_CUSTOM_FIELD_NAME, JsonNodeFactory.instance.booleanNode(true)); + + // new asset drafts with different kind of asset actions (change order, add asset, remove asset, + // change asset + // name, set asset custom fields, change + final List assetDrafts = + asList( createAssetDraftWithKey("4", ofEnglish("4"), ASSETS_CUSTOM_TYPE_KEY), - createAssetDraftWithKey("3", ofEnglish("3"), ASSETS_CUSTOM_TYPE_KEY, customFieldsJsonMap), + createAssetDraftWithKey( + "3", ofEnglish("3"), ASSETS_CUSTOM_TYPE_KEY, customFieldsJsonMap), createAssetDraft("2", ofEnglish("new name"))); - - final ProductDraft productDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("draftName"), ofEnglish("existingSlug"), + final ProductDraft productDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("draftName"), + ofEnglish("existingSlug"), createVariantDraft("v1", assetDrafts, null)) .plusVariants(createVariantDraft("v2", assetDrafts, null)) .key(product.getKey()) .build(); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); - // Assert results of sync - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(updateActionsFromSync).isNotEmpty(); + // Assert results of sync + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(updateActionsFromSync).isNotEmpty(); - final Map assetsKeyToIdMap = product.getMasterData() - .getStaged() - .getMasterVariant() - .getAssets() - .stream().collect(toMap(Asset::getKey, Asset::getId)); + final Map assetsKeyToIdMap = + product.getMasterData().getStaged().getMasterVariant().getAssets().stream() + .collect(toMap(Asset::getKey, Asset::getId)); - assertThat(updateActionsFromSync).containsExactly( + assertThat(updateActionsFromSync) + .containsExactly( AddVariant.of(null, null, "v2", true).withKey("v2"), AddAsset.ofSku("v2", createAssetDraft("4", ofEnglish("4"), assetsCustomType.getId())) - .withStaged(true), - AddAsset.ofSku("v2", createAssetDraft("3", ofEnglish("3"), assetsCustomType.getId(), customFieldsJsonMap)) - .withStaged(true), - AddAsset.ofSku("v2", createAssetDraft("2", ofEnglish("new name"))) - .withStaged(true), + .withStaged(true), + AddAsset.ofSku( + "v2", + createAssetDraft( + "3", ofEnglish("3"), assetsCustomType.getId(), customFieldsJsonMap)) + .withStaged(true), + AddAsset.ofSku("v2", createAssetDraft("2", ofEnglish("new name"))).withStaged(true), RemoveAsset.ofVariantIdWithKey(1, "1", true), ChangeAssetName.ofAssetKeyAndVariantId(1, "2", ofEnglish("new name"), true), SetAssetCustomType.ofVariantIdAndAssetKey(1, "2", null, true), - SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey(1, "3", BOOLEAN_CUSTOM_FIELD_NAME, - customFieldsJsonMap.get(BOOLEAN_CUSTOM_FIELD_NAME), true), - SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey(1, "3", LOCALISED_STRING_CUSTOM_FIELD_NAME, - null, true), - ChangeAssetOrder.ofVariantId(1, asList(assetsKeyToIdMap.get("3"), assetsKeyToIdMap.get("2")), true), + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, + "3", + BOOLEAN_CUSTOM_FIELD_NAME, + customFieldsJsonMap.get(BOOLEAN_CUSTOM_FIELD_NAME), + true), + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, "3", LOCALISED_STRING_CUSTOM_FIELD_NAME, null, true), + ChangeAssetOrder.ofVariantId( + 1, asList(assetsKeyToIdMap.get("3"), assetsKeyToIdMap.get("2")), true), AddAsset.ofVariantId(1, createAssetDraft("4", ofEnglish("4"), assetsCustomType.getId())) - .withStaged(true).withPosition(0) - ); - - // Assert that assets got updated correctly - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - assertAssetsAreEqual(productProjection.getMasterVariant().getAssets(), assetDrafts); - } - - @Test - void sync_withMatchingProductWithDuplicateAssets_shouldFailToUpdateProductAndTriggerErrorCallback() { - final List assetDrafts = asList( + .withStaged(true) + .withPosition(0)); + + // Assert that assets got updated correctly + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + assertAssetsAreEqual(productProjection.getMasterVariant().getAssets(), assetDrafts); + } + + @Test + void + sync_withMatchingProductWithDuplicateAssets_shouldFailToUpdateProductAndTriggerErrorCallback() { + final List assetDrafts = + asList( createAssetDraftWithKey("4", ofEnglish("4"), ASSETS_CUSTOM_TYPE_KEY), createAssetDraftWithKey("4", ofEnglish("4"), ASSETS_CUSTOM_TYPE_KEY), createAssetDraftWithKey("2", ofEnglish("2"), ASSETS_CUSTOM_TYPE_KEY)); - - final ProductDraft productDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("draftName"), ofEnglish("existingSlug"), + final ProductDraft productDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("draftName"), + ofEnglish("existingSlug"), createVariantDraft("v1", assetDrafts, null)) .key(product.getKey()) .build(); - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(productDraft))); - - // Assert results of sync - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).matches("Failed to build update actions for the assets of the product " - + "variant with the sku 'v1'. Reason: .*DuplicateKeyException: Supplied asset drafts " - + "have duplicate keys. Asset keys are expected to be unique inside their container \\(a product variant " - + "or a category\\)."); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(BuildUpdateActionException.class); - assertThat(errorCallBackExceptions.get(0).getMessage()).contains("Supplied asset drafts have duplicate " - + "keys. Asset keys are expected to be unique inside their container (a product variant or a category)."); - assertThat(errorCallBackExceptions.get(0).getCause()).isExactlyInstanceOf(DuplicateKeyException.class); - assertThat(updateActionsFromSync).isEmpty(); - - - // Assert that existing assets haven't changed. - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - - assertAssetsAreEqual(productProjection.getMasterVariant().getAssets(), assetDraftsToCreateOnExistingProduct); - } + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(productDraft))); + + // Assert results of sync + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .matches( + "Failed to build update actions for the assets of the product " + + "variant with the sku 'v1'. Reason: .*DuplicateKeyException: Supplied asset drafts " + + "have duplicate keys. Asset keys are expected to be unique inside their container \\(a product variant " + + "or a category\\)."); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)) + .isExactlyInstanceOf(BuildUpdateActionException.class); + assertThat(errorCallBackExceptions.get(0).getMessage()) + .contains( + "Supplied asset drafts have duplicate " + + "keys. Asset keys are expected to be unique inside their container (a product variant or a category)."); + assertThat(errorCallBackExceptions.get(0).getCause()) + .isExactlyInstanceOf(DuplicateKeyException.class); + assertThat(updateActionsFromSync).isEmpty(); + + // Assert that existing assets haven't changed. + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of(productDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + + assertAssetsAreEqual( + productProjection.getMasterVariant().getAssets(), assetDraftsToCreateOnExistingProduct); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedCategoriesIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedCategoriesIT.java index fcb8172601..1759343154 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedCategoriesIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedCategoriesIT.java @@ -1,5 +1,23 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createArrayNode; +import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueReferences; +import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueSetOfReferences; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.TriConsumer; import com.commercetools.sync.products.ProductSync; @@ -32,641 +50,685 @@ import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.commands.ProductTypeUpdateCommand; import io.sphere.sdk.producttypes.commands.updateactions.AddAttributeDefinition; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createArrayNode; -import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueReferences; -import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueSetOfReferences; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; - class ProductSyncWithNestedReferencedCategoriesIT { - private static ProductType productType; - private static Category testCategory1; - private static Category testCategory2; - - private ProductSyncOptions syncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> actions; - - private static final String ATTRIBUTE_NAME_FIELD = "name"; - private static final String ATTRIBUTE_VALUE_FIELD = "value"; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - final ProductType nestedProductType = createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, - CTP_TARGET_CLIENT); - - - final AttributeDefinitionDraft nestedAttributeDef = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(nestedProductType), "nestedAttribute", ofEnglish("nestedAttribute"), false) + private static ProductType productType; + private static Category testCategory1; + private static Category testCategory2; + + private ProductSyncOptions syncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> actions; + + private static final String ATTRIBUTE_NAME_FIELD = "name"; + private static final String ATTRIBUTE_VALUE_FIELD = "value"; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + final ProductType nestedProductType = + createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, CTP_TARGET_CLIENT); + + final AttributeDefinitionDraft nestedAttributeDef = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(nestedProductType), + "nestedAttribute", + ofEnglish("nestedAttribute"), + false) .searchable(false) .build(); - final AttributeDefinitionDraft setOfNestedAttributeDef = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(NestedAttributeType.of(nestedProductType)), "setOfNestedAttribute", - ofEnglish("setOfNestedAttribute"), false) + final AttributeDefinitionDraft setOfNestedAttributeDef = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(NestedAttributeType.of(nestedProductType)), + "setOfNestedAttribute", + ofEnglish("setOfNestedAttribute"), + false) .searchable(false) .build(); + final ProductTypeUpdateCommand productTypeUpdateCommand = + ProductTypeUpdateCommand.of( + productType, + asList( + AddAttributeDefinition.of(nestedAttributeDef), + AddAttributeDefinition.of(setOfNestedAttributeDef))); - final ProductTypeUpdateCommand productTypeUpdateCommand = ProductTypeUpdateCommand.of(productType, - asList(AddAttributeDefinition.of(nestedAttributeDef), AddAttributeDefinition.of(setOfNestedAttributeDef))); + CTP_TARGET_CLIENT.execute(productTypeUpdateCommand).toCompletableFuture().join(); - CTP_TARGET_CLIENT.execute(productTypeUpdateCommand).toCompletableFuture().join(); - - final CategoryDraft category1Draft = CategoryDraftBuilder - .of(ofEnglish("cat1-name"), ofEnglish("cat1-slug")) + final CategoryDraft category1Draft = + CategoryDraftBuilder.of(ofEnglish("cat1-name"), ofEnglish("cat1-slug")) .key("cat1-key") .build(); - testCategory1 = CTP_TARGET_CLIENT + testCategory1 = + CTP_TARGET_CLIENT .execute(CategoryCreateCommand.of(category1Draft)) .toCompletableFuture() .join(); - final CategoryDraft category2Draft = CategoryDraftBuilder - .of(ofEnglish("cat2-name"), ofEnglish("cat2-slug")) + final CategoryDraft category2Draft = + CategoryDraftBuilder.of(ofEnglish("cat2-name"), ofEnglish("cat2-slug")) .key("cat2-key") .build(); - testCategory2 = CTP_TARGET_CLIENT + testCategory2 = + CTP_TARGET_CLIENT .execute(CategoryCreateCommand.of(category2Draft)) .toCompletableFuture() .join(); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - actions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final TriConsumer, Optional> warningCallback - = (syncException, productDraft, product) -> warningCallBackMessages.add(syncException.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((syncException, draft, product, updateActions) - -> collectErrors(syncException.getMessage(), syncException)) - .beforeUpdateCallback(this::collectActions) - .warningCallback(warningCallback) - .build(); - } - - private void collectErrors(final String errorMessage, final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> collectActions(@Nonnull final List> actions, - @Nonnull final ProductDraft productDraft, - @Nonnull final Product product) { - this.actions.addAll(actions); - return actions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withNestedCategoryReferenceAsAttribute_shouldCreateProductReferencingExistingCategory() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("category-reference", - createReferenceObject(testCategory1.getKey(), Category.referenceTypeId())); - - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + actions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final TriConsumer, Optional> warningCallback = + (syncException, productDraft, product) -> + warningCallBackMessages.add(syncException.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (syncException, draft, product, updateActions) -> + collectErrors(syncException.getMessage(), syncException)) + .beforeUpdateCallback(this::collectActions) + .warningCallback(warningCallback) + .build(); + } + + private void collectErrors(final String errorMessage, final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> collectActions( + @Nonnull final List> actions, + @Nonnull final ProductDraft productDraft, + @Nonnull final Product product) { + this.actions.addAll(actions); + return actions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void + sync_withNestedCategoryReferenceAsAttribute_shouldCreateProductReferencingExistingCategory() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "category-reference", + createReferenceObject(testCategory1.getKey(), Category.referenceTypeId())); + + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) .toCompletableFuture() .join(); - final Optional createdCategoryReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceAttribute.getName()); - - assertThat(createdCategoryReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeReferenceValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(nestedAttributeNameField.asText()).isEqualTo("category-reference"); - assertThat(nestedAttributeReferenceValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Category.referenceTypeId()); - assertThat(nestedAttributeReferenceValueField.get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(testCategory1.getId()); - }); - } - - @Test - void sync_withSameNestedCategoryReferenceAsAttribute_shouldNotSyncAnythingNew() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("category-reference", - createReferenceObject(testCategory1.getId(), Category.referenceTypeId())); - - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdCategoryReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceAttribute.getName()); + + assertThat(createdCategoryReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeReferenceValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()).isEqualTo("category-reference"); + assertThat(nestedAttributeReferenceValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(nestedAttributeReferenceValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCategory1.getId()); + }); + } + + @Test + void sync_withSameNestedCategoryReferenceAsAttribute_shouldNotSyncAnythingNew() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "category-reference", + createReferenceObject(testCategory1.getId(), Category.referenceTypeId())); + + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); - final ObjectNode newNestedAttributeValue = - createNestedAttributeValueReferences("category-reference", - createReferenceObject(testCategory1.getKey(), Category.referenceTypeId())); + final ObjectNode newNestedAttributeValue = + createNestedAttributeValueReferences( + "category-reference", + createReferenceObject(testCategory1.getKey(), Category.referenceTypeId())); - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) .toCompletableFuture() .join(); - final Optional createdCategoryReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceAttribute.getName()); - - assertThat(createdCategoryReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - assertThat(nestedAttributeNameField.asText()).isEqualTo("category-reference"); - assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Category.referenceTypeId()); - assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(testCategory1.getId()); - }); - } - - @Test - void sync_withChangedNestedCategoryReferenceAsAttribute_shouldUpdateProductReferencingExistingCategory() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("category-reference", - createReferenceObject(testCategory1.getId(), Category.referenceTypeId())); - - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdCategoryReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceAttribute.getName()); + + assertThat(createdCategoryReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + assertThat(nestedAttributeNameField.asText()).isEqualTo("category-reference"); + assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCategory1.getId()); + }); + } + + @Test + void + sync_withChangedNestedCategoryReferenceAsAttribute_shouldUpdateProductReferencingExistingCategory() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "category-reference", + createReferenceObject(testCategory1.getId(), Category.referenceTypeId())); + + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); - final ObjectNode newNestedAttributeValue = - createNestedAttributeValueReferences("category-reference", - createReferenceObject(testCategory2.getKey(), Category.referenceTypeId())); + final ObjectNode newNestedAttributeValue = + createNestedAttributeValueReferences( + "category-reference", + createReferenceObject(testCategory2.getKey(), Category.referenceTypeId())); - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithCategoryReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - final ObjectNode expectedNestedAttributeValue = - createNestedAttributeValueReferences("category-reference", - createReferenceObject(testCategory2.getId(), Category.referenceTypeId())); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); - final AttributeDraft expectedCategoryReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(expectedNestedAttributeValue)); - assertThat(actions).containsExactly(SetAttribute.of(1, expectedCategoryReferenceAttribute, true)); + final ObjectNode expectedNestedAttributeValue = + createNestedAttributeValueReferences( + "category-reference", + createReferenceObject(testCategory2.getId(), Category.referenceTypeId())); + final AttributeDraft expectedCategoryReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(expectedNestedAttributeValue)); + assertThat(actions) + .containsExactly(SetAttribute.of(1, expectedCategoryReferenceAttribute, true)); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) .toCompletableFuture() .join(); - final Optional createdCategoryReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceAttribute.getName()); - - assertThat(createdCategoryReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - - assertThat(nestedAttributeNameField.asText()).isEqualTo("category-reference"); - assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Category.referenceTypeId()); - assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCategory2.getId()); - }); - } - - @Test - void sync_withNonExistingNestedCategoryReferenceAsAttribute_ShouldFailCreatingTheProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("category-reference", - createReferenceObject("nonExistingKey", Category.referenceTypeId())); - - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdCategoryReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceAttribute.getName()); + + assertThat(createdCategoryReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()).isEqualTo("category-reference"); + assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCategory2.getId()); + }); + } + + @Test + void sync_withNonExistingNestedCategoryReferenceAsAttribute_ShouldFailCreatingTheProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "category-reference", + createReferenceObject("nonExistingKey", Category.referenceTypeId())); + + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'nestedAttribute.category-reference'"); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'nestedAttribute.category-reference'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'nestedAttribute.category-reference'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } - - @Test - void sync_withNestedCategoryReferenceSetAsAttribute_shouldCreateProductReferencingExistingCategories() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueSetOfReferences("category-reference-set", - createReferenceObject(testCategory1.getKey(), Category.referenceTypeId()), - createReferenceObject(testCategory2.getKey(), Category.referenceTypeId())); - - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + .contains( + "The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'nestedAttribute.category-reference'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } + + @Test + void + sync_withNestedCategoryReferenceSetAsAttribute_shouldCreateProductReferencingExistingCategories() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueSetOfReferences( + "category-reference-set", + createReferenceObject(testCategory1.getKey(), Category.referenceTypeId()), + createReferenceObject(testCategory2.getKey(), Category.referenceTypeId())); + + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - - final Product createdProduct = CTP_TARGET_CLIENT - .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCategoryReference)) .toCompletableFuture() .join(); - final Optional createdCategoryReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceAttribute.getName()); - - assertThat(createdCategoryReferenceAttribute).hasValueSatisfying(attribute -> { - - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(nestedAttributeNameField.asText()).isEqualTo("category-reference-set"); - assertThat(nestedAttributeValueField).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) nestedAttributeValueField; - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Category.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCategory1.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Category.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCategory2.getId()); - }); - }); - } - - @Test - void sync_withNestedCategoryReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueSetOfReferences("category-reference-set", - createReferenceObject(testCategory1.getKey(), Category.referenceTypeId()), - createReferenceObject("nonExistingKey", Category.referenceTypeId())); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + final Product createdProduct = + CTP_TARGET_CLIENT + .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) + .toCompletableFuture() + .join(); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdCategoryReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceAttribute.getName()); + + assertThat(createdCategoryReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()).isEqualTo("category-reference-set"); + assertThat(nestedAttributeValueField).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) nestedAttributeValueField; + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCategory1.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCategory2.getId()); + }); + }); + } + + @Test + void + sync_withNestedCategoryReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueSetOfReferences( + "category-reference-set", + createReferenceObject(testCategory1.getKey(), Category.referenceTypeId()), + createReferenceObject("nonExistingKey", Category.referenceTypeId())); + + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'nestedAttribute.category-reference-set'"); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'nestedAttribute.category-reference-set'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'nestedAttribute.category-reference-set'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } - - @Test - void sync_withSetOfNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingCategories() { - // preparation - final ArrayNode nestedAttributeValue = - createArrayNode( - createNestedAttributeValueSetOfReferences("category-reference-set", - createReferenceObject(testCategory1.getKey(), Category.referenceTypeId()), - createReferenceObject(testCategory2.getKey(), Category.referenceTypeId()))); - - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("setOfNestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + .contains( + "The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'nestedAttribute.category-reference-set'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } + + @Test + void + sync_withSetOfNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingCategories() { + // preparation + final ArrayNode nestedAttributeValue = + createArrayNode( + createNestedAttributeValueSetOfReferences( + "category-reference-set", + createReferenceObject(testCategory1.getKey(), Category.referenceTypeId()), + createReferenceObject(testCategory2.getKey(), Category.referenceTypeId()))); + + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("setOfNestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) .toCompletableFuture() .join(); - final Optional createdCategoryReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceAttribute.getName()); - - assertThat(createdCategoryReferenceAttribute).hasValueSatisfying(attribute -> { - - final JsonNode setOfNestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode setOfNestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(setOfNestedAttributeNameField.asText()).isEqualTo("category-reference-set"); - assertThat(setOfNestedAttributeValueField).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) setOfNestedAttributeValueField; - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Category.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCategory1.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Category.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCategory2.getId()); - }); - }); - } + final Optional createdCategoryReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceAttribute.getName()); + + assertThat(createdCategoryReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode setOfNestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode setOfNestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(setOfNestedAttributeNameField.asText()) + .isEqualTo("category-reference-set"); + assertThat(setOfNestedAttributeValueField).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) setOfNestedAttributeValueField; + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCategory1.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCategory2.getId()); + }); + }); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedCustomObjectsIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedCustomObjectsIT.java index efcca40b43..6133e31746 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedCustomObjectsIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedCustomObjectsIT.java @@ -1,5 +1,26 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.createCustomObject; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteCustomObject; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createArrayNode; +import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueReferences; +import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueSetOfReferences; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.TriConsumer; import com.commercetools.sync.products.ProductSync; @@ -30,661 +51,711 @@ import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.commands.ProductTypeUpdateCommand; import io.sphere.sdk.producttypes.commands.updateactions.AddAttributeDefinition; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.createCustomObject; -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteCustomObject; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createArrayNode; -import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueReferences; -import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueSetOfReferences; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; - class ProductSyncWithNestedReferencedCustomObjectsIT { - private static ProductType productType; - private static CustomObject testCustomObject1; - private static CustomObject testCustomObject2; - - private ProductSyncOptions syncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> actions; - - private static final String ATTRIBUTE_NAME_FIELD = "name"; - private static final String ATTRIBUTE_VALUE_FIELD = "value"; - private static final String CUSTOM_OBJECT_REFERENCE_ATTR_NAME = "customObject-reference"; - private static final String CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME = "customObject-reference-set"; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - deleteCustomObjects(); - - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - final ProductType nestedProductType = createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, - CTP_TARGET_CLIENT); - - - final AttributeDefinitionDraft nestedAttributeDef = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(nestedProductType), "nestedAttribute", ofEnglish("nestedAttribute"), false) + private static ProductType productType; + private static CustomObject testCustomObject1; + private static CustomObject testCustomObject2; + + private ProductSyncOptions syncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> actions; + + private static final String ATTRIBUTE_NAME_FIELD = "name"; + private static final String ATTRIBUTE_VALUE_FIELD = "value"; + private static final String CUSTOM_OBJECT_REFERENCE_ATTR_NAME = "customObject-reference"; + private static final String CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME = "customObject-reference-set"; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + deleteCustomObjects(); + + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + final ProductType nestedProductType = + createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, CTP_TARGET_CLIENT); + + final AttributeDefinitionDraft nestedAttributeDef = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(nestedProductType), + "nestedAttribute", + ofEnglish("nestedAttribute"), + false) .searchable(false) .build(); - final AttributeDefinitionDraft setOfNestedAttributeDef = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(NestedAttributeType.of(nestedProductType)), "setOfNestedAttribute", - ofEnglish("setOfNestedAttribute"), false) + final AttributeDefinitionDraft setOfNestedAttributeDef = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(NestedAttributeType.of(nestedProductType)), + "setOfNestedAttribute", + ofEnglish("setOfNestedAttribute"), + false) .searchable(false) .build(); - - final ProductTypeUpdateCommand productTypeUpdateCommand = ProductTypeUpdateCommand.of(productType, - asList(AddAttributeDefinition.of(nestedAttributeDef), AddAttributeDefinition.of(setOfNestedAttributeDef))); - - CTP_TARGET_CLIENT.execute(productTypeUpdateCommand).toCompletableFuture().join(); - - final ObjectNode customObject1Value = - JsonNodeFactory.instance.objectNode().put("name", "value1"); - - testCustomObject1 = createCustomObject(CTP_TARGET_CLIENT, "key1", "container1", customObject1Value); - - final ObjectNode customObject2Value = - JsonNodeFactory.instance.objectNode().put("name", "value2"); - - testCustomObject2 = createCustomObject(CTP_TARGET_CLIENT, "key2", "container2", customObject2Value); - } - - private static void deleteCustomObjects() { - deleteCustomObject(CTP_TARGET_CLIENT, "key1", "container1"); - deleteCustomObject(CTP_TARGET_CLIENT, "key2", "container2"); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - actions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final TriConsumer, Optional> warningCallback - = (syncException, productDraft, product) -> warningCallBackMessages.add(syncException.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((syncException, draft, product, updateActions) - -> collectErrors(syncException.getMessage(), syncException)) - .beforeUpdateCallback(this::collectActions) - .warningCallback(warningCallback) - .build(); - } - - private void collectErrors(final String errorMessage, final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> collectActions(@Nonnull final List> actions, - @Nonnull final ProductDraft productDraft, - @Nonnull final Product product) { - this.actions.addAll(actions); - return actions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - deleteCustomObjects(); - } - - @Test - void sync_withNestedCustomObjectReferenceAsAttribute_shouldCreateProductReferencingExistingCustomObject() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences(CUSTOM_OBJECT_REFERENCE_ATTR_NAME, - createReferenceObject(format("%s|%s", testCustomObject1.getContainer() , testCustomObject1.getKey()), - CustomObject.referenceTypeId())); - - final AttributeDraft customObjectReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final ProductTypeUpdateCommand productTypeUpdateCommand = + ProductTypeUpdateCommand.of( + productType, + asList( + AddAttributeDefinition.of(nestedAttributeDef), + AddAttributeDefinition.of(setOfNestedAttributeDef))); + + CTP_TARGET_CLIENT.execute(productTypeUpdateCommand).toCompletableFuture().join(); + + final ObjectNode customObject1Value = + JsonNodeFactory.instance.objectNode().put("name", "value1"); + + testCustomObject1 = + createCustomObject(CTP_TARGET_CLIENT, "key1", "container1", customObject1Value); + + final ObjectNode customObject2Value = + JsonNodeFactory.instance.objectNode().put("name", "value2"); + + testCustomObject2 = + createCustomObject(CTP_TARGET_CLIENT, "key2", "container2", customObject2Value); + } + + private static void deleteCustomObjects() { + deleteCustomObject(CTP_TARGET_CLIENT, "key1", "container1"); + deleteCustomObject(CTP_TARGET_CLIENT, "key2", "container2"); + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + actions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final TriConsumer, Optional> warningCallback = + (syncException, productDraft, product) -> + warningCallBackMessages.add(syncException.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (syncException, draft, product, updateActions) -> + collectErrors(syncException.getMessage(), syncException)) + .beforeUpdateCallback(this::collectActions) + .warningCallback(warningCallback) + .build(); + } + + private void collectErrors(final String errorMessage, final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> collectActions( + @Nonnull final List> actions, + @Nonnull final ProductDraft productDraft, + @Nonnull final Product product) { + this.actions.addAll(actions); + return actions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + deleteCustomObjects(); + } + + @Test + void + sync_withNestedCustomObjectReferenceAsAttribute_shouldCreateProductReferencingExistingCustomObject() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + CUSTOM_OBJECT_REFERENCE_ATTR_NAME, + createReferenceObject( + format("%s|%s", testCustomObject1.getContainer(), testCustomObject1.getKey()), + CustomObject.referenceTypeId())); + + final AttributeDraft customObjectReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(customObjectReferenceAttribute) .build(); - final ProductDraft productDraftWithCustomObjectReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCustomObjectReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCustomObjectReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCustomObjectReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCustomObjectReference.getKey())) .toCompletableFuture() .join(); - final Optional createdCustomObjectReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(customObjectReferenceAttribute.getName()); - - assertThat(createdCustomObjectReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeReferenceValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(nestedAttributeNameField.asText()).isEqualTo(CUSTOM_OBJECT_REFERENCE_ATTR_NAME); - assertThat(nestedAttributeReferenceValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(CustomObject.referenceTypeId()); - assertThat(nestedAttributeReferenceValueField.get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(testCustomObject1.getId()); - }); - } - - @Test - void sync_withSameNestedCustomObjectReferenceAsAttribute_shouldNotSyncAnythingNew() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences(CUSTOM_OBJECT_REFERENCE_ATTR_NAME, - createReferenceObject(testCustomObject1.getId(), CustomObject.referenceTypeId())); - - final AttributeDraft customObjectReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdCustomObjectReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(customObjectReferenceAttribute.getName()); + + assertThat(createdCustomObjectReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeReferenceValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()) + .isEqualTo(CUSTOM_OBJECT_REFERENCE_ATTR_NAME); + assertThat(nestedAttributeReferenceValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(CustomObject.referenceTypeId()); + assertThat(nestedAttributeReferenceValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCustomObject1.getId()); + }); + } + + @Test + void sync_withSameNestedCustomObjectReferenceAsAttribute_shouldNotSyncAnythingNew() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + CUSTOM_OBJECT_REFERENCE_ATTR_NAME, + createReferenceObject(testCustomObject1.getId(), CustomObject.referenceTypeId())); + + final AttributeDraft customObjectReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(customObjectReferenceAttribute) .build(); - final ProductDraft productDraftWithCustomObjectReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCustomObjectReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithCustomObjectReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithCustomObjectReference)) + .toCompletableFuture() + .join(); - final ObjectNode newNestedAttributeValue = - createNestedAttributeValueReferences(CUSTOM_OBJECT_REFERENCE_ATTR_NAME, - createReferenceObject(format("%s|%s", testCustomObject1.getContainer() , testCustomObject1.getKey()), - CustomObject.referenceTypeId())); + final ObjectNode newNestedAttributeValue = + createNestedAttributeValueReferences( + CUSTOM_OBJECT_REFERENCE_ATTR_NAME, + createReferenceObject( + format("%s|%s", testCustomObject1.getContainer(), testCustomObject1.getKey()), + CustomObject.referenceTypeId())); - final AttributeDraft newCustomObjectReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); + final AttributeDraft newCustomObjectReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newCustomObjectReferenceAttribute) .build(); - final ProductDraft newProductDraftWithCustomObjectReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithCustomObjectReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithCustomObjectReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithCustomObjectReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCustomObjectReference.getKey())) .toCompletableFuture() .join(); - final Optional createdCustomObjectReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(customObjectReferenceAttribute.getName()); - - assertThat(createdCustomObjectReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - assertThat(nestedAttributeNameField.asText()).isEqualTo(CUSTOM_OBJECT_REFERENCE_ATTR_NAME); - assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(CustomObject.referenceTypeId()); - assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(testCustomObject1.getId()); - }); - } - - @Test - void sync_withChangedNestedCustomObjectReferenceAsAttribute_shouldUpdateProductReferencingExistingCustomObject() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences(CUSTOM_OBJECT_REFERENCE_ATTR_NAME, - createReferenceObject(testCustomObject1.getId(), CustomObject.referenceTypeId())); - - final AttributeDraft customObjectReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdCustomObjectReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(customObjectReferenceAttribute.getName()); + + assertThat(createdCustomObjectReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + assertThat(nestedAttributeNameField.asText()) + .isEqualTo(CUSTOM_OBJECT_REFERENCE_ATTR_NAME); + assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(CustomObject.referenceTypeId()); + assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCustomObject1.getId()); + }); + } + + @Test + void + sync_withChangedNestedCustomObjectReferenceAsAttribute_shouldUpdateProductReferencingExistingCustomObject() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + CUSTOM_OBJECT_REFERENCE_ATTR_NAME, + createReferenceObject(testCustomObject1.getId(), CustomObject.referenceTypeId())); + + final AttributeDraft customObjectReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(customObjectReferenceAttribute) .build(); - final ProductDraft productDraftWithCustomObjectReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCustomObjectReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithCustomObjectReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithCustomObjectReference)) + .toCompletableFuture() + .join(); - final ObjectNode newNestedAttributeValue = - createNestedAttributeValueReferences(CUSTOM_OBJECT_REFERENCE_ATTR_NAME, - createReferenceObject(format("%s|%s", testCustomObject2.getContainer() , testCustomObject2.getKey()), - CustomObject.referenceTypeId())); + final ObjectNode newNestedAttributeValue = + createNestedAttributeValueReferences( + CUSTOM_OBJECT_REFERENCE_ATTR_NAME, + createReferenceObject( + format("%s|%s", testCustomObject2.getContainer(), testCustomObject2.getKey()), + CustomObject.referenceTypeId())); - final AttributeDraft newCustomObjectReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); + final AttributeDraft newCustomObjectReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newCustomObjectReferenceAttribute) .build(); - final ProductDraft newProductDraftWithCustomObjectReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithCustomObjectReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithCustomObjectReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithCustomObjectReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - final ObjectNode expectedNestedAttributeValue = - createNestedAttributeValueReferences(CUSTOM_OBJECT_REFERENCE_ATTR_NAME, - createReferenceObject(testCustomObject2.getId(), CustomObject.referenceTypeId())); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); - final AttributeDraft expectedCustomObjectReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(expectedNestedAttributeValue)); - assertThat(actions).containsExactly(SetAttribute.of(1, expectedCustomObjectReferenceAttribute, true)); + final ObjectNode expectedNestedAttributeValue = + createNestedAttributeValueReferences( + CUSTOM_OBJECT_REFERENCE_ATTR_NAME, + createReferenceObject(testCustomObject2.getId(), CustomObject.referenceTypeId())); + final AttributeDraft expectedCustomObjectReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(expectedNestedAttributeValue)); + assertThat(actions) + .containsExactly(SetAttribute.of(1, expectedCustomObjectReferenceAttribute, true)); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCustomObjectReference.getKey())) .toCompletableFuture() .join(); - final Optional createdCustomObjectReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(customObjectReferenceAttribute.getName()); - - assertThat(createdCustomObjectReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - - assertThat(nestedAttributeNameField.asText()).isEqualTo(CUSTOM_OBJECT_REFERENCE_ATTR_NAME); - assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(CustomObject.referenceTypeId()); - assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCustomObject2.getId()); - }); - } - - @Test - void sync_withNonExistingNestedCustomObjectReferenceAsAttribute_ShouldFailCreatingTheProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences(CUSTOM_OBJECT_REFERENCE_ATTR_NAME, - createReferenceObject("non-existing-container|non-existing-key", CustomObject.referenceTypeId())); - - final AttributeDraft customObjectReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdCustomObjectReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(customObjectReferenceAttribute.getName()); + + assertThat(createdCustomObjectReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()) + .isEqualTo(CUSTOM_OBJECT_REFERENCE_ATTR_NAME); + assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(CustomObject.referenceTypeId()); + assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCustomObject2.getId()); + }); + } + + @Test + void sync_withNonExistingNestedCustomObjectReferenceAsAttribute_ShouldFailCreatingTheProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + CUSTOM_OBJECT_REFERENCE_ATTR_NAME, + createReferenceObject( + "non-existing-container|non-existing-key", CustomObject.referenceTypeId())); + + final AttributeDraft customObjectReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(customObjectReferenceAttribute) .build(); - final ProductDraft productDraftWithCustomObjectReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCustomObjectReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCustomObjectReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"key-value-document\"," - + "\"id\":\"non-existing-container|non-existing-key\"}' " - + "is not valid for field 'nestedAttribute.customObject-reference'"); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCustomObjectReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"key-value-document\"," + + "\"id\":\"non-existing-container|non-existing-key\"}' " + + "is not valid for field 'nestedAttribute.customObject-reference'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"key-value-document\"," - + "\"id\":\"non-existing-container|non-existing-key\"}' " - + "is not valid for field 'nestedAttribute.customObject-reference'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } - - @Test - void sync_withNestedCustomObjectReferenceSetAsAttribute_shouldCreateProductReferencingExistingCustomObjects() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueSetOfReferences(CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME, - createReferenceObject(format("%s|%s", testCustomObject1.getContainer() , testCustomObject1.getKey()), - CustomObject.referenceTypeId()), - createReferenceObject(format("%s|%s", testCustomObject2.getContainer() , testCustomObject2.getKey()), - CustomObject.referenceTypeId())); - - final AttributeDraft customObjectReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + .contains( + "The value '{\"typeId\":\"key-value-document\"," + + "\"id\":\"non-existing-container|non-existing-key\"}' " + + "is not valid for field 'nestedAttribute.customObject-reference'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } + + @Test + void + sync_withNestedCustomObjectReferenceSetAsAttribute_shouldCreateProductReferencingExistingCustomObjects() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueSetOfReferences( + CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME, + createReferenceObject( + format("%s|%s", testCustomObject1.getContainer(), testCustomObject1.getKey()), + CustomObject.referenceTypeId()), + createReferenceObject( + format("%s|%s", testCustomObject2.getContainer(), testCustomObject2.getKey()), + CustomObject.referenceTypeId())); + + final AttributeDraft customObjectReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(customObjectReferenceAttribute) .build(); - final ProductDraft productDraftWithCustomObjectReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCustomObjectReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCustomObjectReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - - final Product createdProduct = CTP_TARGET_CLIENT - .execute(ProductByKeyGet.of(productDraftWithCustomObjectReference.getKey())) + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCustomObjectReference)) .toCompletableFuture() .join(); - final Optional createdCustomObjectReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(customObjectReferenceAttribute.getName()); - - assertThat(createdCustomObjectReferenceAttribute).hasValueSatisfying(attribute -> { - - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(nestedAttributeNameField.asText()).isEqualTo(CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME); - assertThat(nestedAttributeValueField).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) nestedAttributeValueField; - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(CustomObject.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCustomObject1.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(CustomObject.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCustomObject2.getId()); - }); - }); - } - - @Test - void sync_withNestedCustomObjectReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueSetOfReferences(CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME, - createReferenceObject(format("%s|%s", testCustomObject1.getContainer() , testCustomObject1.getKey()), - CustomObject.referenceTypeId()), - createReferenceObject("non-existing-container|non-existing-key", CustomObject.referenceTypeId())); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final AttributeDraft customObjectReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + final Product createdProduct = + CTP_TARGET_CLIENT + .execute(ProductByKeyGet.of(productDraftWithCustomObjectReference.getKey())) + .toCompletableFuture() + .join(); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdCustomObjectReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(customObjectReferenceAttribute.getName()); + + assertThat(createdCustomObjectReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()) + .isEqualTo(CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME); + assertThat(nestedAttributeValueField).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) nestedAttributeValueField; + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(CustomObject.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCustomObject1.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(CustomObject.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCustomObject2.getId()); + }); + }); + } + + @Test + void + sync_withNestedCustomObjectReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueSetOfReferences( + CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME, + createReferenceObject( + format("%s|%s", testCustomObject1.getContainer(), testCustomObject1.getKey()), + CustomObject.referenceTypeId()), + createReferenceObject( + "non-existing-container|non-existing-key", CustomObject.referenceTypeId())); + + final AttributeDraft customObjectReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(customObjectReferenceAttribute) .build(); - final ProductDraft productDraftWithCustomObjectReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCustomObjectReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCustomObjectReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"key-value-document\"," - + "\"id\":\"non-existing-container|non-existing-key\"}' " - + "is not valid for field 'nestedAttribute.customObject-reference-set'"); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCustomObjectReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"key-value-document\"," + + "\"id\":\"non-existing-container|non-existing-key\"}' " + + "is not valid for field 'nestedAttribute.customObject-reference-set'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"key-value-document\"," - + "\"id\":\"non-existing-container|non-existing-key\"}' " - + "is not valid for field 'nestedAttribute.customObject-reference-set'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } - - @Test - void sync_withSetOfNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingCustomObjects() { - // preparation - final ArrayNode nestedAttributeValue = - createArrayNode( - createNestedAttributeValueSetOfReferences(CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME, - createReferenceObject( - format("%s|%s", testCustomObject1.getContainer() , testCustomObject1.getKey()), - CustomObject.referenceTypeId()), - createReferenceObject( - format("%s|%s", testCustomObject2.getContainer() , testCustomObject2.getKey()), - CustomObject.referenceTypeId()))); - - final AttributeDraft customObjectReferenceAttribute = - AttributeDraft.of("setOfNestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + .contains( + "The value '{\"typeId\":\"key-value-document\"," + + "\"id\":\"non-existing-container|non-existing-key\"}' " + + "is not valid for field 'nestedAttribute.customObject-reference-set'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } + + @Test + void + sync_withSetOfNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingCustomObjects() { + // preparation + final ArrayNode nestedAttributeValue = + createArrayNode( + createNestedAttributeValueSetOfReferences( + CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME, + createReferenceObject( + format("%s|%s", testCustomObject1.getContainer(), testCustomObject1.getKey()), + CustomObject.referenceTypeId()), + createReferenceObject( + format("%s|%s", testCustomObject2.getContainer(), testCustomObject2.getKey()), + CustomObject.referenceTypeId()))); + + final AttributeDraft customObjectReferenceAttribute = + AttributeDraft.of("setOfNestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(customObjectReferenceAttribute) .build(); - final ProductDraft productDraftWithCustomObjectReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCustomObjectReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCustomObjectReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCustomObjectReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCustomObjectReference.getKey())) .toCompletableFuture() .join(); - final Optional createdCustomObjectReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(customObjectReferenceAttribute.getName()); - - assertThat(createdCustomObjectReferenceAttribute).hasValueSatisfying(attribute -> { - - final JsonNode setOfNestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode setOfNestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(setOfNestedAttributeNameField.asText()).isEqualTo(CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME); - assertThat(setOfNestedAttributeValueField).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) setOfNestedAttributeValueField; - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(CustomObject.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCustomObject1.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(CustomObject.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testCustomObject2.getId()); - }); - }); - } + final Optional createdCustomObjectReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(customObjectReferenceAttribute.getName()); + + assertThat(createdCustomObjectReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode setOfNestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode setOfNestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(setOfNestedAttributeNameField.asText()) + .isEqualTo(CUSTOM_OBJECT_REFERENCE_SET_ATTR_NAME); + assertThat(setOfNestedAttributeValueField).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) setOfNestedAttributeValueField; + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(CustomObject.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCustomObject1.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(CustomObject.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testCustomObject2.getId()); + }); + }); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedProductTypesIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedProductTypesIT.java index cb5b2f4ad9..6713e1ac40 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedProductTypesIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedProductTypesIT.java @@ -1,5 +1,23 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createArrayNode; +import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueReferences; +import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueSetOfReferences; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.TriConsumer; import com.commercetools.sync.products.ProductSync; @@ -28,623 +46,668 @@ import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.commands.ProductTypeUpdateCommand; import io.sphere.sdk.producttypes.commands.updateactions.AddAttributeDefinition; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createArrayNode; -import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueReferences; -import static com.commercetools.sync.integration.externalsource.products.ProductSyncWithNestedReferencedProductsIT.createNestedAttributeValueSetOfReferences; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; - class ProductSyncWithNestedReferencedProductTypesIT { - private static ProductType testProductType1; - private static ProductType testProductType2; - - private ProductSyncOptions syncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> actions; - - private static final String ATTRIBUTE_NAME_FIELD = "name"; - private static final String ATTRIBUTE_VALUE_FIELD = "value"; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - testProductType1 = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - testProductType2 = createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, CTP_TARGET_CLIENT); - - final AttributeDefinitionDraft nestedAttributeDef = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(testProductType2), "nestedAttribute", ofEnglish("nestedAttribute"), false) + private static ProductType testProductType1; + private static ProductType testProductType2; + + private ProductSyncOptions syncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> actions; + + private static final String ATTRIBUTE_NAME_FIELD = "name"; + private static final String ATTRIBUTE_VALUE_FIELD = "value"; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + testProductType1 = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + testProductType2 = + createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, CTP_TARGET_CLIENT); + + final AttributeDefinitionDraft nestedAttributeDef = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(testProductType2), + "nestedAttribute", + ofEnglish("nestedAttribute"), + false) .searchable(false) .build(); - final AttributeDefinitionDraft setOfNestedAttributeDef = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(NestedAttributeType.of(testProductType2)), "setOfNestedAttribute", - ofEnglish("setOfNestedAttribute"), false) + final AttributeDefinitionDraft setOfNestedAttributeDef = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(NestedAttributeType.of(testProductType2)), + "setOfNestedAttribute", + ofEnglish("setOfNestedAttribute"), + false) .searchable(false) .build(); - - final ProductTypeUpdateCommand productTypeUpdateCommand = ProductTypeUpdateCommand.of(testProductType1, - asList(AddAttributeDefinition.of(nestedAttributeDef), AddAttributeDefinition.of(setOfNestedAttributeDef))); - - CTP_TARGET_CLIENT.execute(productTypeUpdateCommand).toCompletableFuture().join(); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - actions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final TriConsumer, Optional> warningCallback - = (syncException, productDraft, product) -> warningCallBackMessages.add(syncException.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((syncException, draft, product, updateActions) - -> collectErrors(syncException.getMessage(), syncException)) - .beforeUpdateCallback(this::collectActions) - .warningCallback(warningCallback) - .build(); - } - - private void collectErrors(final String errorMessage, final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> collectActions(@Nonnull final List> actions, - @Nonnull final ProductDraft productDraft, - @Nonnull final Product product) { - this.actions.addAll(actions); - return actions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withNestedProductTypeReferenceAsAttribute_shouldCreateProductReferencingExistingProductType() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("productType-reference", - createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId())); - - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final ProductTypeUpdateCommand productTypeUpdateCommand = + ProductTypeUpdateCommand.of( + testProductType1, + asList( + AddAttributeDefinition.of(nestedAttributeDef), + AddAttributeDefinition.of(setOfNestedAttributeDef))); + + CTP_TARGET_CLIENT.execute(productTypeUpdateCommand).toCompletableFuture().join(); + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + actions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final TriConsumer, Optional> warningCallback = + (syncException, productDraft, product) -> + warningCallBackMessages.add(syncException.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (syncException, draft, product, updateActions) -> + collectErrors(syncException.getMessage(), syncException)) + .beforeUpdateCallback(this::collectActions) + .warningCallback(warningCallback) + .build(); + } + + private void collectErrors(final String errorMessage, final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> collectActions( + @Nonnull final List> actions, + @Nonnull final ProductDraft productDraft, + @Nonnull final Product product) { + this.actions.addAll(actions); + return actions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void + sync_withNestedProductTypeReferenceAsAttribute_shouldCreateProductReferencingExistingProductType() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "productType-reference", + createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId())); + + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductTypeReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceAttribute.getName()); - - assertThat(createdProductTypeReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeReferenceValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(nestedAttributeNameField.asText()).isEqualTo("productType-reference"); - assertThat(nestedAttributeReferenceValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(nestedAttributeReferenceValueField.get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(testProductType1.getId()); - }); - } - - @Test - void sync_withSameNestedProductTypeReferenceAsAttribute_shouldNotSyncAnythingNew() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("productType-reference", - createReferenceObject(testProductType1.getId(), ProductType.referenceTypeId())); - - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductTypeReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceAttribute.getName()); + + assertThat(createdProductTypeReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeReferenceValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()).isEqualTo("productType-reference"); + assertThat(nestedAttributeReferenceValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(nestedAttributeReferenceValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProductType1.getId()); + }); + } + + @Test + void sync_withSameNestedProductTypeReferenceAsAttribute_shouldNotSyncAnythingNew() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "productType-reference", + createReferenceObject(testProductType1.getId(), ProductType.referenceTypeId())); + + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); - final ObjectNode newNestedAttributeValue = - createNestedAttributeValueReferences("productType-reference", - createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId())); + final ObjectNode newNestedAttributeValue = + createNestedAttributeValueReferences( + "productType-reference", + createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId())); - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductReference = ProductDraftBuilder - .of(testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductReference = + ProductDraftBuilder.of( + testProductType1, + ofEnglish("productName"), + ofEnglish("productSlug"), + newMasterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductTypeReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceAttribute.getName()); - - assertThat(createdProductTypeReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - assertThat(nestedAttributeNameField.asText()).isEqualTo("productType-reference"); - assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(testProductType1.getId()); - }); - } - - @Test - void sync_withChangedNestedProductTypeReferenceAsAttribute_shouldUpdateProductReferencingExistingProductType() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("productType-reference", - createReferenceObject(testProductType1.getId(), ProductType.referenceTypeId())); - - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductTypeReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceAttribute.getName()); + + assertThat(createdProductTypeReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + assertThat(nestedAttributeNameField.asText()).isEqualTo("productType-reference"); + assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProductType1.getId()); + }); + } + + @Test + void + sync_withChangedNestedProductTypeReferenceAsAttribute_shouldUpdateProductReferencingExistingProductType() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "productType-reference", + createReferenceObject(testProductType1.getId(), ProductType.referenceTypeId())); + + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); - final ObjectNode newNestedAttributeValue = - createNestedAttributeValueReferences("productType-reference", - createReferenceObject(testProductType2.getKey(), ProductType.referenceTypeId())); + final ObjectNode newNestedAttributeValue = + createNestedAttributeValueReferences( + "productType-reference", + createReferenceObject(testProductType2.getKey(), ProductType.referenceTypeId())); - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductTypeReference = ProductDraftBuilder - .of(testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductTypeReference = + ProductDraftBuilder.of( + testProductType1, + ofEnglish("productName"), + ofEnglish("productSlug"), + newMasterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - final ObjectNode expectedNestedAttributeValue = - createNestedAttributeValueReferences("productType-reference", - createReferenceObject(testProductType2.getId(), ProductType.referenceTypeId())); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); - final AttributeDraft expectedProductTypeReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(expectedNestedAttributeValue)); - assertThat(actions).containsExactly(SetAttribute.of(1, expectedProductTypeReferenceAttribute, true)); + final ObjectNode expectedNestedAttributeValue = + createNestedAttributeValueReferences( + "productType-reference", + createReferenceObject(testProductType2.getId(), ProductType.referenceTypeId())); + final AttributeDraft expectedProductTypeReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(expectedNestedAttributeValue)); + assertThat(actions) + .containsExactly(SetAttribute.of(1, expectedProductTypeReferenceAttribute, true)); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductTypeReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceAttribute.getName()); - - assertThat(createdProductTypeReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - - assertThat(nestedAttributeNameField.asText()).isEqualTo("productType-reference"); - assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProductType2.getId()); - }); - } - - @Test - void sync_withNonExistingNestedProductTypeReferenceAsAttribute_ShouldFailCreatingTheProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("productType-reference", - createReferenceObject("nonExistingKey", ProductType.referenceTypeId())); - - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductTypeReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceAttribute.getName()); + + assertThat(createdProductTypeReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()).isEqualTo("productType-reference"); + assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProductType2.getId()); + }); + } + + @Test + void sync_withNonExistingNestedProductTypeReferenceAsAttribute_ShouldFailCreatingTheProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "productType-reference", + createReferenceObject("nonExistingKey", ProductType.referenceTypeId())); + + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'nestedAttribute.productType-reference'"); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'nestedAttribute.productType-reference'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'nestedAttribute.productType-reference'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } - - @Test - void sync_withNestedProductTypeReferenceSetAsAttribute_shouldCreateProductReferencingExistingProductType() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueSetOfReferences("productType-reference-set", - createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId()), - createReferenceObject(testProductType2.getKey(), ProductType.referenceTypeId())); - - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + .contains( + "The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'nestedAttribute.productType-reference'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } + + @Test + void + sync_withNestedProductTypeReferenceSetAsAttribute_shouldCreateProductReferencingExistingProductType() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueSetOfReferences( + "productType-reference-set", + createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId()), + createReferenceObject(testProductType2.getKey(), ProductType.referenceTypeId())); + + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - - final Product createdProduct = CTP_TARGET_CLIENT - .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductTypeReference)) .toCompletableFuture() .join(); - final Optional createdProductTypeReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceAttribute.getName()); - - assertThat(createdProductTypeReferenceAttribute).hasValueSatisfying(attribute -> { - - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(nestedAttributeNameField.asText()).isEqualTo("productType-reference-set"); - assertThat(nestedAttributeValueField).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) nestedAttributeValueField; - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProductType1.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProductType2.getId()); - }); - }); - } - - @Test - void sync_withNestedProductTypeReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueSetOfReferences("productType-reference-set", - createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId()), - createReferenceObject("nonExistingKey", ProductType.referenceTypeId())); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + final Product createdProduct = + CTP_TARGET_CLIENT + .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) + .toCompletableFuture() + .join(); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductTypeReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceAttribute.getName()); + + assertThat(createdProductTypeReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()).isEqualTo("productType-reference-set"); + assertThat(nestedAttributeValueField).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) nestedAttributeValueField; + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProductType1.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProductType2.getId()); + }); + }); + } + + @Test + void + sync_withNestedProductTypeReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueSetOfReferences( + "productType-reference-set", + createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId()), + createReferenceObject("nonExistingKey", ProductType.referenceTypeId())); + + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0 - ); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'nestedAttribute.productType-reference-set'"); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'nestedAttribute.productType-reference-set'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'nestedAttribute.productType-reference-set'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } - - @Test - void sync_withSetOfNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingProductType() { - // preparation - final ArrayNode nestedAttributeValue = - createArrayNode( - createNestedAttributeValueSetOfReferences("productType-reference-set", - createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId()), - createReferenceObject(testProductType2.getKey(), ProductType.referenceTypeId()))); - - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("setOfNestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + .contains( + "The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'nestedAttribute.productType-reference-set'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } + + @Test + void + sync_withSetOfNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingProductType() { + // preparation + final ArrayNode nestedAttributeValue = + createArrayNode( + createNestedAttributeValueSetOfReferences( + "productType-reference-set", + createReferenceObject(testProductType1.getKey(), ProductType.referenceTypeId()), + createReferenceObject(testProductType2.getKey(), ProductType.referenceTypeId()))); + + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("setOfNestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + testProductType1, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductTypeReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceAttribute.getName()); - - assertThat(createdProductTypeReferenceAttribute).hasValueSatisfying(attribute -> { - - final JsonNode setOfNestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode setOfNestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(setOfNestedAttributeNameField.asText()).isEqualTo("productType-reference-set"); - assertThat(setOfNestedAttributeValueField).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) setOfNestedAttributeValueField; - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProductType1.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProductType2.getId()); - }); - }); - } + final Optional createdProductTypeReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceAttribute.getName()); + + assertThat(createdProductTypeReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode setOfNestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode setOfNestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(setOfNestedAttributeNameField.asText()) + .isEqualTo("productType-reference-set"); + assertThat(setOfNestedAttributeValueField).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) setOfNestedAttributeValueField; + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProductType1.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProductType2.getId()); + }); + }); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedProductsIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedProductsIT.java index 5eacf60a99..ed67838f9a 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedProductsIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithNestedReferencedProductsIT.java @@ -1,5 +1,23 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.models.WaitingToBeResolved; import com.commercetools.sync.commons.utils.TriConsumer; @@ -30,732 +48,772 @@ import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.commands.ProductTypeUpdateCommand; import io.sphere.sdk.producttypes.commands.updateactions.AddAttributeDefinition; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncWithNestedReferencedProductsIT { - private static ProductType productType; - - private ProductSyncOptions syncOptions; - private Product testProduct1; - private Product testProduct2; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> actions; - - private static final String ATTRIBUTE_NAME_FIELD = "name"; - private static final String ATTRIBUTE_VALUE_FIELD = "value"; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - final ProductType nestedProductType = createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, - CTP_TARGET_CLIENT); - - - final AttributeDefinitionDraft nestedAttributeDef = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(nestedProductType), "nestedAttribute", ofEnglish("nestedAttribute"), false) + private static ProductType productType; + + private ProductSyncOptions syncOptions; + private Product testProduct1; + private Product testProduct2; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> actions; + + private static final String ATTRIBUTE_NAME_FIELD = "name"; + private static final String ATTRIBUTE_VALUE_FIELD = "value"; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + final ProductType nestedProductType = + createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, CTP_TARGET_CLIENT); + + final AttributeDefinitionDraft nestedAttributeDef = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(nestedProductType), + "nestedAttribute", + ofEnglish("nestedAttribute"), + false) .searchable(false) .build(); - final AttributeDefinitionDraft setOfNestedAttributeDef = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(NestedAttributeType.of(nestedProductType)), "setOfNestedAttribute", - ofEnglish("setOfNestedAttribute"), false) + final AttributeDefinitionDraft setOfNestedAttributeDef = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(NestedAttributeType.of(nestedProductType)), + "setOfNestedAttribute", + ofEnglish("setOfNestedAttribute"), + false) .searchable(false) .build(); + final ProductTypeUpdateCommand productTypeUpdateCommand = + ProductTypeUpdateCommand.of( + productType, + asList( + AddAttributeDefinition.of(nestedAttributeDef), + AddAttributeDefinition.of(setOfNestedAttributeDef))); - final ProductTypeUpdateCommand productTypeUpdateCommand = ProductTypeUpdateCommand.of(productType, - asList(AddAttributeDefinition.of(nestedAttributeDef), AddAttributeDefinition.of(setOfNestedAttributeDef))); - - CTP_TARGET_CLIENT.execute(productTypeUpdateCommand).toCompletableFuture().join(); - } + CTP_TARGET_CLIENT.execute(productTypeUpdateCommand).toCompletableFuture().join(); + } + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - - final ProductDraft productDraft = ProductDraftBuilder - .of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), emptyList()) + final ProductDraft productDraft = + ProductDraftBuilder.of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), emptyList()) .key("foo") .build(); - final ProductDraft productDraft2 = ProductDraftBuilder - .of(productType, ofEnglish("foo2"), ofEnglish("foo-slug-2"), emptyList()) + final ProductDraft productDraft2 = + ProductDraftBuilder.of(productType, ofEnglish("foo2"), ofEnglish("foo-slug-2"), emptyList()) .key("foo2") .build(); - testProduct1 = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); - testProduct2 = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft2))); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - actions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final TriConsumer, Optional> warningCallBack - = (syncException, productDraft, product) -> warningCallBackMessages.add(syncException.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, draft, product, updateActions) - -> collectErrors(exception.getMessage(), exception)) - .beforeUpdateCallback(this::collectActions) - .warningCallback(warningCallBack) - .build(); - } - - private void collectErrors(final String errorMessage, final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> collectActions(@Nonnull final List> actions, - @Nonnull final ProductDraft productDraft, - @Nonnull final Product product) { - this.actions.addAll(actions); - return actions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withNestedAttributeWithTextAttribute_shouldCreateProduct() { - // preparation - final ArrayNode nestedAttributeValue = JsonNodeFactory.instance.arrayNode(); - final ObjectNode nestedProductTypeAttribute = JsonNodeFactory.instance.objectNode(); - nestedAttributeValue.add(nestedProductTypeAttribute); - nestedProductTypeAttribute.put(ATTRIBUTE_NAME_FIELD, "text-attr"); - nestedProductTypeAttribute.put(ATTRIBUTE_VALUE_FIELD, "text-attr-value"); - - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("nestedAttribute", nestedAttributeValue); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + testProduct1 = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); + testProduct2 = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft2))); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + actions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final TriConsumer, Optional> warningCallBack = + (syncException, productDraft, product) -> + warningCallBackMessages.add(syncException.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, draft, product, updateActions) -> + collectErrors(exception.getMessage(), exception)) + .beforeUpdateCallback(this::collectActions) + .warningCallback(warningCallBack) + .build(); + } + + private void collectErrors(final String errorMessage, final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> collectActions( + @Nonnull final List> actions, + @Nonnull final ProductDraft productDraft, + @Nonnull final Product product) { + this.actions.addAll(actions); + return actions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withNestedAttributeWithTextAttribute_shouldCreateProduct() { + // preparation + final ArrayNode nestedAttributeValue = JsonNodeFactory.instance.arrayNode(); + final ObjectNode nestedProductTypeAttribute = JsonNodeFactory.instance.objectNode(); + nestedAttributeValue.add(nestedProductTypeAttribute); + nestedProductTypeAttribute.put(ATTRIBUTE_NAME_FIELD, "text-attr"); + nestedProductTypeAttribute.put(ATTRIBUTE_VALUE_FIELD, "text-attr-value"); + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("nestedAttribute", nestedAttributeValue); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD).asText()) - .isEqualTo("text-attr-value"); - assertThat(attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD).asText()).isEqualTo("text-attr"); - }); - } - - @Test - void sync_withNestedProductReferenceAsAttribute_shouldCreateProductReferencingExistingProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("product-reference", - createReferenceObject(testProduct1.getKey(), Product.referenceTypeId())); - - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD).asText()) + .isEqualTo("text-attr-value"); + assertThat(attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD).asText()) + .isEqualTo("text-attr"); + }); + } + + @Test + void sync_withNestedProductReferenceAsAttribute_shouldCreateProductReferencingExistingProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "product-reference", + createReferenceObject(testProduct1.getKey(), Product.referenceTypeId())); + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeReferenceValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(nestedAttributeNameField.asText()).isEqualTo("product-reference"); - assertThat(nestedAttributeReferenceValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(nestedAttributeReferenceValueField.get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(testProduct1.getId()); - }); - } - - @Test - void sync_withSameNestedProductReferenceAsAttribute_shouldNotSyncAnythingNew() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("product-reference", - createReferenceObject(testProduct1.getId(), Product.referenceTypeId())); - - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeReferenceValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()).isEqualTo("product-reference"); + assertThat(nestedAttributeReferenceValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(nestedAttributeReferenceValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProduct1.getId()); + }); + } + + @Test + void sync_withSameNestedProductReferenceAsAttribute_shouldNotSyncAnythingNew() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "product-reference", + createReferenceObject(testProduct1.getId(), Product.referenceTypeId())); + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithProductReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithProductReference)) + .toCompletableFuture() + .join(); - final ObjectNode newNestedAttributeValue = - createNestedAttributeValueReferences("product-reference", - createReferenceObject(testProduct1.getKey(), Product.referenceTypeId())); + final ObjectNode newNestedAttributeValue = + createNestedAttributeValueReferences( + "product-reference", + createReferenceObject(testProduct1.getKey(), Product.referenceTypeId())); - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - assertThat(nestedAttributeNameField.asText()).isEqualTo("product-reference"); - assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProduct1.getId()); - }); - } - - @Test - void sync_withChangedNestedProductReferenceAsAttribute_shouldUpdateProductReferencingExistingProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("product-reference", - createReferenceObject(testProduct1.getId(), Product.referenceTypeId())); - - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + assertThat(nestedAttributeNameField.asText()).isEqualTo("product-reference"); + assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProduct1.getId()); + }); + } + + @Test + void + sync_withChangedNestedProductReferenceAsAttribute_shouldUpdateProductReferencingExistingProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "product-reference", + createReferenceObject(testProduct1.getId(), Product.referenceTypeId())); + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithProductReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithProductReference)) + .toCompletableFuture() + .join(); - final ObjectNode newNestedAttributeValue = - createNestedAttributeValueReferences("product-reference", - createReferenceObject(testProduct2.getKey(), Product.referenceTypeId())); + final ObjectNode newNestedAttributeValue = + createNestedAttributeValueReferences( + "product-reference", + createReferenceObject(testProduct2.getKey(), Product.referenceTypeId())); - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(newNestedAttributeValue)); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - final ObjectNode expectedNestedAttributeValue = - createNestedAttributeValueReferences("product-reference", - createReferenceObject(testProduct2.getId(), Product.referenceTypeId())); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); - final AttributeDraft expectedProductReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(expectedNestedAttributeValue)); - assertThat(actions).containsExactly(SetAttribute.of(1, expectedProductReferenceAttribute, true)); + final ObjectNode expectedNestedAttributeValue = + createNestedAttributeValueReferences( + "product-reference", + createReferenceObject(testProduct2.getId(), Product.referenceTypeId())); + final AttributeDraft expectedProductReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(expectedNestedAttributeValue)); + assertThat(actions) + .containsExactly(SetAttribute.of(1, expectedProductReferenceAttribute, true)); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - - assertThat(nestedAttributeNameField.asText()).isEqualTo("product-reference"); - assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProduct2.getId()); - }); - } - - @Test - void sync_withNonExistingNestedProductReferenceAsAttribute_ShouldFailCreatingTheProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueReferences("product-reference", - createReferenceObject("nonExistingKey", Product.referenceTypeId())); - - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()).isEqualTo("product-reference"); + assertThat(nestedAttributeValueField.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(nestedAttributeValueField.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProduct2.getId()); + }); + } + + @Test + void sync_withNonExistingNestedProductReferenceAsAttribute_ShouldFailCreatingTheProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueReferences( + "product-reference", + createReferenceObject("nonExistingKey", Product.referenceTypeId())); + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - - final UnresolvedReferencesServiceImpl unresolvedReferencesService = - new UnresolvedReferencesServiceImpl(syncOptions); - final Set waitingToBeResolvedDrafts = unresolvedReferencesService + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + + final UnresolvedReferencesServiceImpl unresolvedReferencesService = + new UnresolvedReferencesServiceImpl(syncOptions); + final Set waitingToBeResolvedDrafts = + unresolvedReferencesService .fetch(asSet(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - assertThat(waitingToBeResolvedDrafts) - .hasOnlyOneElementSatisfying(waitingToBeResolvedDraft -> { - assertThat(waitingToBeResolvedDraft.getProductDraft().getKey()) - .isEqualTo(productDraftWithProductReference.getKey()); - assertThat(waitingToBeResolvedDraft.getMissingReferencedProductKeys()) - .containsExactly("nonExistingKey"); + assertThat(waitingToBeResolvedDrafts) + .hasOnlyOneElementSatisfying( + waitingToBeResolvedDraft -> { + assertThat(waitingToBeResolvedDraft.getProductDraft().getKey()) + .isEqualTo(productDraftWithProductReference.getKey()); + assertThat(waitingToBeResolvedDraft.getMissingReferencedProductKeys()) + .containsExactly("nonExistingKey"); }); - } - - @Test - void sync_withNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingProducts() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueSetOfReferences("product-reference-set", - createReferenceObject(testProduct1.getKey(), Product.referenceTypeId()), - createReferenceObject(testProduct2.getKey(), Product.referenceTypeId())); - - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + } + + @Test + void + sync_withNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingProducts() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueSetOfReferences( + "product-reference-set", + createReferenceObject(testProduct1.getKey(), Product.referenceTypeId()), + createReferenceObject(testProduct2.getKey(), Product.referenceTypeId())); + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - - final Product createdProduct = CTP_TARGET_CLIENT - .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - - final JsonNode nestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode nestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(nestedAttributeNameField.asText()).isEqualTo("product-reference-set"); - assertThat(nestedAttributeValueField).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) nestedAttributeValueField; - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Product.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProduct1.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Product.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProduct2.getId()); - }); - }); - } - - @Test - void sync_withNestedProductReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { - // preparation - final ObjectNode nestedAttributeValue = - createNestedAttributeValueSetOfReferences("product-reference-set", - createReferenceObject(testProduct1.getKey(), Product.referenceTypeId()), - createReferenceObject("nonExistingKey", Product.referenceTypeId())); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + final Product createdProduct = + CTP_TARGET_CLIENT + .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) + .toCompletableFuture() + .join(); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode nestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode nestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(nestedAttributeNameField.asText()).isEqualTo("product-reference-set"); + assertThat(nestedAttributeValueField).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) nestedAttributeValueField; + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProduct1.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProduct2.getId()); + }); + }); + } + + @Test + void + sync_withNestedProductReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { + // preparation + final ObjectNode nestedAttributeValue = + createNestedAttributeValueSetOfReferences( + "product-reference-set", + createReferenceObject(testProduct1.getKey(), Product.referenceTypeId()), + createReferenceObject("nonExistingKey", Product.referenceTypeId())); + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("nestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - - final UnresolvedReferencesServiceImpl unresolvedReferencesService = - new UnresolvedReferencesServiceImpl(syncOptions); - final Set waitingToBeResolvedDrafts = unresolvedReferencesService + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + + final UnresolvedReferencesServiceImpl unresolvedReferencesService = + new UnresolvedReferencesServiceImpl(syncOptions); + final Set waitingToBeResolvedDrafts = + unresolvedReferencesService .fetch(asSet(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - assertThat(waitingToBeResolvedDrafts) - .hasOnlyOneElementSatisfying(waitingToBeResolvedDraft -> { - assertThat(waitingToBeResolvedDraft.getProductDraft().getKey()) - .isEqualTo(productDraftWithProductReference.getKey()); - assertThat(waitingToBeResolvedDraft.getMissingReferencedProductKeys()) - .containsExactly("nonExistingKey"); + assertThat(waitingToBeResolvedDrafts) + .hasOnlyOneElementSatisfying( + waitingToBeResolvedDraft -> { + assertThat(waitingToBeResolvedDraft.getProductDraft().getKey()) + .isEqualTo(productDraftWithProductReference.getKey()); + assertThat(waitingToBeResolvedDraft.getMissingReferencedProductKeys()) + .containsExactly("nonExistingKey"); }); - } - - @Test - void sync_withSetOfNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingProducts() { - // preparation - final ArrayNode nestedAttributeValue = - createArrayNode( - createNestedAttributeValueSetOfReferences("product-reference-set", - createReferenceObject(testProduct1.getKey(), Product.referenceTypeId()), - createReferenceObject(testProduct2.getKey(), Product.referenceTypeId()))); - - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("setOfNestedAttribute", createArrayNode(nestedAttributeValue)); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + } + + @Test + void + sync_withSetOfNestedProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingProducts() { + // preparation + final ArrayNode nestedAttributeValue = + createArrayNode( + createNestedAttributeValueSetOfReferences( + "product-reference-set", + createReferenceObject(testProduct1.getKey(), Product.referenceTypeId()), + createReferenceObject(testProduct2.getKey(), Product.referenceTypeId()))); + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("setOfNestedAttribute", createArrayNode(nestedAttributeValue)); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - - final JsonNode setOfNestedAttributeNameField = attribute - .getValueAsJsonNode() - .get(0) - .get(0) - .get(ATTRIBUTE_NAME_FIELD); - final JsonNode setOfNestedAttributeValueField = attribute - .getValueAsJsonNode() - .get(0) - .get(0) - .get(ATTRIBUTE_VALUE_FIELD); - - assertThat(setOfNestedAttributeNameField.asText()).isEqualTo("product-reference-set"); - assertThat(setOfNestedAttributeValueField).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) setOfNestedAttributeValueField; - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Product.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProduct1.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Product.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(testProduct2.getId()); - }); - }); - } - - @Nonnull - static ObjectNode createNestedAttributeValueReferences( - @Nonnull final String attributeName, - @Nonnull final ObjectNode referenceValue) { - - final ObjectNode referenceAttribute = JsonNodeFactory.instance.objectNode(); - referenceAttribute.put(ATTRIBUTE_NAME_FIELD, attributeName); - referenceAttribute.set(ATTRIBUTE_VALUE_FIELD, referenceValue); - - return referenceAttribute; - } - - @Nonnull - static ObjectNode createNestedAttributeValueSetOfReferences( - @Nonnull final String attributeName, - @Nonnull final ObjectNode... referenceValues) { - - final ObjectNode setOfReferencesAttribute = JsonNodeFactory.instance.objectNode(); - setOfReferencesAttribute.put(ATTRIBUTE_NAME_FIELD, attributeName); - setOfReferencesAttribute.set(ATTRIBUTE_VALUE_FIELD, createArrayNode(referenceValues)); - - return setOfReferencesAttribute; - } - - @Nonnull - static ArrayNode createArrayNode(@Nonnull final ObjectNode... objectNodes) { - final ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode(); - asList(objectNodes).forEach(arrayNode::add); - return arrayNode; - } - - @Nonnull - static ArrayNode createArrayNode(@Nonnull final ArrayNode arrayNode) { - final ArrayNode containingArrayNode = JsonNodeFactory.instance.arrayNode(); - containingArrayNode.add(arrayNode); - return containingArrayNode; - } + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + final JsonNode setOfNestedAttributeNameField = + attribute.getValueAsJsonNode().get(0).get(0).get(ATTRIBUTE_NAME_FIELD); + final JsonNode setOfNestedAttributeValueField = + attribute.getValueAsJsonNode().get(0).get(0).get(ATTRIBUTE_VALUE_FIELD); + + assertThat(setOfNestedAttributeNameField.asText()).isEqualTo("product-reference-set"); + assertThat(setOfNestedAttributeValueField).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) setOfNestedAttributeValueField; + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProduct1.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(testProduct2.getId()); + }); + }); + } + + @Nonnull + static ObjectNode createNestedAttributeValueReferences( + @Nonnull final String attributeName, @Nonnull final ObjectNode referenceValue) { + + final ObjectNode referenceAttribute = JsonNodeFactory.instance.objectNode(); + referenceAttribute.put(ATTRIBUTE_NAME_FIELD, attributeName); + referenceAttribute.set(ATTRIBUTE_VALUE_FIELD, referenceValue); + + return referenceAttribute; + } + + @Nonnull + static ObjectNode createNestedAttributeValueSetOfReferences( + @Nonnull final String attributeName, @Nonnull final ObjectNode... referenceValues) { + + final ObjectNode setOfReferencesAttribute = JsonNodeFactory.instance.objectNode(); + setOfReferencesAttribute.put(ATTRIBUTE_NAME_FIELD, attributeName); + setOfReferencesAttribute.set(ATTRIBUTE_VALUE_FIELD, createArrayNode(referenceValues)); + + return setOfReferencesAttribute; + } + + @Nonnull + static ArrayNode createArrayNode(@Nonnull final ObjectNode... objectNodes) { + final ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode(); + asList(objectNodes).forEach(arrayNode::add); + return arrayNode; + } + + @Nonnull + static ArrayNode createArrayNode(@Nonnull final ArrayNode arrayNode) { + final ArrayNode containingArrayNode = JsonNodeFactory.instance.arrayNode(); + containingArrayNode.add(arrayNode); + return containingArrayNode; + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithPricesIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithPricesIT.java index d88f02fc57..305701e61e 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithPricesIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithPricesIT.java @@ -1,56 +1,5 @@ package com.commercetools.sync.integration.externalsource.products; -import com.commercetools.sync.commons.exceptions.SyncException; -import com.commercetools.sync.commons.utils.QuadConsumer; -import com.commercetools.sync.commons.utils.TriConsumer; -import com.commercetools.sync.commons.utils.TriFunction; -import com.commercetools.sync.internals.helpers.PriceCompositeId; -import com.commercetools.sync.products.ProductSync; -import com.commercetools.sync.products.ProductSyncOptions; -import com.commercetools.sync.products.ProductSyncOptionsBuilder; -import com.commercetools.sync.products.helpers.ProductSyncStatistics; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.NullNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import io.sphere.sdk.channels.Channel; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.customergroups.CustomerGroup; -import io.sphere.sdk.models.Asset; -import io.sphere.sdk.models.AssetDraft; -import io.sphere.sdk.products.Price; -import io.sphere.sdk.products.PriceDraft; -import io.sphere.sdk.products.PriceDraftBuilder; -import io.sphere.sdk.products.Product; -import io.sphere.sdk.products.ProductDraft; -import io.sphere.sdk.products.ProductDraftBuilder; -import io.sphere.sdk.products.ProductProjection; -import io.sphere.sdk.products.ProductProjectionType; -import io.sphere.sdk.products.commands.ProductCreateCommand; -import io.sphere.sdk.products.commands.updateactions.AddPrice; -import io.sphere.sdk.products.commands.updateactions.ChangePrice; -import io.sphere.sdk.products.commands.updateactions.RemovePrice; -import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomField; -import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomType; -import io.sphere.sdk.products.queries.ProductProjectionByKeyGet; -import io.sphere.sdk.producttypes.ProductType; -import io.sphere.sdk.types.CustomFieldsDraft; -import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; - import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToMap; import static com.commercetools.sync.integration.commons.utils.ChannelITUtils.createChannel; @@ -104,172 +53,243 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; -class ProductSyncWithPricesIT { - private static ProductType productType; - private static Type customType1; - private static Type customType2; - private static Channel channel1; - private static Channel channel2; - private static CustomerGroup cust1; - private static CustomerGroup cust2; - private Product product; - private ProductSync productSync; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> updateActionsFromSync; - - /** - * Delete all product related test data from the target project. Then creates price custom types, customer groups, - * channels and a product type for the target CTP project. - */ - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - - customType1 = createPricesCustomType("customType1", Locale.ENGLISH, - "customType1", CTP_TARGET_CLIENT); - - customType2 = createPricesCustomType("customType2", Locale.ENGLISH, - "customType2", CTP_TARGET_CLIENT); - - cust1 = createCustomerGroup(CTP_TARGET_CLIENT, "cust1", "cust1"); - cust2 = createCustomerGroup(CTP_TARGET_CLIENT, "cust2", "cust2"); - - channel1 = createChannel(CTP_TARGET_CLIENT, "channel1", "channel1"); - channel2 = createChannel(CTP_TARGET_CLIENT, "channel2", "channel2"); - - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - /** - * Deletes Products from the target CTP project. - */ - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - productSync = new ProductSync(buildSyncOptions()); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - updateActionsFromSync = new ArrayList<>(); - } +import com.commercetools.sync.commons.exceptions.SyncException; +import com.commercetools.sync.commons.utils.QuadConsumer; +import com.commercetools.sync.commons.utils.TriConsumer; +import com.commercetools.sync.commons.utils.TriFunction; +import com.commercetools.sync.internals.helpers.PriceCompositeId; +import com.commercetools.sync.products.ProductSync; +import com.commercetools.sync.products.ProductSyncOptions; +import com.commercetools.sync.products.ProductSyncOptionsBuilder; +import com.commercetools.sync.products.helpers.ProductSyncStatistics; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.sphere.sdk.channels.Channel; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.customergroups.CustomerGroup; +import io.sphere.sdk.models.Asset; +import io.sphere.sdk.models.AssetDraft; +import io.sphere.sdk.products.Price; +import io.sphere.sdk.products.PriceDraft; +import io.sphere.sdk.products.PriceDraftBuilder; +import io.sphere.sdk.products.Product; +import io.sphere.sdk.products.ProductDraft; +import io.sphere.sdk.products.ProductDraftBuilder; +import io.sphere.sdk.products.ProductProjection; +import io.sphere.sdk.products.ProductProjectionType; +import io.sphere.sdk.products.commands.ProductCreateCommand; +import io.sphere.sdk.products.commands.updateactions.AddPrice; +import io.sphere.sdk.products.commands.updateactions.ChangePrice; +import io.sphere.sdk.products.commands.updateactions.RemovePrice; +import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomField; +import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomType; +import io.sphere.sdk.products.queries.ProductProjectionByKeyGet; +import io.sphere.sdk.producttypes.ProductType; +import io.sphere.sdk.types.CustomFieldsDraft; +import io.sphere.sdk.types.Type; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; - private ProductSyncOptions buildSyncOptions() { - final QuadConsumer, Optional, List>> - errorCallBack = (exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }; - final TriConsumer, Optional> warningCallBack = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - - final TriFunction>, ProductDraft, Product, List>> - actionsCallBack = (updateActions, newDraft, oldProduct) -> { - updateActionsFromSync.addAll(updateActions); - return updateActions; +class ProductSyncWithPricesIT { + private static ProductType productType; + private static Type customType1; + private static Type customType2; + private static Channel channel1; + private static Channel channel2; + private static CustomerGroup cust1; + private static CustomerGroup cust2; + private Product product; + private ProductSync productSync; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> updateActionsFromSync; + + /** + * Delete all product related test data from the target project. Then creates price custom types, + * customer groups, channels and a product type for the target CTP project. + */ + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + + customType1 = + createPricesCustomType("customType1", Locale.ENGLISH, "customType1", CTP_TARGET_CLIENT); + + customType2 = + createPricesCustomType("customType2", Locale.ENGLISH, "customType2", CTP_TARGET_CLIENT); + + cust1 = createCustomerGroup(CTP_TARGET_CLIENT, "cust1", "cust1"); + cust2 = createCustomerGroup(CTP_TARGET_CLIENT, "cust2", "cust2"); + + channel1 = createChannel(CTP_TARGET_CLIENT, "channel1", "channel1"); + channel2 = createChannel(CTP_TARGET_CLIENT, "channel2", "channel2"); + + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + /** Deletes Products from the target CTP project. */ + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + productSync = new ProductSync(buildSyncOptions()); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + updateActionsFromSync = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallBack = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }; + final TriConsumer, Optional> warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + + final TriFunction< + List>, ProductDraft, Product, List>> + actionsCallBack = + (updateActions, newDraft, oldProduct) -> { + updateActionsFromSync.addAll(updateActions); + return updateActions; }; - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallBack) - .warningCallback(warningCallBack) - .beforeUpdateCallback(actionsCallBack) - .build(); - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withNullNewPricesAndEmptyExistingPrices_ShouldNotBuildUpdateActions() { - // Preparation - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("draftName"), ofEnglish("existingSlug"), + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback(errorCallBack) + .warningCallback(warningCallBack) + .beforeUpdateCallback(actionsCallBack) + .build(); + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withNullNewPricesAndEmptyExistingPrices_ShouldNotBuildUpdateActions() { + // Preparation + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("draftName"), + ofEnglish("existingSlug"), createVariantDraft("v1", null, null)) .key("existingProduct") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("draftName"), ofEnglish("existingSlug"), + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("draftName"), + ofEnglish("existingSlug"), createVariantDraft("v1", null, null)) .key("existingProduct") .build(); - - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(updateActionsFromSync).isEmpty(); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - assertThat(productProjection.getMasterVariant().getPrices()).isEmpty(); - } - - @Test - void sync_withAllMatchingPrices_ShouldNotBuildActions() { - // Preparation - final List oldPrices = asList( - DRAFT_US_111_USD, - DRAFT_DE_111_EUR, - DRAFT_DE_111_EUR_01_02, - DRAFT_DE_111_USD); - - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(updateActionsFromSync).isEmpty(); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + assertThat(productProjection.getMasterVariant().getPrices()).isEmpty(); + } + + @Test + void sync_withAllMatchingPrices_ShouldNotBuildActions() { + // Preparation + final List oldPrices = + asList(DRAFT_US_111_USD, DRAFT_DE_111_EUR, DRAFT_DE_111_EUR_01_02, DRAFT_DE_111_USD); + + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, oldPrices)) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, oldPrices)) .key("bar") .build(); - - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(updateActionsFromSync).isEmpty(); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), oldPrices); - } - - @Test - void withNonEmptyNewPricesButEmptyExistingPrices_ShouldAddAllNewPrices() { - // Preparation - final List newPrices = asList( + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(updateActionsFromSync).isEmpty(); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), oldPrices); + } + + @Test + void withNonEmptyNewPricesButEmptyExistingPrices_ShouldAddAllNewPrices() { + // Preparation + final List newPrices = + asList( DRAFT_US_111_USD, DRAFT_DE_111_EUR, DRAFT_DE_111_EUR_01_02, @@ -277,32 +297,39 @@ void withNonEmptyNewPricesButEmptyExistingPrices_ShouldAddAllNewPrices() { DRAFT_DE_111_USD, DRAFT_UK_111_GBP); - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, null)) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, newPrices)) .key("bar") .build(); - - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - final Integer masterVariantId = product.getMasterData().getStaged().getMasterVariant().getId(); - assertThat(updateActionsFromSync).containsExactlyInAnyOrder( + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + final Integer masterVariantId = product.getMasterData().getStaged().getMasterVariant().getId(); + assertThat(updateActionsFromSync) + .containsExactlyInAnyOrder( AddPrice.ofVariantId(masterVariantId, DRAFT_US_111_USD, true), AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_EUR, true), AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_EUR_01_02, true), @@ -310,18 +337,23 @@ void withNonEmptyNewPricesButEmptyExistingPrices_ShouldAddAllNewPrices() { AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_USD, true), AddPrice.ofVariantId(masterVariantId, DRAFT_UK_111_GBP, true)); - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), newPrices); - } - - @Test - void withNonEmptyNewWithDuplicatesPricesButEmptyExistingPrices_ShouldNotSyncPrices() { - // Preparation - final List newPrices = asList( + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), newPrices); + } + + @Test + void withNonEmptyNewWithDuplicatesPricesButEmptyExistingPrices_ShouldNotSyncPrices() { + // Preparation + final List newPrices = + asList( DRAFT_US_111_USD, DRAFT_US_111_USD, DRAFT_DE_111_EUR, @@ -330,41 +362,48 @@ void withNonEmptyNewWithDuplicatesPricesButEmptyExistingPrices_ShouldNotSyncPric DRAFT_DE_111_USD, DRAFT_UK_111_GBP); - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, null)) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, newPrices)) .key("bar") .build(); - - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - - assertThat(errorCallBackExceptions).hasSize(1) - .extracting(Throwable::getMessage) - .extracting(String::toLowerCase) - .hasOnlyOneElementSatisfying(msg -> - assertThat(msg).contains("duplicate price scope")); - assertThat(errorCallBackMessages).hasSize(1) - .extracting(String::toLowerCase) - .hasOnlyOneElementSatisfying(msg -> - assertThat(msg).contains("duplicate price scope")); - assertThat(warningCallBackMessages).isEmpty(); - - final Integer masterVariantId = product.getMasterData().getStaged().getMasterVariant().getId(); - assertThat(updateActionsFromSync).containsExactlyInAnyOrder( + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + + assertThat(errorCallBackExceptions) + .hasSize(1) + .extracting(Throwable::getMessage) + .extracting(String::toLowerCase) + .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("duplicate price scope")); + assertThat(errorCallBackMessages) + .hasSize(1) + .extracting(String::toLowerCase) + .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("duplicate price scope")); + assertThat(warningCallBackMessages).isEmpty(); + + final Integer masterVariantId = product.getMasterData().getStaged().getMasterVariant().getId(); + assertThat(updateActionsFromSync) + .containsExactlyInAnyOrder( AddPrice.ofVariantId(masterVariantId, DRAFT_US_111_USD, true), AddPrice.ofVariantId(masterVariantId, DRAFT_US_111_USD, true), AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_EUR, true), @@ -373,18 +412,23 @@ void withNonEmptyNewWithDuplicatesPricesButEmptyExistingPrices_ShouldNotSyncPric AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_USD, true), AddPrice.ofVariantId(masterVariantId, DRAFT_UK_111_GBP, true)); - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), emptyList()); - } - - @Test - void sync_withNullNewPrices_ShouldRemoveAllPrices() { - // Preparation - final List oldPrices = asList( + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), emptyList()); + } + + @Test + void sync_withNullNewPrices_ShouldRemoveAllPrices() { + // Preparation + final List oldPrices = + asList( DRAFT_US_111_USD, DRAFT_DE_111_EUR, DRAFT_DE_111_EUR_01_02, @@ -392,195 +436,234 @@ void sync_withNullNewPrices_ShouldRemoveAllPrices() { DRAFT_DE_111_USD, DRAFT_UK_111_GBP); - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, oldPrices)) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, null)) .key("bar") .build(); - - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - - final RemovePrice[] expectedActions = product.getMasterData().getStaged().getMasterVariant().getPrices() - .stream() - .map(Price::getId) - .map(id -> RemovePrice.of(id, true)) - .toArray(RemovePrice[]::new); - assertThat(updateActionsFromSync).containsExactlyInAnyOrder(expectedActions); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), emptyList()); - } - - @Test - void sync_withSomeChangedMatchingPrices_ShouldAddAndRemovePrices() { - // Preparation - final List oldPrices = asList( - DRAFT_DE_111_EUR, - DRAFT_UK_111_GBP, - DRAFT_DE_111_EUR_03_04, - DRAFT_DE_111_EUR_01_02); - - final PriceDraft price1WithCustomerGroupWithKey = getPriceDraft(BigDecimal.valueOf(222), EUR, - DE, "cust1", null, null, null, null); - final PriceDraft price2WithCustomerGroupWithKey = getPriceDraft(BigDecimal.valueOf(333), USD, - DE, "cust1", null, null, null, null); - - final PriceDraft price1WithCustomerGroupWithId = getPriceDraft(BigDecimal.valueOf(222), EUR, - DE, cust1.getId(), null, null, null, null); - final PriceDraft price2WithCustomerGroupWithId = getPriceDraft(BigDecimal.valueOf(333), USD, - DE, cust1.getId(), null, null, null, null); - - final List newPrices = asList( + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + final RemovePrice[] expectedActions = + product.getMasterData().getStaged().getMasterVariant().getPrices().stream() + .map(Price::getId) + .map(id -> RemovePrice.of(id, true)) + .toArray(RemovePrice[]::new); + assertThat(updateActionsFromSync).containsExactlyInAnyOrder(expectedActions); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), emptyList()); + } + + @Test + void sync_withSomeChangedMatchingPrices_ShouldAddAndRemovePrices() { + // Preparation + final List oldPrices = + asList(DRAFT_DE_111_EUR, DRAFT_UK_111_GBP, DRAFT_DE_111_EUR_03_04, DRAFT_DE_111_EUR_01_02); + + final PriceDraft price1WithCustomerGroupWithKey = + getPriceDraft(BigDecimal.valueOf(222), EUR, DE, "cust1", null, null, null, null); + final PriceDraft price2WithCustomerGroupWithKey = + getPriceDraft(BigDecimal.valueOf(333), USD, DE, "cust1", null, null, null, null); + + final PriceDraft price1WithCustomerGroupWithId = + getPriceDraft(BigDecimal.valueOf(222), EUR, DE, cust1.getId(), null, null, null, null); + final PriceDraft price2WithCustomerGroupWithId = + getPriceDraft(BigDecimal.valueOf(333), USD, DE, cust1.getId(), null, null, null, null); + + final List newPrices = + asList( DRAFT_DE_111_EUR, DRAFT_UK_111_GBP, price1WithCustomerGroupWithKey, price2WithCustomerGroupWithKey); - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, oldPrices)) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, newPrices)) .key("bar") .build(); - - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - - final Integer masterVariantId = product.getMasterData().getStaged().getMasterVariant().getId(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof RemovePrice) - .hasSize(2); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof AddPrice) - .hasSize(2) - .containsExactlyInAnyOrder( - AddPrice.ofVariantId(masterVariantId, price1WithCustomerGroupWithId, true), - AddPrice.ofVariantId(masterVariantId, price2WithCustomerGroupWithId, - true)); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - - final List newPricesWithIds = asList( + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + + final Integer masterVariantId = product.getMasterData().getStaged().getMasterVariant().getId(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof RemovePrice) + .hasSize(2); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof AddPrice) + .hasSize(2) + .containsExactlyInAnyOrder( + AddPrice.ofVariantId(masterVariantId, price1WithCustomerGroupWithId, true), + AddPrice.ofVariantId(masterVariantId, price2WithCustomerGroupWithId, true)); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + + final List newPricesWithIds = + asList( DRAFT_DE_111_EUR, DRAFT_UK_111_GBP, price1WithCustomerGroupWithId, price2WithCustomerGroupWithId); - assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), newPricesWithIds); - } + assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), newPricesWithIds); + } - @Test - void withMixedCasesOfPriceMatches_ShouldBuildActions() { - // Preparation - createExistingProductWithPrices(); - final ProductDraft newProductDraft = createProductDraftWithNewPrices(); + @Test + void withMixedCasesOfPriceMatches_ShouldBuildActions() { + // Preparation + createExistingProductWithPrices(); + final ProductDraft newProductDraft = createProductDraftWithNewPrices(); + final ObjectNode lTextWithEnDe = + JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red"); - final ObjectNode lTextWithEnDe = JsonNodeFactory.instance.objectNode() - .put("de", "rot") - .put("en", "red"); + final PriceDraft de222EurCust1Ofid = + PriceDraftBuilder.of(DRAFT_DE_222_EUR_CUST1).customerGroup(cust1).build(); - final PriceDraft de222EurCust1Ofid = PriceDraftBuilder.of(DRAFT_DE_222_EUR_CUST1) - .customerGroup(cust1) - .build(); + final PriceDraft de333UsdCust1Ofid = + PriceDraftBuilder.of(DRAFT_DE_333_USD_CUST1).customerGroup(cust1).build(); - final PriceDraft de333UsdCust1Ofid = PriceDraftBuilder.of(DRAFT_DE_333_USD_CUST1) - .customerGroup(cust1) - .build(); + final PriceDraft us666Usd0102Cust2OfId = + PriceDraftBuilder.of(DRAFT_US_666_USD_CUST2_01_02).customerGroup(cust2).build(); - final PriceDraft us666Usd0102Cust2OfId = PriceDraftBuilder.of(DRAFT_US_666_USD_CUST2_01_02) - .customerGroup(cust2) - .build(); - - final CustomFieldsDraft customType1WithEnDeOfId = CustomFieldsDraft.ofTypeIdAndJson(customType1.getId(), + final CustomFieldsDraft customType1WithEnDeOfId = + CustomFieldsDraft.ofTypeIdAndJson( + customType1.getId(), createCustomFieldsJsonMap(LOCALISED_STRING_CUSTOM_FIELD_NAME, lTextWithEnDe)); - final PriceDraft withChannel1CustomType1WithEnDeOfId = getPriceDraft(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getId(), customType1WithEnDeOfId); - final PriceDraft withChannel2CustomType1WithEnDeOfId = getPriceDraft(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel2.getId(), customType1WithEnDeOfId); - - - - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - - final Integer masterVariantId = product.getMasterData().getStaged().getMasterVariant().getId(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof RemovePrice) - .hasSize(5); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof ChangePrice) - .hasSize(3); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof SetProductPriceCustomType) - .hasSize(1); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof SetProductPriceCustomField) - .hasSize(1); - - assertThat(updateActionsFromSync) - .filteredOn(action -> action instanceof AddPrice) - .hasSize(9) - .containsExactlyInAnyOrder( - AddPrice.ofVariantId(masterVariantId, de222EurCust1Ofid, true), - AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_EUR_01_02, true), - AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_EUR_03_04, true), - AddPrice.ofVariantId(masterVariantId, de333UsdCust1Ofid, true), - AddPrice.ofVariantId(masterVariantId, DRAFT_UK_999_GBP, true), - AddPrice.ofVariantId(masterVariantId, us666Usd0102Cust2OfId, true), - AddPrice.ofVariantId(masterVariantId, DRAFT_FR_888_EUR_01_03, true), - AddPrice.ofVariantId(masterVariantId, DRAFT_FR_999_EUR_03_06, true), - AddPrice.ofVariantId(masterVariantId, DRAFT_NE_777_EUR_05_07, true)); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - - final List newPricesWithReferenceIds = asList( + final PriceDraft withChannel1CustomType1WithEnDeOfId = + getPriceDraft( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getId(), + customType1WithEnDeOfId); + final PriceDraft withChannel2CustomType1WithEnDeOfId = + getPriceDraft( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel2.getId(), + customType1WithEnDeOfId); + + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + + final Integer masterVariantId = product.getMasterData().getStaged().getMasterVariant().getId(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof RemovePrice) + .hasSize(5); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof ChangePrice) + .hasSize(3); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof SetProductPriceCustomType) + .hasSize(1); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof SetProductPriceCustomField) + .hasSize(1); + + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof AddPrice) + .hasSize(9) + .containsExactlyInAnyOrder( + AddPrice.ofVariantId(masterVariantId, de222EurCust1Ofid, true), + AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_EUR_01_02, true), + AddPrice.ofVariantId(masterVariantId, DRAFT_DE_111_EUR_03_04, true), + AddPrice.ofVariantId(masterVariantId, de333UsdCust1Ofid, true), + AddPrice.ofVariantId(masterVariantId, DRAFT_UK_999_GBP, true), + AddPrice.ofVariantId(masterVariantId, us666Usd0102Cust2OfId, true), + AddPrice.ofVariantId(masterVariantId, DRAFT_FR_888_EUR_01_03, true), + AddPrice.ofVariantId(masterVariantId, DRAFT_FR_999_EUR_03_06, true), + AddPrice.ofVariantId(masterVariantId, DRAFT_NE_777_EUR_05_07, true)); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + + final List newPricesWithReferenceIds = + asList( DRAFT_DE_111_EUR, de222EurCust1Ofid, DRAFT_DE_111_EUR_01_02, @@ -596,308 +679,428 @@ void withMixedCasesOfPriceMatches_ShouldBuildActions() { DRAFT_FR_999_EUR_03_06, DRAFT_NE_777_EUR_01_04, DRAFT_NE_777_EUR_05_07); - assertPricesAreEqual(productProjection.getMasterVariant().getPrices(), newPricesWithReferenceIds); - } - - @Test - void sync_WithEmptySetNewCustomFields_ShouldCorrectlyUpdateCustomFields() { - // preparation - final ArrayNode emptySet = JsonNodeFactory.instance.arrayNode(); - final Map customFieldsJsonMap = - createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); - final ArrayNode nonEmptySet = JsonNodeFactory.instance.arrayNode(); - nonEmptySet.add("foo"); - customFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptySet); - - - final CustomFieldsDraft customType1WithSet = CustomFieldsDraft.ofTypeIdAndJson(customType1.getId(), - customFieldsJsonMap); - - final PriceDraft withChannel1CustomType1WithSet = getPriceDraft(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getId(), customType1WithSet); - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), - createVariantDraft("foo", null, - singletonList(withChannel1CustomType1WithSet))) + assertPricesAreEqual( + productProjection.getMasterVariant().getPrices(), newPricesWithReferenceIds); + } + + @Test + void sync_WithEmptySetNewCustomFields_ShouldCorrectlyUpdateCustomFields() { + // preparation + final ArrayNode emptySet = JsonNodeFactory.instance.arrayNode(); + final Map customFieldsJsonMap = + createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); + final ArrayNode nonEmptySet = JsonNodeFactory.instance.arrayNode(); + nonEmptySet.add("foo"); + customFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptySet); + + final CustomFieldsDraft customType1WithSet = + CustomFieldsDraft.ofTypeIdAndJson(customType1.getId(), customFieldsJsonMap); + + final PriceDraft withChannel1CustomType1WithSet = + getPriceDraft( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getId(), + customType1WithSet); + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), + createVariantDraft("foo", null, singletonList(withChannel1CustomType1WithSet))) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - - final Map newCustomFieldsJsonMap = - createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); - newCustomFieldsJsonMap.put(NULL_SET_CUSTOM_FIELD_NAME, emptySet); - newCustomFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, emptySet); - newCustomFieldsJsonMap.put(NULL_NODE_SET_CUSTOM_FIELD_NAME, emptySet); - - - final CustomFieldsDraft customType1WithEmptySet = CustomFieldsDraft.ofTypeKeyAndJson(customType1.getKey(), - newCustomFieldsJsonMap); - final PriceDraft withChannel1CustomType1WithNullSet = getPriceDraftWithChannelKey(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getKey(), customType1WithEmptySet); - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), - createVariantDraft("foo", null, - singletonList(withChannel1CustomType1WithNullSet))) + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + + final Map newCustomFieldsJsonMap = + createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); + newCustomFieldsJsonMap.put(NULL_SET_CUSTOM_FIELD_NAME, emptySet); + newCustomFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, emptySet); + newCustomFieldsJsonMap.put(NULL_NODE_SET_CUSTOM_FIELD_NAME, emptySet); + + final CustomFieldsDraft customType1WithEmptySet = + CustomFieldsDraft.ofTypeKeyAndJson(customType1.getKey(), newCustomFieldsJsonMap); + final PriceDraft withChannel1CustomType1WithNullSet = + getPriceDraftWithChannelKey( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getKey(), + customType1WithEmptySet); + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), + createVariantDraft("foo", null, singletonList(withChannel1CustomType1WithNullSet))) .key("bar") .build(); - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof SetProductPriceCustomField) - .hasSize(3); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - - final List prices = productProjection.getMasterVariant().getPrices(); - for (Price price : prices) { - assertThat(price.getCustom().getFieldAsStringSet(EMPTY_SET_CUSTOM_FIELD_NAME)).isEmpty(); - assertThat(price.getCustom().getFieldAsStringSet(NULL_SET_CUSTOM_FIELD_NAME)).isEmpty(); - assertThat(price.getCustom().getFieldAsStringSet(NULL_NODE_SET_CUSTOM_FIELD_NAME)).isEmpty(); - assertThat(price.getCustom().getFieldAsStringSet(NON_EMPTY_SEY_CUSTOM_FIELD_NAME)).isEmpty(); - } + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof SetProductPriceCustomField) + .hasSize(3); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + + final List prices = productProjection.getMasterVariant().getPrices(); + for (Price price : prices) { + assertThat(price.getCustom().getFieldAsStringSet(EMPTY_SET_CUSTOM_FIELD_NAME)).isEmpty(); + assertThat(price.getCustom().getFieldAsStringSet(NULL_SET_CUSTOM_FIELD_NAME)).isEmpty(); + assertThat(price.getCustom().getFieldAsStringSet(NULL_NODE_SET_CUSTOM_FIELD_NAME)).isEmpty(); + assertThat(price.getCustom().getFieldAsStringSet(NON_EMPTY_SEY_CUSTOM_FIELD_NAME)).isEmpty(); } - - @Test - void sync_WithNonEmptySetNewCustomFields_ShouldCorrectlyUpdateCustomFields() { - // preparation - final ArrayNode emptySet = JsonNodeFactory.instance.arrayNode(); - final Map customFieldsJsonMap = - createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); - final ArrayNode nonEmptySet = JsonNodeFactory.instance.arrayNode(); - nonEmptySet.add("foo"); - customFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptySet); - - - final CustomFieldsDraft customType1WithSet = CustomFieldsDraft.ofTypeIdAndJson(customType1.getId(), - customFieldsJsonMap); - - final PriceDraft withChannel1CustomType1WithSet = getPriceDraft(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getId(), customType1WithSet); - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), - createVariantDraft("foo", null, - singletonList(withChannel1CustomType1WithSet))) + } + + @Test + void sync_WithNonEmptySetNewCustomFields_ShouldCorrectlyUpdateCustomFields() { + // preparation + final ArrayNode emptySet = JsonNodeFactory.instance.arrayNode(); + final Map customFieldsJsonMap = + createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); + final ArrayNode nonEmptySet = JsonNodeFactory.instance.arrayNode(); + nonEmptySet.add("foo"); + customFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptySet); + + final CustomFieldsDraft customType1WithSet = + CustomFieldsDraft.ofTypeIdAndJson(customType1.getId(), customFieldsJsonMap); + + final PriceDraft withChannel1CustomType1WithSet = + getPriceDraft( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getId(), + customType1WithSet); + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), + createVariantDraft("foo", null, singletonList(withChannel1CustomType1WithSet))) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - - final ArrayNode nonEmptyNewSet = JsonNodeFactory.instance.arrayNode(); - nonEmptyNewSet.add("bar"); - final Map newCustomFieldsJsonMap = - createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, nonEmptyNewSet); - newCustomFieldsJsonMap.put(NULL_SET_CUSTOM_FIELD_NAME, nonEmptyNewSet); - newCustomFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptyNewSet); - newCustomFieldsJsonMap.put(NULL_NODE_SET_CUSTOM_FIELD_NAME, nonEmptyNewSet); - - - final CustomFieldsDraft customType1WithEmptySet = CustomFieldsDraft.ofTypeKeyAndJson(customType1.getKey(), - newCustomFieldsJsonMap); - final PriceDraft withChannel1CustomType1WithNullSet = getPriceDraftWithChannelKey(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getKey(), customType1WithEmptySet); - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), - createVariantDraft("foo", null, - singletonList(withChannel1CustomType1WithNullSet))) + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + + final ArrayNode nonEmptyNewSet = JsonNodeFactory.instance.arrayNode(); + nonEmptyNewSet.add("bar"); + final Map newCustomFieldsJsonMap = + createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, nonEmptyNewSet); + newCustomFieldsJsonMap.put(NULL_SET_CUSTOM_FIELD_NAME, nonEmptyNewSet); + newCustomFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptyNewSet); + newCustomFieldsJsonMap.put(NULL_NODE_SET_CUSTOM_FIELD_NAME, nonEmptyNewSet); + + final CustomFieldsDraft customType1WithEmptySet = + CustomFieldsDraft.ofTypeKeyAndJson(customType1.getKey(), newCustomFieldsJsonMap); + final PriceDraft withChannel1CustomType1WithNullSet = + getPriceDraftWithChannelKey( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getKey(), + customType1WithEmptySet); + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), + createVariantDraft("foo", null, singletonList(withChannel1CustomType1WithNullSet))) .key("bar") .build(); - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof SetProductPriceCustomField) - .hasSize(4); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - - final List prices = productProjection.getMasterVariant().getPrices(); - for (Price price : prices) { - assertThat(price.getCustom().getFieldAsStringSet(EMPTY_SET_CUSTOM_FIELD_NAME)).containsOnly("bar"); - assertThat(price.getCustom().getFieldAsStringSet(NULL_SET_CUSTOM_FIELD_NAME)).containsOnly("bar"); - assertThat(price.getCustom().getFieldAsStringSet(NULL_NODE_SET_CUSTOM_FIELD_NAME)).containsOnly("bar"); - assertThat(price.getCustom().getFieldAsStringSet(NON_EMPTY_SEY_CUSTOM_FIELD_NAME)).containsOnly("bar"); - } + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof SetProductPriceCustomField) + .hasSize(4); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + + final List prices = productProjection.getMasterVariant().getPrices(); + for (Price price : prices) { + assertThat(price.getCustom().getFieldAsStringSet(EMPTY_SET_CUSTOM_FIELD_NAME)) + .containsOnly("bar"); + assertThat(price.getCustom().getFieldAsStringSet(NULL_SET_CUSTOM_FIELD_NAME)) + .containsOnly("bar"); + assertThat(price.getCustom().getFieldAsStringSet(NULL_NODE_SET_CUSTOM_FIELD_NAME)) + .containsOnly("bar"); + assertThat(price.getCustom().getFieldAsStringSet(NON_EMPTY_SEY_CUSTOM_FIELD_NAME)) + .containsOnly("bar"); } - - @Test - void sync_WithNullJsonNodeNewCustomFields_ShouldCorrectlyUpdateCustomFields() { - // preparation - final ArrayNode emptySet = JsonNodeFactory.instance.arrayNode(); - final Map customFieldsJsonMap = - createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); - final ArrayNode nonEmptySet = JsonNodeFactory.instance.arrayNode(); - nonEmptySet.add("foo"); - customFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptySet); - - - final CustomFieldsDraft customType1WithSet = CustomFieldsDraft.ofTypeIdAndJson(customType1.getId(), - customFieldsJsonMap); - - final PriceDraft withChannel1CustomType1WithSet = getPriceDraft(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getId(), customType1WithSet); - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), - createVariantDraft("foo", null, - singletonList(withChannel1CustomType1WithSet))) + } + + @Test + void sync_WithNullJsonNodeNewCustomFields_ShouldCorrectlyUpdateCustomFields() { + // preparation + final ArrayNode emptySet = JsonNodeFactory.instance.arrayNode(); + final Map customFieldsJsonMap = + createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); + final ArrayNode nonEmptySet = JsonNodeFactory.instance.arrayNode(); + nonEmptySet.add("foo"); + customFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptySet); + + final CustomFieldsDraft customType1WithSet = + CustomFieldsDraft.ofTypeIdAndJson(customType1.getId(), customFieldsJsonMap); + + final PriceDraft withChannel1CustomType1WithSet = + getPriceDraft( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getId(), + customType1WithSet); + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), + createVariantDraft("foo", null, singletonList(withChannel1CustomType1WithSet))) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - - final NullNode nullJsonNode = JsonNodeFactory.instance.nullNode(); - final Map newCustomFieldsJsonMap = createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, - nullJsonNode); - newCustomFieldsJsonMap.put(NULL_SET_CUSTOM_FIELD_NAME, nullJsonNode); - newCustomFieldsJsonMap.put(NULL_NODE_SET_CUSTOM_FIELD_NAME, nullJsonNode); - newCustomFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nullJsonNode); - - - final CustomFieldsDraft customType1WithEmptySet = CustomFieldsDraft.ofTypeKeyAndJson(customType1.getKey(), - newCustomFieldsJsonMap); - final PriceDraft withChannel1CustomType1WithNullSet = getPriceDraftWithChannelKey(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getKey(), customType1WithEmptySet); - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), - createVariantDraft("foo", null, - singletonList(withChannel1CustomType1WithNullSet))) + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + + final NullNode nullJsonNode = JsonNodeFactory.instance.nullNode(); + final Map newCustomFieldsJsonMap = + createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, nullJsonNode); + newCustomFieldsJsonMap.put(NULL_SET_CUSTOM_FIELD_NAME, nullJsonNode); + newCustomFieldsJsonMap.put(NULL_NODE_SET_CUSTOM_FIELD_NAME, nullJsonNode); + newCustomFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nullJsonNode); + + final CustomFieldsDraft customType1WithEmptySet = + CustomFieldsDraft.ofTypeKeyAndJson(customType1.getKey(), newCustomFieldsJsonMap); + final PriceDraft withChannel1CustomType1WithNullSet = + getPriceDraftWithChannelKey( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getKey(), + customType1WithEmptySet); + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), + createVariantDraft("foo", null, singletonList(withChannel1CustomType1WithNullSet))) .key("bar") .build(); - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof SetProductPriceCustomField) - .hasSize(2); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - - final List prices = productProjection.getMasterVariant().getPrices(); - for (Price price : prices) { - assertThat(price.getCustom().getFieldAsStringSet(EMPTY_SET_CUSTOM_FIELD_NAME)).isNull(); - assertThat(price.getCustom().getFieldAsStringSet(NULL_SET_CUSTOM_FIELD_NAME)).isNull(); - assertThat(price.getCustom().getFieldAsStringSet(NULL_NODE_SET_CUSTOM_FIELD_NAME)).isNull(); - assertThat(price.getCustom().getFieldAsStringSet(NON_EMPTY_SEY_CUSTOM_FIELD_NAME)).isNull(); - } + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof SetProductPriceCustomField) + .hasSize(2); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + + final List prices = productProjection.getMasterVariant().getPrices(); + for (Price price : prices) { + assertThat(price.getCustom().getFieldAsStringSet(EMPTY_SET_CUSTOM_FIELD_NAME)).isNull(); + assertThat(price.getCustom().getFieldAsStringSet(NULL_SET_CUSTOM_FIELD_NAME)).isNull(); + assertThat(price.getCustom().getFieldAsStringSet(NULL_NODE_SET_CUSTOM_FIELD_NAME)).isNull(); + assertThat(price.getCustom().getFieldAsStringSet(NON_EMPTY_SEY_CUSTOM_FIELD_NAME)).isNull(); } - - @Test - void sync_WithNullNewCustomFields_ShouldCorrectlyUpdateCustomFields() { - // preparation - final ArrayNode emptySet = JsonNodeFactory.instance.arrayNode(); - final Map customFieldsJsonMap = - createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); - final ArrayNode nonEmptySet = JsonNodeFactory.instance.arrayNode(); - nonEmptySet.add("foo"); - customFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptySet); - - - final CustomFieldsDraft customType1WithSet = CustomFieldsDraft.ofTypeIdAndJson(customType1.getId(), - customFieldsJsonMap); - - final PriceDraft withChannel1CustomType1WithSet = getPriceDraft(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getId(), customType1WithSet); - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), - createVariantDraft("foo", null, - singletonList(withChannel1CustomType1WithSet))) + } + + @Test + void sync_WithNullNewCustomFields_ShouldCorrectlyUpdateCustomFields() { + // preparation + final ArrayNode emptySet = JsonNodeFactory.instance.arrayNode(); + final Map customFieldsJsonMap = + createCustomFieldsJsonMap(EMPTY_SET_CUSTOM_FIELD_NAME, emptySet); + final ArrayNode nonEmptySet = JsonNodeFactory.instance.arrayNode(); + nonEmptySet.add("foo"); + customFieldsJsonMap.put(NON_EMPTY_SEY_CUSTOM_FIELD_NAME, nonEmptySet); + + final CustomFieldsDraft customType1WithSet = + CustomFieldsDraft.ofTypeIdAndJson(customType1.getId(), customFieldsJsonMap); + + final PriceDraft withChannel1CustomType1WithSet = + getPriceDraft( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getId(), + customType1WithSet); + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), + createVariantDraft("foo", null, singletonList(withChannel1CustomType1WithSet))) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - - final CustomFieldsDraft customType1WithEmptySet = CustomFieldsDraft.ofTypeKeyAndJson(customType1.getKey(), - new HashMap<>()); - final PriceDraft withChannel1CustomType1WithNullSet = getPriceDraftWithChannelKey(BigDecimal.valueOf(100), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getKey(), customType1WithEmptySet); - - final ProductDraft newProductDraft = ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), - createVariantDraft("foo", null, - singletonList(withChannel1CustomType1WithNullSet))) + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + + final CustomFieldsDraft customType1WithEmptySet = + CustomFieldsDraft.ofTypeKeyAndJson(customType1.getKey(), new HashMap<>()); + final PriceDraft withChannel1CustomType1WithNullSet = + getPriceDraftWithChannelKey( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getKey(), + customType1WithEmptySet); + + final ProductDraft newProductDraft = + ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), + createVariantDraft("foo", null, singletonList(withChannel1CustomType1WithNullSet))) .key("bar") .build(); - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(singletonList(newProductDraft))); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(updateActionsFromSync).filteredOn(action -> action instanceof SetProductPriceCustomField) - .hasSize(2); - - final ProductProjection productProjection = CTP_TARGET_CLIENT - .execute(ProductProjectionByKeyGet.of(newProductDraft.getKey(), ProductProjectionType.STAGED)) - .toCompletableFuture().join(); - - assertThat(productProjection).isNotNull(); - - final List prices = productProjection.getMasterVariant().getPrices(); - for (Price price : prices) { - assertThat(price.getCustom().getFieldAsStringSet(EMPTY_SET_CUSTOM_FIELD_NAME)).isNull(); - assertThat(price.getCustom().getFieldAsStringSet(NULL_SET_CUSTOM_FIELD_NAME)).isNull(); - assertThat(price.getCustom().getFieldAsStringSet(NULL_NODE_SET_CUSTOM_FIELD_NAME)).isNull(); - assertThat(price.getCustom().getFieldAsStringSet(NON_EMPTY_SEY_CUSTOM_FIELD_NAME)).isNull(); - } + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(singletonList(newProductDraft))); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(updateActionsFromSync) + .filteredOn(action -> action instanceof SetProductPriceCustomField) + .hasSize(2); + + final ProductProjection productProjection = + CTP_TARGET_CLIENT + .execute( + ProductProjectionByKeyGet.of( + newProductDraft.getKey(), ProductProjectionType.STAGED)) + .toCompletableFuture() + .join(); + + assertThat(productProjection).isNotNull(); + + final List prices = productProjection.getMasterVariant().getPrices(); + for (Price price : prices) { + assertThat(price.getCustom().getFieldAsStringSet(EMPTY_SET_CUSTOM_FIELD_NAME)).isNull(); + assertThat(price.getCustom().getFieldAsStringSet(NULL_SET_CUSTOM_FIELD_NAME)).isNull(); + assertThat(price.getCustom().getFieldAsStringSet(NULL_NODE_SET_CUSTOM_FIELD_NAME)).isNull(); + assertThat(price.getCustom().getFieldAsStringSet(NON_EMPTY_SEY_CUSTOM_FIELD_NAME)).isNull(); } - - /** - * Creates a productDraft with a master variant containing the following priceDrafts: - *

    - *
  • DE_111_EUR
  • - *
  • DE_222_EUR_CUST1
  • - *
  • DE_111_EUR_01_02
  • - *
  • DE_111_EUR_03_04
  • - *
  • DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELD{en, de}
  • - *
  • DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELD{en, de}
  • - *
  • DE_333_USD_CUST1
  • - *
  • DE_22_USD
  • - *
  • UK_111_GBP_01_02
  • - *
  • UK_999_GBP
  • - *
  • US_666_USD_CUST2_01_02,
  • - *
  • FR_888_EUR_01_03
  • - *
  • FR_999_EUR_03_06
  • - *
  • NE_777_EUR_01_04
  • - *
  • NE_777_EUR_05_07
  • - *
- */ - private ProductDraft createProductDraftWithNewPrices() { - final ObjectNode lTextWithEnDe = JsonNodeFactory.instance.objectNode() - .put("de", "rot") - .put("en", "red"); - - - final CustomFieldsDraft customType1WithEnDeOfKey = CustomFieldsDraft.ofTypeKeyAndJson("customType1", + } + + /** + * Creates a productDraft with a master variant containing the following priceDrafts: + * + *
    + *
  • DE_111_EUR + *
  • DE_222_EUR_CUST1 + *
  • DE_111_EUR_01_02 + *
  • DE_111_EUR_03_04 + *
  • DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELD{en, de} + *
  • DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELD{en, de} + *
  • DE_333_USD_CUST1 + *
  • DE_22_USD + *
  • UK_111_GBP_01_02 + *
  • UK_999_GBP + *
  • US_666_USD_CUST2_01_02, + *
  • FR_888_EUR_01_03 + *
  • FR_999_EUR_03_06 + *
  • NE_777_EUR_01_04 + *
  • NE_777_EUR_05_07 + *
+ */ + private ProductDraft createProductDraftWithNewPrices() { + final ObjectNode lTextWithEnDe = + JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red"); + + final CustomFieldsDraft customType1WithEnDeOfKey = + CustomFieldsDraft.ofTypeKeyAndJson( + "customType1", createCustomFieldsJsonMap(LOCALISED_STRING_CUSTOM_FIELD_NAME, lTextWithEnDe)); - final PriceDraft withChannel1CustomType1WithEnDeOfKey = getPriceDraftWithChannelKey(BigDecimal.valueOf(100), - EUR, DE, null, byMonth(1), byMonth(2), "channel1", customType1WithEnDeOfKey); - final PriceDraft withChannel2CustomType1WithEnDeOfKey = getPriceDraftWithChannelKey(BigDecimal.valueOf(100), - EUR, DE, null, byMonth(1), byMonth(2), "channel2", customType1WithEnDeOfKey); - - final List newPrices = asList( + final PriceDraft withChannel1CustomType1WithEnDeOfKey = + getPriceDraftWithChannelKey( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + "channel1", + customType1WithEnDeOfKey); + final PriceDraft withChannel2CustomType1WithEnDeOfKey = + getPriceDraftWithChannelKey( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + "channel2", + customType1WithEnDeOfKey); + + final List newPrices = + asList( DRAFT_DE_111_EUR, DRAFT_DE_222_EUR_CUST1, DRAFT_DE_111_EUR_01_02, @@ -914,59 +1117,77 @@ private ProductDraft createProductDraftWithNewPrices() { DRAFT_NE_777_EUR_01_04, DRAFT_NE_777_EUR_05_07); - return ProductDraftBuilder - .of(referenceOfId(productType.getKey()), ofEnglish("foo"), ofEnglish("bar"), - createVariantDraft("foo", null, newPrices)) - .key("bar") - .build(); - } - - /** - * Creates a product with a master variant containing the following prices: - *
    - *
  • DE_111_EUR
  • - *
  • DE_345_EUR_CUST2
  • - *
  • DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELD{en, de, it}
  • - *
  • DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE2_CUSTOMFIELD{en, de}
  • - *
  • DE_22_USD
  • - *
  • UK_111_GBP_01_02
  • - *
  • UK_111_GBP_02_03
  • - *
  • UK_333_GBP_03_05
  • - *
  • FR_777_EUR_01_04
  • - *
  • NE_123_EUR_01_04
  • - *
  • NE_321_EUR_04_06
  • - *
- */ - private void createExistingProductWithPrices() { - - final ObjectNode lTextWithEnDeIt = JsonNodeFactory.instance.objectNode() - .put("de", "rot") - .put("en", "red") - .put("it", "rosso"); - - - final ObjectNode lTextWithEnDe = JsonNodeFactory.instance.objectNode() - .put("de", "rot") - .put("en", "red"); - - - final CustomFieldsDraft customType1WithEnDeItOfId = CustomFieldsDraft - .ofTypeIdAndJson(ProductSyncWithPricesIT.customType1.getId(), - createCustomFieldsJsonMap(LOCALISED_STRING_CUSTOM_FIELD_NAME, lTextWithEnDeIt)); - - final CustomFieldsDraft customType2WithEnDeOfId = CustomFieldsDraft.ofTypeIdAndJson(customType2.getId(), + return ProductDraftBuilder.of( + referenceOfId(productType.getKey()), + ofEnglish("foo"), + ofEnglish("bar"), + createVariantDraft("foo", null, newPrices)) + .key("bar") + .build(); + } + + /** + * Creates a product with a master variant containing the following prices: + * + *
    + *
  • DE_111_EUR + *
  • DE_345_EUR_CUST2 + *
  • DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELD{en, de, it} + *
  • DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE2_CUSTOMFIELD{en, de} + *
  • DE_22_USD + *
  • UK_111_GBP_01_02 + *
  • UK_111_GBP_02_03 + *
  • UK_333_GBP_03_05 + *
  • FR_777_EUR_01_04 + *
  • NE_123_EUR_01_04 + *
  • NE_321_EUR_04_06 + *
+ */ + private void createExistingProductWithPrices() { + + final ObjectNode lTextWithEnDeIt = + JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red").put("it", "rosso"); + + final ObjectNode lTextWithEnDe = + JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red"); + + final CustomFieldsDraft customType1WithEnDeItOfId = + CustomFieldsDraft.ofTypeIdAndJson( + ProductSyncWithPricesIT.customType1.getId(), + createCustomFieldsJsonMap(LOCALISED_STRING_CUSTOM_FIELD_NAME, lTextWithEnDeIt)); + + final CustomFieldsDraft customType2WithEnDeOfId = + CustomFieldsDraft.ofTypeIdAndJson( + customType2.getId(), createCustomFieldsJsonMap(LOCALISED_STRING_CUSTOM_FIELD_NAME, lTextWithEnDe)); - final PriceDraft de222Eur0102Channel1Ct1DeEnItOfId = getPriceDraft(BigDecimal.valueOf(222), EUR, - DE, null, byMonth(1), byMonth(2), channel1.getId(), customType1WithEnDeItOfId); - - final PriceDraft de222Eur0102Channel2Ct2DeEnOfId = getPriceDraft(BigDecimal.valueOf(222), EUR, - DE, null, byMonth(1), byMonth(2), channel2.getId(), customType2WithEnDeOfId); - - final PriceDraft de345EurCust2OfId = getPriceDraft(BigDecimal.valueOf(345), EUR, - DE, cust2.getId(), null, null, null, null); - - final List oldPrices = asList( + final PriceDraft de222Eur0102Channel1Ct1DeEnItOfId = + getPriceDraft( + BigDecimal.valueOf(222), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel1.getId(), + customType1WithEnDeItOfId); + + final PriceDraft de222Eur0102Channel2Ct2DeEnOfId = + getPriceDraft( + BigDecimal.valueOf(222), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + channel2.getId(), + customType2WithEnDeOfId); + + final PriceDraft de345EurCust2OfId = + getPriceDraft(BigDecimal.valueOf(345), EUR, DE, cust2.getId(), null, null, null, null); + + final List oldPrices = + asList( DRAFT_DE_111_EUR, de345EurCust2OfId, de222Eur0102Channel1Ct1DeEnItOfId, @@ -977,36 +1198,38 @@ private void createExistingProductWithPrices() { DRAFT_UK_333_GBP_03_05, DRAFT_FR_777_EUR_01_04, DRAFT_NE_123_EUR_01_04, - DRAFT_NE_321_EUR_04_06 - ); + DRAFT_NE_321_EUR_04_06); - final ProductDraft existingProductDraft = ProductDraftBuilder - .of(productType.toReference(), ofEnglish("foo"), ofEnglish("bar"), + final ProductDraft existingProductDraft = + ProductDraftBuilder.of( + productType.toReference(), + ofEnglish("foo"), + ofEnglish("bar"), createVariantDraft("foo", null, oldPrices)) .key("bar") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); - } - - /** - * Asserts that a list of {@link Asset} and a list of {@link AssetDraft} have the same ordering of prices (prices - * are matched by key). It asserts that the matching assets have the same name, description, custom fields, tags, - * and asset sources. - * - *

TODO: This should be refactored into Asset asserts helpers. GITHUB ISSUE#261 - * - * @param prices the list of prices to compare to the list of price drafts. - * @param priceDrafts the list of price drafts to compare to the list of prices. - */ - static void assertPricesAreEqual(@Nonnull final List prices, - @Nonnull final List priceDrafts) { - - final Map priceMap = collectionToMap(prices, PriceCompositeId::of); - final PriceCompositeId[] priceCompositeIds = priceDrafts.stream() - .map(PriceCompositeId::of) - .toArray(PriceCompositeId[]::new); - - assertThat(priceMap).containsOnlyKeys(priceCompositeIds); - } + product = + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(existingProductDraft))); + } + + /** + * Asserts that a list of {@link Asset} and a list of {@link AssetDraft} have the same ordering of + * prices (prices are matched by key). It asserts that the matching assets have the same name, + * description, custom fields, tags, and asset sources. + * + *

TODO: This should be refactored into Asset asserts helpers. GITHUB ISSUE#261 + * + * @param prices the list of prices to compare to the list of price drafts. + * @param priceDrafts the list of price drafts to compare to the list of prices. + */ + static void assertPricesAreEqual( + @Nonnull final List prices, @Nonnull final List priceDrafts) { + + final Map priceMap = collectionToMap(prices, PriceCompositeId::of); + final PriceCompositeId[] priceCompositeIds = + priceDrafts.stream().map(PriceCompositeId::of).toArray(PriceCompositeId[]::new); + + assertThat(priceMap).containsOnlyKeys(priceCompositeIds); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedCategoriesIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedCategoriesIT.java index acaf98322b..625e58a651 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedCategoriesIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedCategoriesIT.java @@ -1,5 +1,17 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.TriConsumer; import com.commercetools.sync.products.ProductSync; @@ -25,479 +37,524 @@ import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; import io.sphere.sdk.products.queries.ProductByKeyGet; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncWithReferencedCategoriesIT { - private static ProductType productType; - private static Category category; - private static Category category2; - - private ProductSyncOptions syncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> actions; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - final CategoryDraft category1Draft = CategoryDraftBuilder - .of(ofEnglish("cat1-name"), ofEnglish("cat1-slug")) + private static ProductType productType; + private static Category category; + private static Category category2; + + private ProductSyncOptions syncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> actions; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + final CategoryDraft category1Draft = + CategoryDraftBuilder.of(ofEnglish("cat1-name"), ofEnglish("cat1-slug")) .key("cat1-key") .build(); - category = CTP_TARGET_CLIENT + category = + CTP_TARGET_CLIENT .execute(CategoryCreateCommand.of(category1Draft)) .toCompletableFuture() .join(); - final CategoryDraft category2Draft = CategoryDraftBuilder - .of(ofEnglish("cat2-name"), ofEnglish("cat2-slug")) + final CategoryDraft category2Draft = + CategoryDraftBuilder.of(ofEnglish("cat2-name"), ofEnglish("cat2-slug")) .key("cat2-key") .build(); - category2 = CTP_TARGET_CLIENT + category2 = + CTP_TARGET_CLIENT .execute(CategoryCreateCommand.of(category2Draft)) .toCompletableFuture() .join(); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - actions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final TriConsumer, Optional> warningCallback - = (syncException, draft, product) -> warningCallBackMessages.add(syncException.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, draft, product, updateActions) -> - collectErrors(exception.getMessage(), exception)) - .beforeUpdateCallback(this::collectActions) - .warningCallback(warningCallback) - .build(); - } - - private void collectErrors(@Nullable final String errorMessage, @Nullable final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> collectActions(@Nonnull final List> actions, - @Nonnull final ProductDraft productDraft, - @Nonnull final Product product) { - this.actions.addAll(actions); - return actions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withCategoryReferenceAsAttribute_shouldCreateProductReferencingExistingCategory() { - // preparation - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category.getKey())); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + actions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final TriConsumer, Optional> warningCallback = + (syncException, draft, product) -> warningCallBackMessages.add(syncException.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, draft, product, updateActions) -> + collectErrors(exception.getMessage(), exception)) + .beforeUpdateCallback(this::collectActions) + .warningCallback(warningCallback) + .build(); + } + + private void collectErrors( + @Nullable final String errorMessage, @Nullable final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> collectActions( + @Nonnull final List> actions, + @Nonnull final ProductDraft productDraft, + @Nonnull final Product product) { + this.actions.addAll(actions); + return actions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withCategoryReferenceAsAttribute_shouldCreateProductReferencingExistingCategory() { + // preparation + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of( + "category-reference", Reference.of(Category.referenceTypeId(), category.getKey())); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Category.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(category.getId()); - }); - } - - @Test - void sync_withSameCategoryReferenceAsAttribute_shouldNotSyncAnythingNew() { - // preparation - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category)); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(category.getId()); + }); + } + + @Test + void sync_withSameCategoryReferenceAsAttribute_shouldNotSyncAnythingNew() { + // preparation + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category)); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category.getKey())); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of( + "category-reference", Reference.of(Category.referenceTypeId(), category.getKey())); + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Category.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(category.getId()); - }); - } - - @Test - void sync_withChangedCategoryReferenceAsAttribute_shouldUpdateProductReferencingExistingCategory() { - // preparation - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category)); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(category.getId()); + }); + } + + @Test + void + sync_withChangedCategoryReferenceAsAttribute_shouldUpdateProductReferencingExistingCategory() { + // preparation + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category)); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); - final AttributeDraft newCategoryReferenceAttribute = - AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category2.getKey())); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final AttributeDraft newCategoryReferenceAttribute = + AttributeDraft.of( + "category-reference", Reference.of(Category.referenceTypeId(), category2.getKey())); + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newCategoryReferenceAttribute) .build(); - final ProductDraft newProductDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - final AttributeDraft expectedAttribute = - AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category2.getId())); - assertThat(actions).containsExactly(SetAttributeInAllVariants.of(expectedAttribute, true)); - - - final Product createdProduct = CTP_TARGET_CLIENT + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithCategoryReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + final AttributeDraft expectedAttribute = + AttributeDraft.of( + "category-reference", Reference.of(Category.referenceTypeId(), category2.getId())); + assertThat(actions).containsExactly(SetAttributeInAllVariants.of(expectedAttribute, true)); + + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Category.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(category2.getId()); - }); - } - - @Test - void sync_withNonExistingCategoryReferenceAsAttribute_ShouldFailCreatingTheProduct() { - // preparation - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), "nonExistingKey")); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(category2.getId()); + }); + } + + @Test + void sync_withNonExistingCategoryReferenceAsAttribute_ShouldFailCreatingTheProduct() { + // preparation + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of( + "category-reference", Reference.of(Category.referenceTypeId(), "nonExistingKey")); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'category-reference'"); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'category-reference'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'category-reference'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } - - @Test - void sync_withCategoryReferenceSetAsAttribute_shouldCreateProductReferencingExistingCategories() { - // preparation - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category.getKey())); - - final HashSet> references = new HashSet<>(); - references.add(Reference.of(Category.referenceTypeId(), category.getKey())); - references.add(Reference.of(Category.referenceTypeId(), category2.getKey())); - - final AttributeDraft categoryReferenceSetAttribute = - AttributeDraft.of("category-reference-set", references); - - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + .contains( + "The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'category-reference'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } + + @Test + void sync_withCategoryReferenceSetAsAttribute_shouldCreateProductReferencingExistingCategories() { + // preparation + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of( + "category-reference", Reference.of(Category.referenceTypeId(), category.getKey())); + + final HashSet> references = new HashSet<>(); + references.add(Reference.of(Category.referenceTypeId(), category.getKey())); + references.add(Reference.of(Category.referenceTypeId(), category2.getKey())); + + final AttributeDraft categoryReferenceSetAttribute = + AttributeDraft.of("category-reference-set", references); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute, categoryReferenceSetAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithCategoryReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Category.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(category.getId()); - }); - - final Optional createdCategoryReferenceSetAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(categoryReferenceSetAttribute.getName()); - - assertThat(createdCategoryReferenceSetAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode()).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) attribute.getValueAsJsonNode(); - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Category.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(category.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Category.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(category2.getId()); - }); - }); - } - - @Test - void sync_withCategoryReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { - // preparation - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("category-reference", Reference.of(Category.referenceTypeId(), category.getKey())); - - final HashSet> references = new HashSet<>(); - references.add(Reference.of(Category.referenceTypeId(), "nonExistingKey")); - references.add(Reference.of(Category.referenceTypeId(), category2.getKey())); - - final AttributeDraft categoryReferenceSetAttribute = - AttributeDraft.of("category-reference-set", references); - - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(category.getId()); + }); + + final Optional createdCategoryReferenceSetAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(categoryReferenceSetAttribute.getName()); + + assertThat(createdCategoryReferenceSetAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode()).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) attribute.getValueAsJsonNode(); + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(category.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(category2.getId()); + }); + }); + } + + @Test + void sync_withCategoryReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { + // preparation + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of( + "category-reference", Reference.of(Category.referenceTypeId(), category.getKey())); + + final HashSet> references = new HashSet<>(); + references.add(Reference.of(Category.referenceTypeId(), "nonExistingKey")); + references.add(Reference.of(Category.referenceTypeId(), category2.getKey())); + + final AttributeDraft categoryReferenceSetAttribute = + AttributeDraft.of("category-reference-set", references); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(categoryReferenceAttribute, categoryReferenceSetAttribute) .build(); - final ProductDraft productDraftWithCategoryReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithCategoryReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithCategoryReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithCategoryReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'category-reference-set'"); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'category-reference-set'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'category-reference-set'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } + .contains( + "The value '{\"typeId\":\"category\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'category-reference-set'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductTypesIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductTypesIT.java index 9c73ec18c8..b5bdd404d6 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductTypesIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductTypesIT.java @@ -1,5 +1,19 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.TriConsumer; import com.commercetools.sync.products.ProductSync; @@ -21,473 +35,517 @@ import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; import io.sphere.sdk.products.queries.ProductByKeyGet; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncWithReferencedProductTypesIT { - private static ProductType productType; - private static ProductType productType2; - - - private ProductSyncOptions syncOptions; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> actions; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - productType2 = createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - deleteAllCategories(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - actions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final TriConsumer, Optional> warningCallback - = (syncException, productDraft, product) -> warningCallBackMessages.add(syncException.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((syncException, draft, product, updateActions) - -> collectErrors(syncException.getMessage(), syncException)) - .beforeUpdateCallback(this::collectActions) - .warningCallback(warningCallback) - .build(); - } - - private void collectErrors(@Nullable final String errorMessage, @Nullable final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> collectActions(@Nonnull final List> actions, - @Nonnull final ProductDraft productDraft, - @Nonnull final Product product) { - this.actions.addAll(actions); - return actions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withProductTypeReferenceAsAttribute_shouldCreateProductReferencingExistingProductType() { - // preparation - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("productType-reference", Reference.of(ProductType.referenceTypeId(), - productType.getKey())); - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + private static ProductType productType; + private static ProductType productType2; + + private ProductSyncOptions syncOptions; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> actions; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + productType2 = createProductType(PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + deleteAllCategories(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + actions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final TriConsumer, Optional> warningCallback = + (syncException, productDraft, product) -> + warningCallBackMessages.add(syncException.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (syncException, draft, product, updateActions) -> + collectErrors(syncException.getMessage(), syncException)) + .beforeUpdateCallback(this::collectActions) + .warningCallback(warningCallback) + .build(); + } + + private void collectErrors( + @Nullable final String errorMessage, @Nullable final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> collectActions( + @Nonnull final List> actions, + @Nonnull final ProductDraft productDraft, + @Nonnull final Product product) { + this.actions.addAll(actions); + return actions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void + sync_withProductTypeReferenceAsAttribute_shouldCreateProductReferencingExistingProductType() { + // preparation + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of( + "productType-reference", + Reference.of(ProductType.referenceTypeId(), productType.getKey())); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(productType.getId()); - }); - } - - @Test - void sync_withSameProductTypeReferenceAsAttribute_shouldNotSyncAnythingNew() { - // preparation - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("productType-reference", Reference.of(ProductType.referenceTypeId(), productType)); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(productType.getId()); + }); + } + + @Test + void sync_withSameProductTypeReferenceAsAttribute_shouldNotSyncAnythingNew() { + // preparation + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of( + "productType-reference", Reference.of(ProductType.referenceTypeId(), productType)); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("productType-reference", Reference.of(ProductType.referenceTypeId(), - productType.getKey())); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); + + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of( + "productType-reference", + Reference.of(ProductType.referenceTypeId(), productType.getKey())); + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(productType.getId()); - }); - } - - @Test - void sync_withChangedProductTypeReferenceAsAttribute_shouldUpdateProductReferencingExistingProductType() { - // preparation - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("productType-reference", Reference.of(ProductType.referenceTypeId(), productType)); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(productType.getId()); + }); + } + + @Test + void + sync_withChangedProductTypeReferenceAsAttribute_shouldUpdateProductReferencingExistingProductType() { + // preparation + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of( + "productType-reference", Reference.of(ProductType.referenceTypeId(), productType)); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - - final AttributeDraft newProductTypeReferenceAttribute = - AttributeDraft.of("productType-reference", Reference.of(ProductType.referenceTypeId(), - productType2.getKey())); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); + + final AttributeDraft newProductTypeReferenceAttribute = + AttributeDraft.of( + "productType-reference", + Reference.of(ProductType.referenceTypeId(), productType2.getKey())); + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductTypeReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductTypeReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductTypeReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - final AttributeDraft expectedAttribute = - AttributeDraft.of("productType-reference", Reference.of(ProductType.referenceTypeId(), - productType2.getId())); - assertThat(actions).containsExactly(SetAttributeInAllVariants.of(expectedAttribute, true)); - - - final Product createdProduct = CTP_TARGET_CLIENT + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + final AttributeDraft expectedAttribute = + AttributeDraft.of( + "productType-reference", + Reference.of(ProductType.referenceTypeId(), productType2.getId())); + assertThat(actions).containsExactly(SetAttributeInAllVariants.of(expectedAttribute, true)); + + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(productType2.getId()); - }); - } - - @Test - void sync_withNonExistingProductTypeReferenceAsAttribute_ShouldFailCreatingTheProduct() { - // preparation - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("productType-reference", Reference.of(ProductType.referenceTypeId(), "nonExistingKey")); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(productType2.getId()); + }); + } + + @Test + void sync_withNonExistingProductTypeReferenceAsAttribute_ShouldFailCreatingTheProduct() { + // preparation + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of( + "productType-reference", Reference.of(ProductType.referenceTypeId(), "nonExistingKey")); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'productType-reference'"); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'productType-reference'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'productType-reference'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } - - @Test - void sync_withProductTypeReferenceSetAsAttribute_shouldCreateProductReferencingExistingCategories() { - // preparation - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("productType-reference", - Reference.of(ProductType.referenceTypeId(), productType.getKey())); - - final HashSet> references = new HashSet<>(); - references.add(Reference.of(ProductType.referenceTypeId(), productType.getKey())); - references.add(Reference.of(ProductType.referenceTypeId(), productType2.getKey())); - - final AttributeDraft productTypeReferenceSetAttribute = - AttributeDraft.of("productType-reference-set", references); - - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + .contains( + "The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'productType-reference'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } + + @Test + void + sync_withProductTypeReferenceSetAsAttribute_shouldCreateProductReferencingExistingCategories() { + // preparation + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of( + "productType-reference", + Reference.of(ProductType.referenceTypeId(), productType.getKey())); + + final HashSet> references = new HashSet<>(); + references.add(Reference.of(ProductType.referenceTypeId(), productType.getKey())); + references.add(Reference.of(ProductType.referenceTypeId(), productType2.getKey())); + + final AttributeDraft productTypeReferenceSetAttribute = + AttributeDraft.of("productType-reference-set", references); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute, productTypeReferenceSetAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductTypeReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(productType.getId()); - }); - - final Optional createdProductTypeReferenceSetAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productTypeReferenceSetAttribute.getName()); - - assertThat(createdProductTypeReferenceSetAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode()).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) attribute.getValueAsJsonNode(); - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(productType.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(productType2.getId()); - }); - }); - } - - @Test - void sync_withProductTypeReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { - // preparation - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("productType-reference", Reference.of(ProductType.referenceTypeId(), - productType.getKey())); - - final HashSet> references = new HashSet<>(); - references.add(Reference.of(ProductType.referenceTypeId(), "nonExistingKey")); - references.add(Reference.of(ProductType.referenceTypeId(), productType2.getKey())); - - final AttributeDraft productTypeReferenceSetAttribute = - AttributeDraft.of("productType-reference-set", references); - - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(productType.getId()); + }); + + final Optional createdProductTypeReferenceSetAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productTypeReferenceSetAttribute.getName()); + + assertThat(createdProductTypeReferenceSetAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode()).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) attribute.getValueAsJsonNode(); + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(productType.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(productType2.getId()); + }); + }); + } + + @Test + void + sync_withProductTypeReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { + // preparation + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of( + "productType-reference", + Reference.of(ProductType.referenceTypeId(), productType.getKey())); + + final HashSet> references = new HashSet<>(); + references.add(Reference.of(ProductType.referenceTypeId(), "nonExistingKey")); + references.add(Reference.of(ProductType.referenceTypeId(), productType2.getKey())); + + final AttributeDraft productTypeReferenceSetAttribute = + AttributeDraft.of("productType-reference-set", references); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productTypeReferenceAttribute, productTypeReferenceSetAttribute) .build(); - final ProductDraft productDraftWithProductTypeReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductTypeReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductTypeReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductTypeReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(error -> { - assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) error.getCause(); - assertThat(errorResponseException.getStatusCode()).isEqualTo(400); - assertThat(error.getMessage()) - .contains("The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'productType-reference-set'"); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + error -> { + assertThat(error).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) error.getCause(); + assertThat(errorResponseException.getStatusCode()).isEqualTo(400); + assertThat(error.getMessage()) + .contains( + "The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'productType-reference-set'"); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message) - .contains("The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " - + "is not valid for field 'productType-reference-set'")); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - } + .contains( + "The value '{\"typeId\":\"product-type\",\"id\":\"nonExistingKey\"}' " + + "is not valid for field 'productType-reference-set'")); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductsIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductsIT.java index b0ed827cac..ab2bebf65e 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductsIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductsIT.java @@ -1,5 +1,20 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.models.WaitingToBeResolved; import com.commercetools.sync.commons.utils.TriConsumer; @@ -22,478 +37,513 @@ import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; import io.sphere.sdk.products.queries.ProductByKeyGet; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncWithReferencedProductsIT { - private static ProductType productType; - - - private ProductSyncOptions syncOptions; - private Product product; - private Product product2; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> actions; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - - final ProductDraft productDraft = ProductDraftBuilder - .of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), emptyList()) + private static ProductType productType; + + private ProductSyncOptions syncOptions; + private Product product; + private Product product2; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> actions; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); + + final ProductDraft productDraft = + ProductDraftBuilder.of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), emptyList()) .key("foo") .build(); - final ProductDraft productDraft2 = ProductDraftBuilder - .of(productType, ofEnglish("foo2"), ofEnglish("foo-slug-2"), emptyList()) + final ProductDraft productDraft2 = + ProductDraftBuilder.of(productType, ofEnglish("foo2"), ofEnglish("foo-slug-2"), emptyList()) .key("foo2") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); - product2 = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft2))); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - actions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final TriConsumer, Optional> warningCallback - = (syncException, productDraft, product) -> warningCallBackMessages.add(syncException.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((syncException, draft, product, updateActions) - -> collectErrors(syncException.getMessage(), syncException)) - .beforeUpdateCallback(this::collectActions) - .warningCallback(warningCallback) - .build(); - } - - private void collectErrors(@Nullable final String errorMessage, @Nullable final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> collectActions(@Nonnull final List> actions, - @Nonnull final ProductDraft productDraft, - @Nonnull final Product product) { - this.actions.addAll(actions); - return actions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withProductReferenceAsAttribute_shouldCreateProductReferencingExistingProduct() { - // preparation - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product.getKey())); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); + product2 = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft2))); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + actions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final TriConsumer, Optional> warningCallback = + (syncException, productDraft, product) -> + warningCallBackMessages.add(syncException.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (syncException, draft, product, updateActions) -> + collectErrors(syncException.getMessage(), syncException)) + .beforeUpdateCallback(this::collectActions) + .warningCallback(warningCallback) + .build(); + } + + private void collectErrors( + @Nullable final String errorMessage, @Nullable final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> collectActions( + @Nonnull final List> actions, + @Nonnull final ProductDraft productDraft, + @Nonnull final Product product) { + this.actions.addAll(actions); + return actions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withProductReferenceAsAttribute_shouldCreateProductReferencingExistingProduct() { + // preparation + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + "product-reference", Reference.of(Product.referenceTypeId(), product.getKey())); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(product.getId()); - }); - } - - @Test - void sync_withSameProductReferenceAsAttribute_shouldNotSyncAnythingNew() { - // preparation - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product)); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(product.getId()); + }); + } + + @Test + void sync_withSameProductReferenceAsAttribute_shouldNotSyncAnythingNew() { + // preparation + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product)); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithProductReference)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithProductReference)) + .toCompletableFuture() + .join(); - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product.getKey())); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of( + "product-reference", Reference.of(Product.referenceTypeId(), product.getKey())); + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(product.getId()); - }); - } - - @Test - void sync_withChangedProductReferenceAsAttribute_shouldUpdateProductReferencingExistingProduct() { - // preparation - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product)); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(product.getId()); + }); + } + + @Test + void sync_withChangedProductReferenceAsAttribute_shouldUpdateProductReferencingExistingProduct() { + // preparation + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product)); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraftWithProductReference)) + .toCompletableFuture() + .join(); - final AttributeDraft newProductReferenceAttribute = - AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product2.getKey())); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder - .of() + final AttributeDraft newProductReferenceAttribute = + AttributeDraft.of( + "product-reference", Reference.of(Product.referenceTypeId(), product2.getKey())); + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(newProductReferenceAttribute) .build(); - final ProductDraft newProductDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) + final ProductDraft newProductDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), newMasterVariant) .key("new-product") .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(newProductDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - final AttributeDraft expectedAttribute = - AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product2.getId())); - assertThat(actions).containsExactly(SetAttributeInAllVariants.of(expectedAttribute, true)); - - - final Product createdProduct = CTP_TARGET_CLIENT + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(newProductDraftWithProductReference)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + final AttributeDraft expectedAttribute = + AttributeDraft.of( + "product-reference", Reference.of(Product.referenceTypeId(), product2.getId())); + assertThat(actions).containsExactly(SetAttributeInAllVariants.of(expectedAttribute, true)); + + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()).isEqualTo(product2.getId()); - }); - } - - @Test - void sync_withNonExistingProductReferenceAsAttribute_ShouldFailCreatingTheProduct() { - // preparation - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), "nonExistingKey")); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(product2.getId()); + }); + } + + @Test + void sync_withNonExistingProductReferenceAsAttribute_ShouldFailCreatingTheProduct() { + // preparation + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + "product-reference", Reference.of(Product.referenceTypeId(), "nonExistingKey")); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - - final UnresolvedReferencesServiceImpl unresolvedReferencesService = - new UnresolvedReferencesServiceImpl(syncOptions); - final Set waitingToBeResolvedDrafts = unresolvedReferencesService + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + + final UnresolvedReferencesServiceImpl unresolvedReferencesService = + new UnresolvedReferencesServiceImpl(syncOptions); + final Set waitingToBeResolvedDrafts = + unresolvedReferencesService .fetch(asSet(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - assertThat(waitingToBeResolvedDrafts) - .hasOnlyOneElementSatisfying(waitingToBeResolvedDraft -> { - assertThat(waitingToBeResolvedDraft.getProductDraft().getKey()) - .isEqualTo(productDraftWithProductReference.getKey()); - assertThat(waitingToBeResolvedDraft.getMissingReferencedProductKeys()) - .containsExactly("nonExistingKey"); + assertThat(waitingToBeResolvedDrafts) + .hasOnlyOneElementSatisfying( + waitingToBeResolvedDraft -> { + assertThat(waitingToBeResolvedDraft.getProductDraft().getKey()) + .isEqualTo(productDraftWithProductReference.getKey()); + assertThat(waitingToBeResolvedDraft.getMissingReferencedProductKeys()) + .containsExactly("nonExistingKey"); }); - } - - @Test - void sync_withProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingProducts() { - // preparation - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product.getKey())); + } - final HashSet> references = new HashSet<>(); - references.add(Reference.of(Product.referenceTypeId(), product.getKey())); - references.add(Reference.of(Product.referenceTypeId(), product2.getKey())); + @Test + void sync_withProductReferenceSetAsAttribute_shouldCreateProductReferencingExistingProducts() { + // preparation + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + "product-reference", Reference.of(Product.referenceTypeId(), product.getKey())); - final AttributeDraft productReferenceSetAttribute = - AttributeDraft.of("product-reference-set", references); + final HashSet> references = new HashSet<>(); + references.add(Reference.of(Product.referenceTypeId(), product.getKey())); + references.add(Reference.of(Product.referenceTypeId(), product2.getKey())); + final AttributeDraft productReferenceSetAttribute = + AttributeDraft.of("product-reference-set", references); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute, productReferenceSetAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final Product createdProduct = CTP_TARGET_CLIENT + final Product createdProduct = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()).isEqualTo(product.getId()); - }); - - final Optional createdProductReferenceSetAttribute = - createdProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceSetAttribute.getName()); - - assertThat(createdProductReferenceSetAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode()).isInstanceOf(ArrayNode.class); - final ArrayNode referenceSet = (ArrayNode) attribute.getValueAsJsonNode(); - assertThat(referenceSet) - .hasSize(2) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Product.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(product.getId()); - }) - .anySatisfy(reference -> { - assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(Product.referenceTypeId()); - assertThat(reference.get(REFERENCE_ID_FIELD).asText()).isEqualTo(product2.getId()); - }); - }); - } - - @Test - void sync_withProductReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { - // preparation - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("product-reference", Reference.of(Product.referenceTypeId(), product.getKey())); - - final HashSet> references = new HashSet<>(); - references.add(Reference.of(Product.referenceTypeId(), "nonExistingKey")); - references.add(Reference.of(Product.referenceTypeId(), product2.getKey())); - - final AttributeDraft productReferenceSetAttribute = - AttributeDraft.of("product-reference-set", references); - - - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of() + final Optional createdProductReferenceAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(product.getId()); + }); + + final Optional createdProductReferenceSetAttribute = + createdProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceSetAttribute.getName()); + + assertThat(createdProductReferenceSetAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode()).isInstanceOf(ArrayNode.class); + final ArrayNode referenceSet = (ArrayNode) attribute.getValueAsJsonNode(); + assertThat(referenceSet) + .hasSize(2) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(product.getId()); + }) + .anySatisfy( + reference -> { + assertThat(reference.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(reference.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(product2.getId()); + }); + }); + } + + @Test + void sync_withProductReferenceSetContainingANonExistingReference_shouldFailCreatingTheProduct() { + // preparation + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + "product-reference", Reference.of(Product.referenceTypeId(), product.getKey())); + + final HashSet> references = new HashSet<>(); + references.add(Reference.of(Product.referenceTypeId(), "nonExistingKey")); + references.add(Reference.of(Product.referenceTypeId(), product2.getKey())); + + final AttributeDraft productReferenceSetAttribute = + AttributeDraft.of("product-reference-set", references); + + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of() .sku("sku") .key("new-product-master-variant") .attributes(productReferenceAttribute, productReferenceSetAttribute) .build(); - final ProductDraft productDraftWithProductReference = ProductDraftBuilder - .of(productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) + final ProductDraft productDraftWithProductReference = + ProductDraftBuilder.of( + productType, ofEnglish("productName"), ofEnglish("productSlug"), masterVariant) .key("new-product") .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync(singletonList(productDraftWithProductReference)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(singletonList(productDraftWithProductReference)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 0, 1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - - final UnresolvedReferencesServiceImpl unresolvedReferencesService = - new UnresolvedReferencesServiceImpl(syncOptions); - final Set waitingToBeResolvedDrafts = unresolvedReferencesService + // assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 0, 1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + + final UnresolvedReferencesServiceImpl unresolvedReferencesService = + new UnresolvedReferencesServiceImpl(syncOptions); + final Set waitingToBeResolvedDrafts = + unresolvedReferencesService .fetch(asSet(productDraftWithProductReference.getKey())) .toCompletableFuture() .join(); - assertThat(waitingToBeResolvedDrafts) - .hasOnlyOneElementSatisfying(waitingToBeResolvedDraft -> { - assertThat(waitingToBeResolvedDraft.getProductDraft().getKey()) - .isEqualTo(productDraftWithProductReference.getKey()); - assertThat(waitingToBeResolvedDraft.getMissingReferencedProductKeys()) - .containsExactly("nonExistingKey"); + assertThat(waitingToBeResolvedDrafts) + .hasOnlyOneElementSatisfying( + waitingToBeResolvedDraft -> { + assertThat(waitingToBeResolvedDraft.getProductDraft().getKey()) + .isEqualTo(productDraftWithProductReference.getKey()); + assertThat(waitingToBeResolvedDraft.getMissingReferencedProductKeys()) + .containsExactly("nonExistingKey"); }); - } + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductsInAnyOrderIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductsInAnyOrderIT.java index 6099379603..e1265d81b4 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductsInAnyOrderIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/ProductSyncWithReferencedProductsInAnyOrderIT.java @@ -1,5 +1,27 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedCustomObjects; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.models.WaitingToBeResolved; import com.commercetools.sync.commons.utils.TriConsumer; @@ -30,123 +52,107 @@ import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedCustomObjects; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncWithReferencedProductsInAnyOrderIT { - private static ProductType productType; - - - private ProductSyncOptions syncOptions; - private Product product; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> actions; - - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - deleteWaitingToBeResolvedCustomObjects(CTP_TARGET_CLIENT); - syncOptions = buildSyncOptions(); - - final ProductDraft productDraft = ProductDraftBuilder - .of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), emptyList()) + private static ProductType productType; + + private ProductSyncOptions syncOptions; + private Product product; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> actions; + + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + deleteWaitingToBeResolvedCustomObjects(CTP_TARGET_CLIENT); + syncOptions = buildSyncOptions(); + + final ProductDraft productDraft = + ProductDraftBuilder.of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), emptyList()) .masterVariant(ProductVariantDraftBuilder.of().key("foo").sku("foo").build()) .key("foo") .build(); - product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - actions = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final TriConsumer, Optional> warningCallback - = (syncException, productDraft, product) -> warningCallBackMessages.add(syncException.getMessage()); - - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((syncException, draft, product, updateActions) - -> collectErrors(syncException.getMessage(), syncException)) - .beforeUpdateCallback(this::collectActions) - .batchSize(1) - .warningCallback(warningCallback) - .build(); - } - - private void collectErrors(@Nullable final String errorMessage, @Nullable final Throwable exception) { - errorCallBackMessages.add(errorMessage); - errorCallBackExceptions.add(exception); - } - - private List> collectActions(@Nonnull final List> actions, - @Nonnull final ProductDraft productDraft, - @Nonnull final Product product) { - this.actions.addAll(actions); - return actions; - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withSingleChildDraftSuppliedBeforeParent_shouldSyncCorrectly() { - // preparation - final String productReferenceAttributeName = "product-reference"; - final String parentProductKey = "parent-product-key"; - - final AttributeDraft productReferenceAttribute = AttributeDraft - .of(productReferenceAttributeName, Reference.of(Product.referenceTypeId(), parentProductKey)); - - final ProductDraft childDraft = ProductDraftBuilder - .of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), - ProductVariantDraftBuilder - .of() + product = executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + actions = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final TriConsumer, Optional> warningCallback = + (syncException, productDraft, product) -> + warningCallBackMessages.add(syncException.getMessage()); + + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (syncException, draft, product, updateActions) -> + collectErrors(syncException.getMessage(), syncException)) + .beforeUpdateCallback(this::collectActions) + .batchSize(1) + .warningCallback(warningCallback) + .build(); + } + + private void collectErrors( + @Nullable final String errorMessage, @Nullable final Throwable exception) { + errorCallBackMessages.add(errorMessage); + errorCallBackExceptions.add(exception); + } + + private List> collectActions( + @Nonnull final List> actions, + @Nonnull final ProductDraft productDraft, + @Nonnull final Product product) { + this.actions.addAll(actions); + return actions; + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withSingleChildDraftSuppliedBeforeParent_shouldSyncCorrectly() { + // preparation + final String productReferenceAttributeName = "product-reference"; + final String parentProductKey = "parent-product-key"; + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + productReferenceAttributeName, + Reference.of(Product.referenceTypeId(), parentProductKey)); + + final ProductDraft childDraft = + ProductDraftBuilder.of( + productType, + ofEnglish("foo"), + ofEnglish("foo-slug"), + ProductVariantDraftBuilder.of() .key("foo") .sku("foo") .attributes(productReferenceAttribute) @@ -154,93 +160,96 @@ void sync_withSingleChildDraftSuppliedBeforeParent_shouldSyncCorrectly() { .key(product.getKey()) .build(); - final ProductDraft parentDraft = ProductDraftBuilder - .of(productType, ofEnglish("bar"), ofEnglish("bar-slug"), - ProductVariantDraftBuilder - .of() - .sku("bar") - .key("bar") - .build()) + final ProductDraft parentDraft = + ProductDraftBuilder.of( + productType, + ofEnglish("bar"), + ofEnglish("bar-slug"), + ProductVariantDraftBuilder.of().sku("bar").key("bar").build()) .key(parentProductKey) .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = productSync - .sync(asList(childDraft, parentDraft)) - .toCompletableFuture() - .join(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync.sync(asList(childDraft, parentDraft)).toCompletableFuture().join(); - final Product syncedParent = CTP_TARGET_CLIENT + final Product syncedParent = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentDraft.getKey())) .toCompletableFuture() .join(); - // assertion - assertThat(syncedParent).isNotNull(); - assertThat(syncedParent.getKey()).isEqualTo(parentProductKey); - assertThat(syncStatistics).hasValues(2, 1, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).containsExactly( + // assertion + assertThat(syncedParent).isNotNull(); + assertThat(syncedParent.getKey()).isEqualTo(parentProductKey); + assertThat(syncStatistics).hasValues(2, 1, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions) + .containsExactly( SetAttributeInAllVariants.of( productReferenceAttributeName, createReferenceObject(syncedParent.getId(), Product.referenceTypeId()), - true - ) - ); + true)); - final Product syncedChild = CTP_TARGET_CLIENT + final Product syncedChild = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(childDraft.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = syncedChild + final Optional createdProductReferenceAttribute = + syncedChild .getMasterData() .getStaged() .getMasterVariant() .findAttribute(productReferenceAttribute.getName()); - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()).isEqualTo(syncedParent.getId()); - }); - - assertNoWaitingDrafts(CTP_TARGET_CLIENT); - } - - private void assertNoWaitingDrafts(@Nonnull final SphereClient client) { - final CustomObjectQuery customObjectQuery = - CustomObjectQuery.of(WaitingToBeResolved.class) - .byContainer("commercetools-sync-java.UnresolvedReferencesService.productDrafts"); - - final PagedQueryResult> queryResult = - client.execute(customObjectQuery).toCompletableFuture().join(); - - assertThat(queryResult.getResults()).isEmpty(); - } - - @Test - void sync_withSingleChildDraftSuppliedBeforeMultipleParents_shouldSyncCorrectly() { - // preparation - final String productReferenceAttributeName = "product-reference-set"; - final String parentProductKey = "parent-product-key"; - final String parentProductKey2 = "parent-product-key2"; - - final AttributeDraft productReferenceAttribute = AttributeDraft - .of( - productReferenceAttributeName, - asSet( - Reference.of(Product.referenceTypeId(), parentProductKey), - Reference.of(Product.referenceTypeId(), parentProductKey2)) - ); - - final ProductDraft childDraft = ProductDraftBuilder - .of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), - ProductVariantDraftBuilder - .of() + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent.getId()); + }); + + assertNoWaitingDrafts(CTP_TARGET_CLIENT); + } + + private void assertNoWaitingDrafts(@Nonnull final SphereClient client) { + final CustomObjectQuery customObjectQuery = + CustomObjectQuery.of(WaitingToBeResolved.class) + .byContainer("commercetools-sync-java.UnresolvedReferencesService.productDrafts"); + + final PagedQueryResult> queryResult = + client.execute(customObjectQuery).toCompletableFuture().join(); + + assertThat(queryResult.getResults()).isEmpty(); + } + + @Test + void sync_withSingleChildDraftSuppliedBeforeMultipleParents_shouldSyncCorrectly() { + // preparation + final String productReferenceAttributeName = "product-reference-set"; + final String parentProductKey = "parent-product-key"; + final String parentProductKey2 = "parent-product-key2"; + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + productReferenceAttributeName, + asSet( + Reference.of(Product.referenceTypeId(), parentProductKey), + Reference.of(Product.referenceTypeId(), parentProductKey2))); + + final ProductDraft childDraft = + ProductDraftBuilder.of( + productType, + ofEnglish("foo"), + ofEnglish("foo-slug"), + ProductVariantDraftBuilder.of() .key("foo") .sku("foo") .attributes(productReferenceAttribute) @@ -248,104 +257,117 @@ void sync_withSingleChildDraftSuppliedBeforeMultipleParents_shouldSyncCorrectly( .key(product.getKey()) .build(); - final ProductDraft parentDraft1 = ProductDraftBuilder - .of(productType, ofEnglish("bar"), ofEnglish("bar-slug"), - ProductVariantDraftBuilder - .of() - .sku("bar") - .key("bar") - .build()) + final ProductDraft parentDraft1 = + ProductDraftBuilder.of( + productType, + ofEnglish("bar"), + ofEnglish("bar-slug"), + ProductVariantDraftBuilder.of().sku("bar").key("bar").build()) .key(parentProductKey) .build(); - final ProductDraft parentDraft2 = ProductDraftBuilder - .of(productType, ofEnglish(parentProductKey2), ofEnglish(parentProductKey2), - ProductVariantDraftBuilder - .of() + final ProductDraft parentDraft2 = + ProductDraftBuilder.of( + productType, + ofEnglish(parentProductKey2), + ofEnglish(parentProductKey2), + ProductVariantDraftBuilder.of() .sku(parentProductKey2) .key(parentProductKey2) .build()) .key(parentProductKey2) .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = productSync + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync .sync(asList(childDraft, parentDraft1, parentDraft2)) .toCompletableFuture() .join(); - final Product syncedParent1 = CTP_TARGET_CLIENT + final Product syncedParent1 = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentDraft1.getKey())) .toCompletableFuture() .join(); - final Product syncedParent2 = CTP_TARGET_CLIENT + final Product syncedParent2 = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentDraft2.getKey())) .toCompletableFuture() .join(); - // assertion - assertThat(syncedParent1).isNotNull(); - assertThat(syncedParent2).isNotNull(); - assertThat(syncedParent1.getKey()).isEqualTo(parentProductKey); - assertThat(syncedParent2.getKey()).isEqualTo(parentProductKey2); - assertThat(syncStatistics).hasValues(3, 2, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - - final ArrayNode expectedAttributeValue = JsonNodeFactory.instance.arrayNode(); - expectedAttributeValue.add(createReferenceObject(syncedParent1.getId(), Product.referenceTypeId())); - expectedAttributeValue.add(createReferenceObject(syncedParent2.getId(), Product.referenceTypeId())); - - assertThat(actions).containsExactly( + // assertion + assertThat(syncedParent1).isNotNull(); + assertThat(syncedParent2).isNotNull(); + assertThat(syncedParent1.getKey()).isEqualTo(parentProductKey); + assertThat(syncedParent2.getKey()).isEqualTo(parentProductKey2); + assertThat(syncStatistics).hasValues(3, 2, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + final ArrayNode expectedAttributeValue = JsonNodeFactory.instance.arrayNode(); + expectedAttributeValue.add( + createReferenceObject(syncedParent1.getId(), Product.referenceTypeId())); + expectedAttributeValue.add( + createReferenceObject(syncedParent2.getId(), Product.referenceTypeId())); + + assertThat(actions) + .containsExactly( SetAttributeInAllVariants.of( - productReferenceAttributeName, - expectedAttributeValue, - true - ) - ); + productReferenceAttributeName, expectedAttributeValue, true)); - final Product syncedChild = CTP_TARGET_CLIENT + final Product syncedChild = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(childDraft.getKey())) .toCompletableFuture() .join(); - final Optional createdProductReferenceAttribute = syncedChild + final Optional createdProductReferenceAttribute = + syncedChild .getMasterData() .getStaged() .getMasterVariant() .findAttribute(productReferenceAttribute.getName()); - assertThat(createdProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(0).get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(0).get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent1.getId()); - - assertThat(attribute.getValueAsJsonNode().get(1).get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(1).get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent2.getId()); - }); - - assertNoWaitingDrafts(CTP_TARGET_CLIENT); - } - - @Test - void sync_withMultipleChildDraftsSuppliedBeforeParent_shouldSyncCorrectly() { - // preparation - final String productReferenceAttributeName = "product-reference"; - final String parentProductKey = "parent-product-key"; - - final AttributeDraft productReferenceAttribute = AttributeDraft - .of(productReferenceAttributeName, Reference.of(Product.referenceTypeId(), parentProductKey)); - - final ProductDraft childDraft1 = ProductDraftBuilder - .of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), - ProductVariantDraftBuilder - .of() + assertThat(createdProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat( + attribute.getValueAsJsonNode().get(0).get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(0).get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent1.getId()); + + assertThat( + attribute.getValueAsJsonNode().get(1).get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(1).get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent2.getId()); + }); + + assertNoWaitingDrafts(CTP_TARGET_CLIENT); + } + + @Test + void sync_withMultipleChildDraftsSuppliedBeforeParent_shouldSyncCorrectly() { + // preparation + final String productReferenceAttributeName = "product-reference"; + final String parentProductKey = "parent-product-key"; + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + productReferenceAttributeName, + Reference.of(Product.referenceTypeId(), parentProductKey)); + + final ProductDraft childDraft1 = + ProductDraftBuilder.of( + productType, + ofEnglish("foo"), + ofEnglish("foo-slug"), + ProductVariantDraftBuilder.of() .key("foo") .sku("foo") .attributes(productReferenceAttribute) @@ -353,10 +375,12 @@ void sync_withMultipleChildDraftsSuppliedBeforeParent_shouldSyncCorrectly() { .key(product.getKey()) .build(); - final ProductDraft childDraft2 = ProductDraftBuilder - .of(productType, ofEnglish("foo-2"), ofEnglish("foo-slug-2"), - ProductVariantDraftBuilder - .of() + final ProductDraft childDraft2 = + ProductDraftBuilder.of( + productType, + ofEnglish("foo-2"), + ofEnglish("foo-slug-2"), + ProductVariantDraftBuilder.of() .key("foo-2") .sku("foo-2") .attributes(productReferenceAttribute) @@ -364,124 +388,142 @@ void sync_withMultipleChildDraftsSuppliedBeforeParent_shouldSyncCorrectly() { .key("foo-2") .build(); - final ProductDraft parentDraft = ProductDraftBuilder - .of(productType, ofEnglish("bar"), ofEnglish("bar-slug"), - ProductVariantDraftBuilder - .of() - .sku("bar") - .key("bar") - .build()) + final ProductDraft parentDraft = + ProductDraftBuilder.of( + productType, + ofEnglish("bar"), + ofEnglish("bar-slug"), + ProductVariantDraftBuilder.of().sku("bar").key("bar").build()) .key(parentProductKey) .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = productSync + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync .sync(asList(childDraft1, childDraft2, parentDraft)) .toCompletableFuture() .join(); - final Product syncedParent = CTP_TARGET_CLIENT + final Product syncedParent = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentDraft.getKey())) .toCompletableFuture() .join(); - // assertion - assertThat(syncedParent).isNotNull(); - assertThat(syncedParent.getKey()).isEqualTo(parentProductKey); - assertThat(syncStatistics).hasValues(3, 2, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).containsExactly( + // assertion + assertThat(syncedParent).isNotNull(); + assertThat(syncedParent.getKey()).isEqualTo(parentProductKey); + assertThat(syncStatistics).hasValues(3, 2, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions) + .containsExactly( SetAttributeInAllVariants.of( productReferenceAttributeName, createReferenceObject(syncedParent.getId(), Product.referenceTypeId()), - true - ) - ); + true)); - final Product syncedChild1 = CTP_TARGET_CLIENT + final Product syncedChild1 = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(childDraft1.getKey())) .toCompletableFuture() .join(); - final Product syncedChild2 = CTP_TARGET_CLIENT + final Product syncedChild2 = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(childDraft2.getKey())) .toCompletableFuture() .join(); - final Optional syncedProductReferenceAttribute1 = syncedChild1 + final Optional syncedProductReferenceAttribute1 = + syncedChild1 .getMasterData() .getStaged() .getMasterVariant() .findAttribute(productReferenceAttribute.getName()); - final Optional createdProductReferenceAttribute2 = syncedChild2 + final Optional createdProductReferenceAttribute2 = + syncedChild2 .getMasterData() .getStaged() .getMasterVariant() .findAttribute(productReferenceAttribute.getName()); - assertThat(syncedProductReferenceAttribute1).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()).isEqualTo(syncedParent.getId()); - }); - - assertThat(createdProductReferenceAttribute2).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()).isEqualTo(syncedParent.getId()); - }); - - assertNoWaitingDrafts(CTP_TARGET_CLIENT); - } - - @Test - void sync_withMultipleHierarchyProductReferenceAsAttribute_shouldCreateProductReferencingExistingProduct() { - // preparation - final String parentKey = "product-parent"; - final String parentKey1 = "product-parent-1"; - final String parentKey2 = "product-parent-2"; - final String parentKey3 = "product-parent-3"; - final String parentKey4 = "product-parent-4"; - final String productAttributeReferenceName = "product-reference"; - final String productAttributeReferenceSetName = "product-reference-set"; - - final AttributeDraft productReferenceAttribute = AttributeDraft - .of(productAttributeReferenceName, - Reference.of(Product.referenceTypeId(), parentKey)); - - final ArrayNode refSet = JsonNodeFactory.instance.arrayNode(); - - final ObjectNode parentKey1RefObject = createReferenceObject(parentKey1, Product.referenceTypeId()); - final ObjectNode parentKey2RefObject = createReferenceObject(parentKey2, Product.referenceTypeId()); - final ObjectNode parentKey3RefObject = createReferenceObject(parentKey3, Product.referenceTypeId()); - final ObjectNode parentKey4RefObject = createReferenceObject(parentKey4, Product.referenceTypeId()); - - refSet.add(parentKey1RefObject); - refSet.add(parentKey2RefObject); - refSet.add(parentKey3RefObject); - refSet.add(parentKey4RefObject); - - - final AttributeDraft productReferenceSetAttribute = AttributeDraft.of(productAttributeReferenceSetName, refSet); - - final ProductDraft childDraft = ProductDraftBuilder - .of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), emptyList()) + assertThat(syncedProductReferenceAttribute1) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent.getId()); + }); + + assertThat(createdProductReferenceAttribute2) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent.getId()); + }); + + assertNoWaitingDrafts(CTP_TARGET_CLIENT); + } + + @Test + void + sync_withMultipleHierarchyProductReferenceAsAttribute_shouldCreateProductReferencingExistingProduct() { + // preparation + final String parentKey = "product-parent"; + final String parentKey1 = "product-parent-1"; + final String parentKey2 = "product-parent-2"; + final String parentKey3 = "product-parent-3"; + final String parentKey4 = "product-parent-4"; + final String productAttributeReferenceName = "product-reference"; + final String productAttributeReferenceSetName = "product-reference-set"; + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + productAttributeReferenceName, Reference.of(Product.referenceTypeId(), parentKey)); + + final ArrayNode refSet = JsonNodeFactory.instance.arrayNode(); + + final ObjectNode parentKey1RefObject = + createReferenceObject(parentKey1, Product.referenceTypeId()); + final ObjectNode parentKey2RefObject = + createReferenceObject(parentKey2, Product.referenceTypeId()); + final ObjectNode parentKey3RefObject = + createReferenceObject(parentKey3, Product.referenceTypeId()); + final ObjectNode parentKey4RefObject = + createReferenceObject(parentKey4, Product.referenceTypeId()); + + refSet.add(parentKey1RefObject); + refSet.add(parentKey2RefObject); + refSet.add(parentKey3RefObject); + refSet.add(parentKey4RefObject); + + final AttributeDraft productReferenceSetAttribute = + AttributeDraft.of(productAttributeReferenceSetName, refSet); + + final ProductDraft childDraft = + ProductDraftBuilder.of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), emptyList()) .key("foo") - .masterVariant(ProductVariantDraftBuilder - .of() - .key("foo") - .sku("foo") - .attributes(productReferenceAttribute, productReferenceSetAttribute).build()) + .masterVariant( + ProductVariantDraftBuilder.of() + .key("foo") + .sku("foo") + .attributes(productReferenceAttribute, productReferenceSetAttribute) + .build()) .build(); - final ProductDraft childDraft1 = ProductDraftBuilder - .of(productType, ofEnglish("childDraft1"), ofEnglish("childDraft1"), - ProductVariantDraftBuilder - .of() + final ProductDraft childDraft1 = + ProductDraftBuilder.of( + productType, + ofEnglish("childDraft1"), + ofEnglish("childDraft1"), + ProductVariantDraftBuilder.of() .key("childDraft1") .sku("childDraft1") .attributes(productReferenceAttribute) @@ -489,222 +531,278 @@ void sync_withMultipleHierarchyProductReferenceAsAttribute_shouldCreateProductRe .key("childDraft1") .build(); - final ProductDraft parentDraft = ProductDraftBuilder - .of(productType, ofEnglish(parentKey), ofEnglish(parentKey), - ProductVariantDraftBuilder - .of() - .sku(parentKey) - .key(parentKey) - .build()) + final ProductDraft parentDraft = + ProductDraftBuilder.of( + productType, + ofEnglish(parentKey), + ofEnglish(parentKey), + ProductVariantDraftBuilder.of().sku(parentKey).key(parentKey).build()) .key(parentKey) .build(); - final ProductDraft parentDraft1 = ProductDraftBuilder - .of(productType, ofEnglish(parentKey1), ofEnglish(parentKey1), - ProductVariantDraftBuilder - .of() - .key(parentKey1) - .sku(parentKey1) - .build()) + final ProductDraft parentDraft1 = + ProductDraftBuilder.of( + productType, + ofEnglish(parentKey1), + ofEnglish(parentKey1), + ProductVariantDraftBuilder.of().key(parentKey1).sku(parentKey1).build()) .key(parentKey1) .build(); - final ProductDraft parentDraft2 = ProductDraftBuilder - .of(productType, ofEnglish(parentKey2), ofEnglish(parentKey2), - ProductVariantDraftBuilder - .of() + final ProductDraft parentDraft2 = + ProductDraftBuilder.of( + productType, + ofEnglish(parentKey2), + ofEnglish(parentKey2), + ProductVariantDraftBuilder.of() .key(parentKey2) .sku(parentKey2) - .attributes(AttributeDraft.of(productAttributeReferenceName, parentKey1RefObject)) + .attributes( + AttributeDraft.of(productAttributeReferenceName, parentKey1RefObject)) .build()) .key(parentKey2) .build(); - final ProductDraft parentDraft3 = ProductDraftBuilder - .of(productType, ofEnglish(parentKey3), ofEnglish(parentKey3), - ProductVariantDraftBuilder - .of() + final ProductDraft parentDraft3 = + ProductDraftBuilder.of( + productType, + ofEnglish(parentKey3), + ofEnglish(parentKey3), + ProductVariantDraftBuilder.of() .key(parentKey3) .sku(parentKey3) - .attributes(AttributeDraft.of(productAttributeReferenceName, parentKey2RefObject)) + .attributes( + AttributeDraft.of(productAttributeReferenceName, parentKey2RefObject)) .build()) .key(parentKey3) .build(); - final ProductDraft parentDraft4 = ProductDraftBuilder - .of(productType, ofEnglish(parentKey4), ofEnglish(parentKey4), - ProductVariantDraftBuilder - .of() + final ProductDraft parentDraft4 = + ProductDraftBuilder.of( + productType, + ofEnglish(parentKey4), + ofEnglish(parentKey4), + ProductVariantDraftBuilder.of() .key(parentKey4) .sku(parentKey4) - .attributes(AttributeDraft.of(productAttributeReferenceName, parentKey3RefObject)) + .attributes( + AttributeDraft.of(productAttributeReferenceName, parentKey3RefObject)) .build()) .key(parentKey4) .build(); + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync + .sync( + asList( + childDraft, + parentDraft, + parentDraft4, + childDraft1, + parentDraft3, + parentDraft2, + parentDraft1)) + .toCompletableFuture() + .join(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = - productSync - .sync(asList(childDraft, - parentDraft, parentDraft4, childDraft1, parentDraft3, parentDraft2, parentDraft1)) - .toCompletableFuture() - .join(); - - // assertion - final Product syncedChild1 = CTP_TARGET_CLIENT + // assertion + final Product syncedChild1 = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(childDraft1.getKey())) .toCompletableFuture() .join(); - final Product syncedParent = CTP_TARGET_CLIENT + final Product syncedParent = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentDraft.getKey())) .toCompletableFuture() .join(); - final Product syncedParent1 = CTP_TARGET_CLIENT + final Product syncedParent1 = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentDraft1.getKey())) .toCompletableFuture() .join(); - final Product syncedParent2 = CTP_TARGET_CLIENT + final Product syncedParent2 = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentDraft2.getKey())) .toCompletableFuture() .join(); - final Product syncedParent3 = CTP_TARGET_CLIENT + final Product syncedParent3 = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentDraft3.getKey())) .toCompletableFuture() .join(); - final Product syncedParent4 = CTP_TARGET_CLIENT + final Product syncedParent4 = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentDraft4.getKey())) .toCompletableFuture() .join(); + final ArrayNode expectedAttributeValue = JsonNodeFactory.instance.arrayNode(); + expectedAttributeValue.add( + createReferenceObject(syncedParent1.getId(), Product.referenceTypeId())); + expectedAttributeValue.add( + createReferenceObject(syncedParent2.getId(), Product.referenceTypeId())); + expectedAttributeValue.add( + createReferenceObject(syncedParent3.getId(), Product.referenceTypeId())); + expectedAttributeValue.add( + createReferenceObject(syncedParent4.getId(), Product.referenceTypeId())); + + assertThat(syncStatistics).hasValues(7, 6, 1, 0, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + + final Product existingProduct = + CTP_TARGET_CLIENT + .execute(ProductByKeyGet.of(childDraft.getKey())) + .toCompletableFuture() + .join(); - final ArrayNode expectedAttributeValue = JsonNodeFactory.instance.arrayNode(); - expectedAttributeValue.add(createReferenceObject(syncedParent1.getId(), Product.referenceTypeId())); - expectedAttributeValue.add(createReferenceObject(syncedParent2.getId(), Product.referenceTypeId())); - expectedAttributeValue.add(createReferenceObject(syncedParent3.getId(), Product.referenceTypeId())); - expectedAttributeValue.add(createReferenceObject(syncedParent4.getId(), Product.referenceTypeId())); + final Optional existingProductReferenceSetAttribute = + existingProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceSetAttribute.getName()); + + assertThat(existingProductReferenceSetAttribute) + .hasValueSatisfying( + attribute -> { + assertThat( + attribute.getValueAsJsonNode().get(0).get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(0).get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent1.getId()); + + assertThat( + attribute.getValueAsJsonNode().get(1).get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(1).get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent2.getId()); + + assertThat( + attribute.getValueAsJsonNode().get(2).get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(2).get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent3.getId()); + + assertThat( + attribute.getValueAsJsonNode().get(3).get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(3).get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent4.getId()); + }); + + final Optional existingProductReferenceAttribute = + existingProduct + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); - assertThat(syncStatistics).hasValues(7, 6, 1, 0, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); + assertThat(existingProductReferenceAttribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent.getId()); + }); + + final Optional syncedProduct4Attribute = + syncedParent4 + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + assertThat(syncedProduct4Attribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent3.getId()); + }); + + final Optional syncedProduct3Attribute = + syncedParent3 + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); - final Product existingProduct = CTP_TARGET_CLIENT - .execute(ProductByKeyGet.of(childDraft.getKey())) - .toCompletableFuture() - .join(); + assertThat(syncedProduct3Attribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent2.getId()); + }); + + final Optional syncedProduct2Attribute = + syncedParent2 + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); - final Optional existingProductReferenceSetAttribute = - existingProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceSetAttribute.getName()); - - assertThat(existingProductReferenceSetAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(0).get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(0).get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent1.getId()); - - assertThat(attribute.getValueAsJsonNode().get(1).get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(1).get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent2.getId()); - - assertThat(attribute.getValueAsJsonNode().get(2).get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(2).get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent3.getId()); - - assertThat(attribute.getValueAsJsonNode().get(3).get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(3).get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent4.getId()); - }); - - final Optional existingProductReferenceAttribute = - existingProduct.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(existingProductReferenceAttribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent.getId()); - }); - - - final Optional syncedProduct4Attribute = - syncedParent4.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(syncedProduct4Attribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent3.getId()); - }); - - final Optional syncedProduct3Attribute = - syncedParent3.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(syncedProduct3Attribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent2.getId()); - }); - - final Optional syncedProduct2Attribute = - syncedParent2.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(syncedProduct2Attribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent1.getId()); - }); - - final Optional syncedChild1Attribute = - syncedChild1.getMasterData().getStaged().getMasterVariant() - .findAttribute(productReferenceAttribute.getName()); - - assertThat(syncedChild1Attribute).hasValueSatisfying(attribute -> { - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(syncedParent.getId()); - }); - - assertNoWaitingDrafts(CTP_TARGET_CLIENT); - } - - @Test - void sync_withMissingParent_shouldSyncCorrectly() { - // preparation - final String productReferenceAttributeName = "product-reference"; - final String parentProductKey = "parent-product-key"; - final String parentProductKey2 = "parent-product-key2"; - - final AttributeDraft productReferenceAttribute = AttributeDraft - .of( - productReferenceAttributeName, - asSet( - Reference.of(Product.referenceTypeId(), parentProductKey), - Reference.of(Product.referenceTypeId(), parentProductKey2)) - ); - - final ProductDraft childDraft1 = ProductDraftBuilder - .of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), - ProductVariantDraftBuilder - .of() + assertThat(syncedProduct2Attribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent1.getId()); + }); + + final Optional syncedChild1Attribute = + syncedChild1 + .getMasterData() + .getStaged() + .getMasterVariant() + .findAttribute(productReferenceAttribute.getName()); + + assertThat(syncedChild1Attribute) + .hasValueSatisfying( + attribute -> { + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + assertThat(attribute.getValueAsJsonNode().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(syncedParent.getId()); + }); + + assertNoWaitingDrafts(CTP_TARGET_CLIENT); + } + + @Test + void sync_withMissingParent_shouldSyncCorrectly() { + // preparation + final String productReferenceAttributeName = "product-reference"; + final String parentProductKey = "parent-product-key"; + final String parentProductKey2 = "parent-product-key2"; + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + productReferenceAttributeName, + asSet( + Reference.of(Product.referenceTypeId(), parentProductKey), + Reference.of(Product.referenceTypeId(), parentProductKey2))); + + final ProductDraft childDraft1 = + ProductDraftBuilder.of( + productType, + ofEnglish("foo"), + ofEnglish("foo-slug"), + ProductVariantDraftBuilder.of() .key("foo") .sku("foo") .attributes(productReferenceAttribute) @@ -712,10 +810,12 @@ void sync_withMissingParent_shouldSyncCorrectly() { .key(product.getKey()) .build(); - final ProductDraft childDraft2 = ProductDraftBuilder - .of(productType, ofEnglish("foo-2"), ofEnglish("foo-slug-2"), - ProductVariantDraftBuilder - .of() + final ProductDraft childDraft2 = + ProductDraftBuilder.of( + productType, + ofEnglish("foo-2"), + ofEnglish("foo-slug-2"), + ProductVariantDraftBuilder.of() .key("foo-2") .sku("foo-2") .attributes(productReferenceAttribute) @@ -723,65 +823,73 @@ void sync_withMissingParent_shouldSyncCorrectly() { .key("foo-2") .build(); - final ProductDraft parentDraft = ProductDraftBuilder - .of(productType, ofEnglish(parentProductKey2), ofEnglish(parentProductKey2), - ProductVariantDraftBuilder - .of() + final ProductDraft parentDraft = + ProductDraftBuilder.of( + productType, + ofEnglish(parentProductKey2), + ofEnglish(parentProductKey2), + ProductVariantDraftBuilder.of() .sku(parentProductKey2) .key(parentProductKey2) .build()) .key(parentProductKey2) .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = productSync + // test + final ProductSync productSync = new ProductSync(syncOptions); + final ProductSyncStatistics syncStatistics = + productSync .sync(asList(childDraft1, childDraft2, parentDraft)) .toCompletableFuture() .join(); - final Product syncedParent = CTP_TARGET_CLIENT + final Product syncedParent = + CTP_TARGET_CLIENT .execute(ProductByKeyGet.of(parentProductKey)) .toCompletableFuture() .join(); - // assertion - assertThat(syncedParent).isNull(); - assertThat(syncStatistics).hasValues(3, 1, 0, 0, 2); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - + // assertion + assertThat(syncedParent).isNull(); + assertThat(syncStatistics).hasValues(3, 1, 0, 0, 2); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); - final UnresolvedReferencesService unresolvedReferencesService = - new UnresolvedReferencesServiceImpl(syncOptions); + final UnresolvedReferencesService unresolvedReferencesService = + new UnresolvedReferencesServiceImpl(syncOptions); - final Set waitingDrafts = unresolvedReferencesService + final Set waitingDrafts = + unresolvedReferencesService .fetch(asSet(childDraft1.getKey(), childDraft2.getKey())) .toCompletableFuture() .join(); - assertThat(waitingDrafts).containsExactlyInAnyOrder( + assertThat(waitingDrafts) + .containsExactlyInAnyOrder( new WaitingToBeResolved(childDraft1, singleton(parentProductKey)), - new WaitingToBeResolved(childDraft2, singleton(parentProductKey)) - ); - } - - @SuppressWarnings("unchecked") - @Test - void sync_withFailToFetchCustomObject_shouldSyncCorrectly() { - // preparation - final String productReferenceAttributeName = "product-reference"; - final String parentProductKey = "parent-product-key"; - - final AttributeDraft productReferenceAttribute = AttributeDraft - .of(productReferenceAttributeName, Reference.of(Product.referenceTypeId(), parentProductKey)); - - final ProductDraft childDraft1 = ProductDraftBuilder - .of(productType, ofEnglish("foo"), ofEnglish("foo-slug"), - ProductVariantDraftBuilder - .of() + new WaitingToBeResolved(childDraft2, singleton(parentProductKey))); + } + + @SuppressWarnings("unchecked") + @Test + void sync_withFailToFetchCustomObject_shouldSyncCorrectly() { + // preparation + final String productReferenceAttributeName = "product-reference"; + final String parentProductKey = "parent-product-key"; + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of( + productReferenceAttributeName, + Reference.of(Product.referenceTypeId(), parentProductKey)); + + final ProductDraft childDraft1 = + ProductDraftBuilder.of( + productType, + ofEnglish("foo"), + ofEnglish("foo-slug"), + ProductVariantDraftBuilder.of() .key("foo") .sku("foo") .attributes(productReferenceAttribute) @@ -789,60 +897,59 @@ void sync_withFailToFetchCustomObject_shouldSyncCorrectly() { .key(product.getKey()) .build(); - final ProductDraft parentDraft = ProductDraftBuilder - .of(productType, ofEnglish(parentProductKey), ofEnglish(parentProductKey), - ProductVariantDraftBuilder - .of() - .sku(parentProductKey) - .key(parentProductKey) - .build()) + final ProductDraft parentDraft = + ProductDraftBuilder.of( + productType, + ofEnglish(parentProductKey), + ofEnglish(parentProductKey), + ProductVariantDraftBuilder.of().sku(parentProductKey).key(parentProductKey).build()) .key(parentProductKey) .build(); - final SphereClient ctpClient = spy(CTP_TARGET_CLIENT); - - final BadGatewayException gatewayException = new BadGatewayException("failed to respond."); - when(ctpClient.execute(any(CustomObjectQuery.class))) - .thenReturn(CompletableFutureUtils.failed(gatewayException)) - .thenCallRealMethod(); + final SphereClient ctpClient = spy(CTP_TARGET_CLIENT); + final BadGatewayException gatewayException = new BadGatewayException("failed to respond."); + when(ctpClient.execute(any(CustomObjectQuery.class))) + .thenReturn(CompletableFutureUtils.failed(gatewayException)) + .thenCallRealMethod(); - syncOptions = ProductSyncOptionsBuilder - .of(ctpClient) - .errorCallback((syncException, draft, product, updateActions) - -> collectErrors(syncException.getMessage(), syncException)) + syncOptions = + ProductSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (syncException, draft, product, updateActions) -> + collectErrors(syncException.getMessage(), syncException)) .beforeUpdateCallback(this::collectActions) .build(); - // test - final ProductSync productSync = new ProductSync(syncOptions); + // test + final ProductSync productSync = new ProductSync(syncOptions); - final ProductSyncStatistics syncStatistics = productSync + final ProductSyncStatistics syncStatistics = + productSync .sync(singletonList(childDraft1)) .thenCompose(ignoredResult -> productSync.sync(singletonList(parentDraft))) .toCompletableFuture() .join(); - // assertion - assertThat(syncStatistics).hasValues(2, 1, 0, 1, 0); - assertThat(errorCallBackMessages) - .containsExactly("Failed to fetch ProductDrafts waiting to be resolved with keys '[foo]'."); - assertThat(errorCallBackExceptions) - .hasOnlyOneElementSatisfying(exception -> - assertThat(exception.getCause()).hasCauseExactlyInstanceOf(BadGatewayException.class)); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(actions).isEmpty(); - - final UnresolvedReferencesService unresolvedReferencesService = - new UnresolvedReferencesServiceImpl(syncOptions); - - final Set waitingDrafts = unresolvedReferencesService - .fetch(asSet(childDraft1.getKey())) - .toCompletableFuture() - .join(); - - assertThat(waitingDrafts).containsExactly( - new WaitingToBeResolved(childDraft1, singleton(parentProductKey)) - ); - } + // assertion + assertThat(syncStatistics).hasValues(2, 1, 0, 1, 0); + assertThat(errorCallBackMessages) + .containsExactly("Failed to fetch ProductDrafts waiting to be resolved with keys '[foo]'."); + assertThat(errorCallBackExceptions) + .hasOnlyOneElementSatisfying( + exception -> + assertThat(exception.getCause()) + .hasCauseExactlyInstanceOf(BadGatewayException.class)); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(actions).isEmpty(); + + final UnresolvedReferencesService unresolvedReferencesService = + new UnresolvedReferencesServiceImpl(syncOptions); + + final Set waitingDrafts = + unresolvedReferencesService.fetch(asSet(childDraft1.getKey())).toCompletableFuture().join(); + + assertThat(waitingDrafts) + .containsExactly(new WaitingToBeResolved(childDraft1, singleton(parentProductKey))); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/SyncSingleLocaleIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/SyncSingleLocaleIT.java index 7194701378..0000f41d0c 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/SyncSingleLocaleIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/products/SyncSingleLocaleIT.java @@ -1,5 +1,18 @@ package com.commercetools.sync.integration.externalsource.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.templates.beforeupdatecallback.SyncSingleLocale.syncFrenchDataOnly; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.models.LocalizedString.of; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -26,11 +39,6 @@ import io.sphere.sdk.products.queries.ProductProjectionQuery; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.queries.PagedQueryResult; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -38,112 +46,112 @@ import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.templates.beforeupdatecallback.SyncSingleLocale.syncFrenchDataOnly; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.models.LocalizedString.of; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class SyncSingleLocaleIT { - private static ProductType productType; - private ProductSync productSync; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - private List> updateActionsFromSync; - - /** - * Delete all product related test data from the target project. Then creates price custom types, customer groups, - * channels and a product type for the target CTP project. - */ - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - /** - * Deletes Products from the target CTP project. - */ - @BeforeEach - void setupTest() { - clearSyncTestCollections(); - deleteAllProducts(CTP_TARGET_CLIENT); - productSync = new ProductSync(buildSyncOptions()); - } - - private void clearSyncTestCollections() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - updateActionsFromSync = new ArrayList<>(); - } - - private ProductSyncOptions buildSyncOptions() { - final QuadConsumer, Optional, List>> - errorCallBack = (exception, newResource, oldResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }; - final TriConsumer, Optional> warningCallBack = - (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage()); - - final TriFunction>, ProductDraft, Product, List>> - actionsCallBack = (updateActions, newDraft, oldProduct) -> { - final List> afterCallback = - syncFrenchDataOnly(updateActions, newDraft, oldProduct); - updateActionsFromSync.addAll(afterCallback); - return afterCallback; + private static ProductType productType; + private ProductSync productSync; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + private List> updateActionsFromSync; + + /** + * Delete all product related test data from the target project. Then creates price custom types, + * customer groups, channels and a product type for the target CTP project. + */ + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + /** Deletes Products from the target CTP project. */ + @BeforeEach + void setupTest() { + clearSyncTestCollections(); + deleteAllProducts(CTP_TARGET_CLIENT); + productSync = new ProductSync(buildSyncOptions()); + } + + private void clearSyncTestCollections() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + updateActionsFromSync = new ArrayList<>(); + } + + private ProductSyncOptions buildSyncOptions() { + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallBack = + (exception, newResource, oldResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }; + final TriConsumer, Optional> warningCallBack = + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage()); + + final TriFunction< + List>, ProductDraft, Product, List>> + actionsCallBack = + (updateActions, newDraft, oldProduct) -> { + final List> afterCallback = + syncFrenchDataOnly(updateActions, newDraft, oldProduct); + updateActionsFromSync.addAll(afterCallback); + return afterCallback; }; - return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback(errorCallBack) - .warningCallback(warningCallBack) - .beforeUpdateCallback(actionsCallBack) - .beforeCreateCallback(SyncSingleLocale::filterFrenchLocales) - .build(); - } - - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void sync_withSingleLocaleBeforeUpdateCallback_ShouldSyncCorrectly() { - // Preparation - final LocalizedString nameTarget = ofEnglish("name_target").plus(Locale.FRENCH, "nom_target"); - final LocalizedString nameSource = ofEnglish("name_source").plus(Locale.FRENCH, "nom_source"); - - final LocalizedString slugTarget = ofEnglish("slug_target"); - final LocalizedString slugSource = ofEnglish("slug_source").plus(Locale.FRENCH, "limace_source"); - - final LocalizedString descriptionSource = of(Locale.FRENCH, "description_source"); - final LocalizedString metaDescriptionSource = ofEnglish("md_source"); - final LocalizedString metaKeywordsTarget = ofEnglish("mk_target"); - final LocalizedString metaTitleTarget = of(Locale.FRENCH, "mt_target"); - - - final ProductDraft targetDraft = ProductDraftBuilder - .of(productType.toReference(), nameTarget, slugTarget, + return ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback(errorCallBack) + .warningCallback(warningCallBack) + .beforeUpdateCallback(actionsCallBack) + .beforeCreateCallback(SyncSingleLocale::filterFrenchLocales) + .build(); + } + + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void sync_withSingleLocaleBeforeUpdateCallback_ShouldSyncCorrectly() { + // Preparation + final LocalizedString nameTarget = ofEnglish("name_target").plus(Locale.FRENCH, "nom_target"); + final LocalizedString nameSource = ofEnglish("name_source").plus(Locale.FRENCH, "nom_source"); + + final LocalizedString slugTarget = ofEnglish("slug_target"); + final LocalizedString slugSource = + ofEnglish("slug_source").plus(Locale.FRENCH, "limace_source"); + + final LocalizedString descriptionSource = of(Locale.FRENCH, "description_source"); + final LocalizedString metaDescriptionSource = ofEnglish("md_source"); + final LocalizedString metaKeywordsTarget = ofEnglish("mk_target"); + final LocalizedString metaTitleTarget = of(Locale.FRENCH, "mt_target"); + + final ProductDraft targetDraft = + ProductDraftBuilder.of( + productType.toReference(), + nameTarget, + slugTarget, ProductVariantDraftBuilder.of().key("foo").sku("sku").build()) .key("existingProduct") .metaKeywords(metaKeywordsTarget) .metaTitle(metaTitleTarget) .build(); - executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(targetDraft))); + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(targetDraft))); - final ProductDraft matchingDraft = ProductDraftBuilder - .of(ResourceIdentifier.ofKey(productType.getKey()), nameSource, slugSource, + final ProductDraft matchingDraft = + ProductDraftBuilder.of( + ResourceIdentifier.ofKey(productType.getKey()), + nameSource, + slugSource, ProductVariantDraftBuilder.of().key("foo").sku("sku").build()) .key("existingProduct") .description(descriptionSource) @@ -151,8 +159,11 @@ void sync_withSingleLocaleBeforeUpdateCallback_ShouldSyncCorrectly() { .isPublish(true) .build(); - final ProductDraft nonMatchingDraft = ProductDraftBuilder - .of(ResourceIdentifier.ofKey(productType.getKey()), nameSource, LocalizedString.of(Locale.FRENCH, "foo"), + final ProductDraft nonMatchingDraft = + ProductDraftBuilder.of( + ResourceIdentifier.ofKey(productType.getKey()), + nameSource, + LocalizedString.of(Locale.FRENCH, "foo"), ProductVariantDraftBuilder.of().key("foo-new").sku("sku-new").build()) .key("newProduct") .description(descriptionSource) @@ -160,63 +171,62 @@ void sync_withSingleLocaleBeforeUpdateCallback_ShouldSyncCorrectly() { .metaKeywords(ofEnglish("foo")) .build(); + // test + final ProductSyncStatistics syncStatistics = + executeBlocking(productSync.sync(asList(matchingDraft, nonMatchingDraft))); - // test - final ProductSyncStatistics syncStatistics = - executeBlocking(productSync.sync(asList(matchingDraft, nonMatchingDraft))); - - // assertion - assertThat(syncStatistics).hasValues(2, 1, 1, 0); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); + // assertion + assertThat(syncStatistics).hasValues(2, 1, 1, 0); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); - final LocalizedString expectedUpdatedName = ofEnglish("name_target").plus(Locale.FRENCH, "nom_source"); - final LocalizedString expectedUpdatedSlug = ofEnglish("slug_target").plus(Locale.FRENCH, "limace_source"); - final LocalizedString expectedUpdatedMetaTitle = of(); + final LocalizedString expectedUpdatedName = + ofEnglish("name_target").plus(Locale.FRENCH, "nom_source"); + final LocalizedString expectedUpdatedSlug = + ofEnglish("slug_target").plus(Locale.FRENCH, "limace_source"); + final LocalizedString expectedUpdatedMetaTitle = of(); - assertThat(updateActionsFromSync).containsExactlyInAnyOrder( + assertThat(updateActionsFromSync) + .containsExactlyInAnyOrder( ChangeName.of(expectedUpdatedName), ChangeSlug.of(expectedUpdatedSlug), SetDescription.of(descriptionSource), SetMetaTitle.of(expectedUpdatedMetaTitle), - Publish.of() - ); - - final ProductProjectionQuery productProjectionQuery = ProductProjectionQuery.ofStaged().plusPredicates( - model -> model.masterVariant().sku().isIn(asList("sku", "sku-new"))); - - - final PagedQueryResult pagedQueryResult = CTP_TARGET_CLIENT.execute(productProjectionQuery) - .toCompletableFuture() - .join(); - - final Map projectionMap = - pagedQueryResult.getResults() - .stream() - .collect(Collectors.toMap(ProductProjection::getKey, Function.identity())); - - final ProductProjection updatedProduct = projectionMap.get(matchingDraft.getKey()); - - assertThat(updatedProduct).isNotNull(); - assertThat(updatedProduct.getName()).isEqualTo(expectedUpdatedName); - assertThat(updatedProduct.getSlug()).isEqualTo(expectedUpdatedSlug); - assertThat(updatedProduct.getMetaTitle()).isNull(); - assertThat(updatedProduct.getMetaDescription()).isNull(); - assertThat(updatedProduct.getDescription()).isEqualTo(descriptionSource); - assertThat(updatedProduct.getMetaKeywords()).isEqualTo(metaKeywordsTarget); - - final ProductProjection createdProduct = projectionMap.get(nonMatchingDraft.getKey()); - - final LocalizedString expectedCreatedName = LocalizedString.of(Locale.FRENCH, "nom_source"); - final LocalizedString expectedCreatedSlug = LocalizedString.of(Locale.FRENCH, "foo"); - - assertThat(createdProduct).isNotNull(); - assertThat(createdProduct.getName()).isEqualTo(expectedCreatedName); - assertThat(createdProduct.getSlug()).isEqualTo(expectedCreatedSlug); - assertThat(createdProduct.getMetaTitle()).isNull(); - assertThat(createdProduct.getMetaDescription()).isNull(); - assertThat(createdProduct.getDescription()).isEqualTo(descriptionSource); - assertThat(createdProduct.getMetaKeywords()).isNull(); - } + Publish.of()); + + final ProductProjectionQuery productProjectionQuery = + ProductProjectionQuery.ofStaged() + .plusPredicates(model -> model.masterVariant().sku().isIn(asList("sku", "sku-new"))); + + final PagedQueryResult pagedQueryResult = + CTP_TARGET_CLIENT.execute(productProjectionQuery).toCompletableFuture().join(); + + final Map projectionMap = + pagedQueryResult.getResults().stream() + .collect(Collectors.toMap(ProductProjection::getKey, Function.identity())); + + final ProductProjection updatedProduct = projectionMap.get(matchingDraft.getKey()); + + assertThat(updatedProduct).isNotNull(); + assertThat(updatedProduct.getName()).isEqualTo(expectedUpdatedName); + assertThat(updatedProduct.getSlug()).isEqualTo(expectedUpdatedSlug); + assertThat(updatedProduct.getMetaTitle()).isNull(); + assertThat(updatedProduct.getMetaDescription()).isNull(); + assertThat(updatedProduct.getDescription()).isEqualTo(descriptionSource); + assertThat(updatedProduct.getMetaKeywords()).isEqualTo(metaKeywordsTarget); + + final ProductProjection createdProduct = projectionMap.get(nonMatchingDraft.getKey()); + + final LocalizedString expectedCreatedName = LocalizedString.of(Locale.FRENCH, "nom_source"); + final LocalizedString expectedCreatedSlug = LocalizedString.of(Locale.FRENCH, "foo"); + + assertThat(createdProduct).isNotNull(); + assertThat(createdProduct.getName()).isEqualTo(expectedCreatedName); + assertThat(createdProduct.getSlug()).isEqualTo(expectedCreatedSlug); + assertThat(createdProduct.getMetaTitle()).isNull(); + assertThat(createdProduct.getMetaDescription()).isNull(); + assertThat(createdProduct.getDescription()).isEqualTo(descriptionSource); + assertThat(createdProduct.getMetaKeywords()).isNull(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/producttypes/ProductTypeSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/producttypes/ProductTypeSyncIT.java index f86b3c88ca..f9fca6f42b 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/producttypes/ProductTypeSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/producttypes/ProductTypeSyncIT.java @@ -1,5 +1,30 @@ package com.commercetools.sync.integration.externalsource.producttypes; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_1; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_2; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_3; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_DESCRIPTION_1; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_DESCRIPTION_2; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_KEY_1; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_KEY_2; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_NAME_1; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_NAME_2; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.assertAttributesAreEqual; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.getProductTypeByKey; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateTargetProject; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Lists.emptyList; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.producttypes.ProductTypeSync; @@ -31,11 +56,6 @@ import io.sphere.sdk.producttypes.commands.updateactions.ChangeAttributeOrderByName; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; import io.sphere.sdk.queries.PagedQueryResult; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -43,886 +63,879 @@ import java.util.concurrent.CompletionException; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_1; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_2; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_3; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_DESCRIPTION_1; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_DESCRIPTION_2; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_KEY_1; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_KEY_2; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_NAME_1; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_NAME_2; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.assertAttributesAreEqual; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.getProductTypeByKey; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateTargetProject; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.util.Lists.emptyList; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductTypeSyncIT { - /** - * Deletes product types from the target CTP project. - * Populates target CTP project with test data. - */ - @BeforeEach - void setup() { - deleteProductTypes(CTP_TARGET_CLIENT); - try { - // The removal of the attributes is eventually consistent. - // Here with one second break we are slowing down the ITs a little bit so CTP could remove the attributes. - // see: SUPPORT-8408 - Thread.sleep(1000); - } catch (InterruptedException expected) { } - populateTargetProject(); + /** + * Deletes product types from the target CTP project. Populates target CTP project with test data. + */ + @BeforeEach + void setup() { + deleteProductTypes(CTP_TARGET_CLIENT); + try { + // The removal of the attributes is eventually consistent. + // Here with one second break we are slowing down the ITs a little bit so CTP could remove the + // attributes. + // see: SUPPORT-8408 + Thread.sleep(1000); + } catch (InterruptedException expected) { } - - /** - * Deletes all the test data from the {@code CTP_TARGET_CLIENT} project that - * were set up in this test class. - */ - @AfterAll - static void tearDown() { - deleteProductTypes(CTP_TARGET_CLIENT); - } - - @Test - void sync_WithUpdatedProductType_ShouldUpdateProductType() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + populateTargetProject(); + } + + /** + * Deletes all the test data from the {@code CTP_TARGET_CLIENT} project that were set up in this + * test class. + */ + @AfterAll + static void tearDown() { + deleteProductTypes(CTP_TARGET_CLIENT); + } + + @Test + void sync_WithUpdatedProductType_ShouldUpdateProductType() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_2, PRODUCT_TYPE_DESCRIPTION_2, - singletonList(ATTRIBUTE_DEFINITION_DRAFT_1) - ); + singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture() - .join(); + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - // assertion - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + // assertion + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); - final Optional oldProductTypeAfter = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> { - assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_2); - assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_2); - assertAttributesAreEqual(productType.getAttributes(), singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); - }); - } + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> { + assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_2); + assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_2); + assertAttributesAreEqual( + productType.getAttributes(), singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); + }); + } - @Test - void sync_WithNewProductType_ShouldCreateProductType() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + @Test + void sync_WithNewProductType_ShouldCreateProductType() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_2, PRODUCT_TYPE_NAME_2, PRODUCT_TYPE_DESCRIPTION_2, - singletonList(ATTRIBUTE_DEFINITION_DRAFT_1) - ); - - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - // tests - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + // tests + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - // assertions - assertThat(productTypeSyncStatistics).hasValues(1, 1, 0, 0, 0); + // assertions + assertThat(productTypeSyncStatistics).hasValues(1, 1, 0, 0, 0); + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_2); - final Optional oldProductTypeAfter = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_2); - - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> { - assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_2); - assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_2); - assertAttributesAreEqual(productType.getAttributes(), singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); - }); - } - - @Test - void sync_WithUpdatedProductType_WithNewAttribute_ShouldUpdateProductTypeAddingAttribute() { - // preparation - // Adding ATTRIBUTE_DEFINITION_DRAFT_3 - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> { + assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_2); + assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_2); + assertAttributesAreEqual( + productType.getAttributes(), singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); + }); + } + + @Test + void sync_WithUpdatedProductType_WithNewAttribute_ShouldUpdateProductTypeAddingAttribute() { + // preparation + // Adding ATTRIBUTE_DEFINITION_DRAFT_3 + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, - asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2, ATTRIBUTE_DEFINITION_DRAFT_3) - ); + asList( + ATTRIBUTE_DEFINITION_DRAFT_1, + ATTRIBUTE_DEFINITION_DRAFT_2, + ATTRIBUTE_DEFINITION_DRAFT_3)); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - // tests - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - // assertions - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + // tests + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - final Optional oldProductTypeAfter = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); + // assertions + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> - assertAttributesAreEqual(productType.getAttributes(), - asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2, - ATTRIBUTE_DEFINITION_DRAFT_3) - )); - } + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); - @Test - void sync_WithUpdatedProductType_WithoutOldAttribute_ShouldUpdateProductTypeRemovingAttribute() { - // Removing ATTRIBUTE_DEFINITION_DRAFT_2 - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> + assertAttributesAreEqual( + productType.getAttributes(), + asList( + ATTRIBUTE_DEFINITION_DRAFT_1, + ATTRIBUTE_DEFINITION_DRAFT_2, + ATTRIBUTE_DEFINITION_DRAFT_3))); + } + + @Test + void sync_WithUpdatedProductType_WithoutOldAttribute_ShouldUpdateProductTypeRemovingAttribute() { + // Removing ATTRIBUTE_DEFINITION_DRAFT_2 + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, - singletonList(ATTRIBUTE_DEFINITION_DRAFT_1) - ); - - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); - final Optional oldProductTypeAfter = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> - assertAttributesAreEqual(productType.getAttributes(), singletonList(ATTRIBUTE_DEFINITION_DRAFT_1))); - } + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> + assertAttributesAreEqual( + productType.getAttributes(), singletonList(ATTRIBUTE_DEFINITION_DRAFT_1))); + } - @Test - void sync_WithUpdatedProductType_ChangingAttributeOrder_ShouldUpdateProductTypeChangingAttributeOrder() { - // Changing order from ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2 to - // ATTRIBUTE_DEFINITION_DRAFT_2, ATTRIBUTE_DEFINITION_DRAFT_1 - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + @Test + void + sync_WithUpdatedProductType_ChangingAttributeOrder_ShouldUpdateProductTypeChangingAttributeOrder() { + // Changing order from ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2 to + // ATTRIBUTE_DEFINITION_DRAFT_2, ATTRIBUTE_DEFINITION_DRAFT_1 + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, - asList(ATTRIBUTE_DEFINITION_DRAFT_2, ATTRIBUTE_DEFINITION_DRAFT_1) - ); + asList(ATTRIBUTE_DEFINITION_DRAFT_2, ATTRIBUTE_DEFINITION_DRAFT_1)); - final ArrayList> builtUpdateActions = new ArrayList<>(); + final ArrayList> builtUpdateActions = new ArrayList<>(); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .beforeUpdateCallback((actions, draft, oldProductType) -> { - builtUpdateActions.addAll(actions); - return actions; - }) + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .beforeUpdateCallback( + (actions, draft, oldProductType) -> { + builtUpdateActions.addAll(actions); + return actions; + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); - final Optional oldProductTypeAfter = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> - assertAttributesAreEqual(productType.getAttributes(), - asList( - ATTRIBUTE_DEFINITION_DRAFT_2, - ATTRIBUTE_DEFINITION_DRAFT_1) - )); + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> + assertAttributesAreEqual( + productType.getAttributes(), + asList(ATTRIBUTE_DEFINITION_DRAFT_2, ATTRIBUTE_DEFINITION_DRAFT_1))); - assertThat(builtUpdateActions).containsExactly( + assertThat(builtUpdateActions) + .containsExactly( ChangeAttributeOrderByName.of( - asList(ATTRIBUTE_DEFINITION_DRAFT_2.getName(), ATTRIBUTE_DEFINITION_DRAFT_1.getName()) - )); - } - - @Test - void sync_WithUpdatedAttributeDefinition_ShouldUpdateProductTypeUpdatingAttribute() { - // Updating ATTRIBUTE_DEFINITION_1 (name = "attr_name_1") changing the label, attribute constraint, input tip, - // input hint, isSearchable fields. - final AttributeDefinitionDraft attributeDefinitionDraftUpdated = AttributeDefinitionDraftBuilder - .of( - StringAttributeType.of(), - "attr_name_1", - ofEnglish("attr_label_updated"), - true - ) + asList( + ATTRIBUTE_DEFINITION_DRAFT_2.getName(), + ATTRIBUTE_DEFINITION_DRAFT_1.getName()))); + } + + @Test + void sync_WithUpdatedAttributeDefinition_ShouldUpdateProductTypeUpdatingAttribute() { + // Updating ATTRIBUTE_DEFINITION_1 (name = "attr_name_1") changing the label, attribute + // constraint, input tip, + // input hint, isSearchable fields. + final AttributeDefinitionDraft attributeDefinitionDraftUpdated = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), "attr_name_1", ofEnglish("attr_label_updated"), true) .attributeConstraint(AttributeConstraint.NONE) .inputTip(ofEnglish("inputTip_updated")) .inputHint(TextInputHint.MULTI_LINE) .isSearchable(false) .build(); - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, - singletonList(attributeDefinitionDraftUpdated) - ); + singletonList(attributeDefinitionDraftUpdated)); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); - final Optional oldProductTypeAfter = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> + assertAttributesAreEqual( + productType.getAttributes(), singletonList(attributeDefinitionDraftUpdated))); + } - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> - assertAttributesAreEqual(productType.getAttributes(), singletonList(attributeDefinitionDraftUpdated))); - } - - @Test - void sync_WithoutKey_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // Draft without key throws an error - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + @Test + void sync_WithoutKey_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // Draft without key throws an error + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( null, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, - asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_1) - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_1)); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); + final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + final String expectedErrorMessage = + format( + "ProductTypeDraft with name: %s doesn't have a key. " + + "Please make sure all productType drafts have keys.", + newProductTypeDraft.getName()); + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).isEqualTo(expectedErrorMessage)); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isInstanceOf(SyncException.class); + assertThat(throwable.getMessage()).isEqualTo(expectedErrorMessage); + }); + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } + + @Test + void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final ProductTypeDraft newProductTypeDraft = null; + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .build(); - final String expectedErrorMessage = format("ProductTypeDraft with name: %s doesn't have a key. " - + "Please make sure all productType drafts have keys.", newProductTypeDraft.getName()); - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> assertThat(message).isEqualTo(expectedErrorMessage)); + final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isInstanceOf(SyncException.class); - assertThat(throwable.getMessage()).isEqualTo(expectedErrorMessage); - }); + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).isEqualTo("ProductTypeDraft is null.")); - @Test - void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final ProductTypeDraft newProductTypeDraft = null; - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .build(); - - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo("ProductTypeDraft is null.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isInstanceOf(SyncException.class); - assertThat(throwable.getMessage()).isEqualTo("ProductTypeDraft is null."); + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isInstanceOf(SyncException.class); + assertThat(throwable.getMessage()).isEqualTo("ProductTypeDraft is null."); }); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } - @Test - void sync_WithErrorCreatingTheProductType_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation + @Test + void sync_WithErrorCreatingTheProductType_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation - // Invalid attribute definition due to having the same name as an already existing one but different - // type. - final AttributeDefinitionDraftDsl invalidAttrDefinition = AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_DRAFT_1) + // Invalid attribute definition due to having the same name as an already existing one but + // different + // type. + final AttributeDefinitionDraftDsl invalidAttrDefinition = + AttributeDefinitionDraftBuilder.of(ATTRIBUTE_DEFINITION_DRAFT_1) .attributeType(MoneyAttributeType.of()) .attributeConstraint(AttributeConstraint.COMBINATION_UNIQUE) .build(); - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_2, PRODUCT_TYPE_NAME_2, PRODUCT_TYPE_DESCRIPTION_2, - singletonList(invalidAttrDefinition) - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + singletonList(invalidAttrDefinition)); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to create draft with key: 'key_2'.")); - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to create draft with key: 'key_2'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); - assertThat(throwable).hasMessageContaining("AttributeDefinitionTypeConflict"); + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); + assertThat(throwable).hasMessageContaining("AttributeDefinitionTypeConflict"); }); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } - @Test - void sync_WithErrorUpdatingTheProductType_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation + @Test + void sync_WithErrorUpdatingTheProductType_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation - // Invalid attribute definition due to having an invalid name. - final AttributeDefinitionDraftDsl invalidAttrDefinition = AttributeDefinitionDraftBuilder - .of(MoneyAttributeType.of(), "*invalidName*", ofEnglish("description"), true) + // Invalid attribute definition due to having an invalid name. + final AttributeDefinitionDraftDsl invalidAttrDefinition = + AttributeDefinitionDraftBuilder.of( + MoneyAttributeType.of(), "*invalidName*", ofEnglish("description"), true) .searchable(false) .build(); - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, - singletonList(invalidAttrDefinition) - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + singletonList(invalidAttrDefinition)); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to update product type with key: 'key_1'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); - assertThat(throwable).hasMessageContaining("InvalidInput"); + final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message).contains("Failed to update product type with key: 'key_1'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); + assertThat(throwable).hasMessageContaining("InvalidInput"); }); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } - @Test - void sync_WithoutName_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - // Draft without "name" throws a commercetools exception because "name" is a required value - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + @Test + void sync_WithoutName_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + // Draft without "name" throws a commercetools exception because "name" is a required value + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, null, PRODUCT_TYPE_DESCRIPTION_1, - asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2) - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2)); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to update product type with key: 'key_1'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); - assertThat(throwable).hasMessageContaining("Missing required value"); + final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message).contains("Failed to update product type with key: 'key_1'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); + assertThat(throwable).hasMessageContaining("Missing required value"); }); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } - @Test - void sync_WithoutAttributeType_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of( - null, - "attr_name_1", - ofEnglish("attr_label_1"), - true - ) + @Test + void sync_WithoutAttributeType_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(null, "attr_name_1", ofEnglish("attr_label_1"), true) .attributeConstraint(AttributeConstraint.NONE) .inputTip(ofEnglish("inputTip1")) .inputHint(TextInputHint.SINGLE_LINE) .isSearchable(false) .build(); - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, null, PRODUCT_TYPE_DESCRIPTION_1, - singletonList(attributeDefinitionDraft) - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + singletonList(attributeDefinitionDraft)); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to update product type with key: 'key_1'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); - assertThat(throwable).hasMessageContaining("Missing required value"); + final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message).contains("Failed to update product type with key: 'key_1'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); + assertThat(throwable).hasMessageContaining("Missing required value"); }); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } - @Test - void syncDrafts_WithConcurrentModificationException_ShouldRetryToUpdateNewProductTypeWithSuccess() { - // Preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); + @Test + void + syncDrafts_WithConcurrentModificationException_ShouldRetryToUpdateNewProductTypeWithSuccess() { + // Preparation + final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("key", "foo", "description", emptyList()) - .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("key", "foo", "description", emptyList()).build(); - CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft)).toCompletableFuture().join(); + CTP_TARGET_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft)) + .toCompletableFuture() + .join(); - final String newProductTypeName = "bar"; - final ProductTypeDraft updatedDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .name(newProductTypeName) - .build(); + final String newProductTypeName = "bar"; + final ProductTypeDraft updatedDraft = + ProductTypeDraftBuilder.of(productTypeDraft).name(newProductTypeName).build(); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder.of(spyClient).build(); + final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder.of(spyClient).build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - // Test - final ProductTypeSyncStatistics statistics = productTypeSync.sync(singletonList(updatedDraft)) - .toCompletableFuture() - .join(); + // Test + final ProductTypeSyncStatistics statistics = + productTypeSync.sync(singletonList(updatedDraft)).toCompletableFuture().join(); - // Assertion - assertThat(statistics).hasValues(1, 0, 1, 0, 0); + // Assertion + assertThat(statistics).hasValues(1, 0, 1, 0, 0); - // Assert CTP state. - final PagedQueryResult queryResult = - CTP_TARGET_CLIENT.execute(ProductTypeQuery.of().plusPredicates(queryModel -> - queryModel.key().is(productTypeDraft.getKey()))) - .toCompletableFuture() - .join(); + // Assert CTP state. + final PagedQueryResult queryResult = + CTP_TARGET_CLIENT + .execute( + ProductTypeQuery.of() + .plusPredicates(queryModel -> queryModel.key().is(productTypeDraft.getKey()))) + .toCompletableFuture() + .join(); - assertThat(queryResult.head()).hasValueSatisfying(productType -> - assertThat(productType.getName()).isEqualTo(newProductTypeName)); - } + assertThat(queryResult.head()) + .hasValueSatisfying( + productType -> assertThat(productType.getName()).isEqualTo(newProductTypeName)); + } - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdate() { + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdate() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final ProductTypeUpdateCommand anyProductTypeUpdate = any(ProductTypeUpdateCommand.class); + final ProductTypeUpdateCommand anyProductTypeUpdate = any(ProductTypeUpdateCommand.class); - when(spyClient.execute(anyProductTypeUpdate)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); + when(spyClient.execute(anyProductTypeUpdate)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); - return spyClient; - } + return spyClient; + } - @Test - void syncDrafts_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { - // Preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); + @Test + void syncDrafts_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { + // Preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("key", "foo", "description", emptyList()) - .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("key", "foo", "description", emptyList()).build(); - CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft)).toCompletableFuture().join(); + CTP_TARGET_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft)) + .toCompletableFuture() + .join(); - final String newProductTypeName = "bar"; - final ProductTypeDraft updatedDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .name(newProductTypeName) - .build(); + final String newProductTypeName = "bar"; + final ProductTypeDraft updatedDraft = + ProductTypeDraftBuilder.of(productTypeDraft).name(newProductTypeName).build(); - final List errorMessages = new ArrayList<>(); - final List errors = new ArrayList<>(); + final List errorMessages = new ArrayList<>(); + final List errors = new ArrayList<>(); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errors.add(exception.getCause()); - }) + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errors.add(exception.getCause()); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - - // Test - final ProductTypeSyncStatistics statistics = productTypeSync.sync(singletonList(updatedDraft)) - .toCompletableFuture() - .join(); - - // Assertion - assertThat(statistics).hasValues(1, 0, 0, 1, 0); - - assertThat(errorMessages).hasSize(1); - assertThat(errors).hasSize(1); - - assertThat(errors.get(0).getCause()).isExactlyInstanceOf(BadGatewayException.class); - assertThat(errorMessages.get(0)).contains( - format("Failed to update product type with key: '%s'. Reason: Failed to fetch from CTP while retrying " - + "after concurrency modification.", productTypeDraft.getKey())); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(ProductTypeQuery.class))) - .thenCallRealMethod() // Call real fetch on fetching matching product types - .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); - - final ProductTypeUpdateCommand anyProductTypeUpdate = any(ProductTypeUpdateCommand.class); - - when(spyClient.execute(anyProductTypeUpdate)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - return spyClient; - } - - @Test - void syncDrafts_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { - // Preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); - - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("key", "foo", "description", emptyList()) - .build(); - - CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(productTypeDraft)).toCompletableFuture().join(); - - final String newProductTypeName = "bar"; - final ProductTypeDraft updatedDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .name(newProductTypeName) - .build(); - - final List errorMessages = new ArrayList<>(); - final List errors = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errors.add(exception.getCause()); - }) + final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); + + // Test + final ProductTypeSyncStatistics statistics = + productTypeSync.sync(singletonList(updatedDraft)).toCompletableFuture().join(); + + // Assertion + assertThat(statistics).hasValues(1, 0, 0, 1, 0); + + assertThat(errorMessages).hasSize(1); + assertThat(errors).hasSize(1); + + assertThat(errors.get(0).getCause()).isExactlyInstanceOf(BadGatewayException.class); + assertThat(errorMessages.get(0)) + .contains( + format( + "Failed to update product type with key: '%s'. Reason: Failed to fetch from CTP while retrying " + + "after concurrency modification.", + productTypeDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { + + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(ProductTypeQuery.class))) + .thenCallRealMethod() // Call real fetch on fetching matching product types + .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); + + final ProductTypeUpdateCommand anyProductTypeUpdate = any(ProductTypeUpdateCommand.class); + + when(spyClient.execute(anyProductTypeUpdate)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); + + return spyClient; + } + + @Test + void + syncDrafts_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { + // Preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); + + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("key", "foo", "description", emptyList()).build(); + + CTP_TARGET_CLIENT + .execute(ProductTypeCreateCommand.of(productTypeDraft)) + .toCompletableFuture() + .join(); + + final String newProductTypeName = "bar"; + final ProductTypeDraft updatedDraft = + ProductTypeDraftBuilder.of(productTypeDraft).name(newProductTypeName).build(); + + final List errorMessages = new ArrayList<>(); + final List errors = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errors.add(exception.getCause()); + }) .build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); - - // Test - final ProductTypeSyncStatistics statistics = productTypeSync.sync(singletonList(updatedDraft)) - .toCompletableFuture() - .join(); - - // Assertion - assertThat(statistics).hasValues(1, 0, 0, 1, 0); - - assertThat(errorMessages).hasSize(1); - assertThat(errors).hasSize(1); - assertThat(errorMessages.get(0)).contains( - format("Failed to update product type with key: '%s'. Reason: Not found when attempting to fetch while " - + "retrying after concurrency modification.", productTypeDraft.getKey())); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final ProductTypeQuery anyProductTypeQuery = any(ProductTypeQuery.class); - - when(spyClient.execute(anyProductTypeQuery)) - .thenCallRealMethod() // Call real fetch on fetching matching product types - .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); - - - final ProductTypeUpdateCommand anyProductTypeUpdateCmd = any(ProductTypeUpdateCommand.class); - when(spyClient.execute(anyProductTypeUpdateCmd)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())); - - - return spyClient; - } - - @Test - void sync_WithSeveralBatches_ShouldReturnProperStatistics() { - // Default batch size is 50 (check ProductTypeSyncOptionsBuilder) so we have 2 batches of 50 - final List productTypeDrafts = IntStream - .range(0, 100) - .mapToObj(i -> ProductTypeDraft.ofAttributeDefinitionDrafts( - "product_type_key_" + Integer.toString(i), - "product_type_name_" + Integer.toString(i), - "product_type_description_" + Integer.toString(i), - singletonList(ATTRIBUTE_DEFINITION_DRAFT_1) - )) + final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions); + + // Test + final ProductTypeSyncStatistics statistics = + productTypeSync.sync(singletonList(updatedDraft)).toCompletableFuture().join(); + + // Assertion + assertThat(statistics).hasValues(1, 0, 0, 1, 0); + + assertThat(errorMessages).hasSize(1); + assertThat(errors).hasSize(1); + assertThat(errorMessages.get(0)) + .contains( + format( + "Failed to update product type with key: '%s'. Reason: Not found when attempting to fetch while " + + "retrying after concurrency modification.", + productTypeDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final ProductTypeQuery anyProductTypeQuery = any(ProductTypeQuery.class); + + when(spyClient.execute(anyProductTypeQuery)) + .thenCallRealMethod() // Call real fetch on fetching matching product types + .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); + + final ProductTypeUpdateCommand anyProductTypeUpdateCmd = any(ProductTypeUpdateCommand.class); + when(spyClient.execute(anyProductTypeUpdateCmd)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())); + + return spyClient; + } + + @Test + void sync_WithSeveralBatches_ShouldReturnProperStatistics() { + // Default batch size is 50 (check ProductTypeSyncOptionsBuilder) so we have 2 batches of 50 + final List productTypeDrafts = + IntStream.range(0, 100) + .mapToObj( + i -> + ProductTypeDraft.ofAttributeDefinitionDrafts( + "product_type_key_" + Integer.toString(i), + "product_type_name_" + Integer.toString(i), + "product_type_description_" + Integer.toString(i), + singletonList(ATTRIBUTE_DEFINITION_DRAFT_1))) .collect(Collectors.toList()); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(productTypeDrafts) - .toCompletableFuture().join(); + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(productTypeDrafts).toCompletableFuture().join(); - assertThat(productTypeSyncStatistics).hasValues(100, 100, 0, 0, 0); - } + assertThat(productTypeSyncStatistics).hasValues(100, 100, 0, 0, 0); + } - @Test - void sync_WithSetOfEnumsAndSetOfLenumsChanges_ShouldUpdateProductType() { - // preparation - final AttributeDefinitionDraft withSetOfEnumsOld = AttributeDefinitionDraftBuilder - .of( - SetAttributeType.of(EnumAttributeType.of( - asList( - EnumValue.of("d", "d"), - EnumValue.of("b", "newB"), - EnumValue.of("a", "a"), - EnumValue.of("c", "c") - ))), + @Test + void sync_WithSetOfEnumsAndSetOfLenumsChanges_ShouldUpdateProductType() { + // preparation + final AttributeDefinitionDraft withSetOfEnumsOld = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of( + EnumAttributeType.of( + asList( + EnumValue.of("d", "d"), + EnumValue.of("b", "newB"), + EnumValue.of("a", "a"), + EnumValue.of("c", "c")))), "foo", ofEnglish("foo"), - false - ) + false) .build(); - final AttributeDefinitionDraft withSetOfSetOfLEnumsOld = AttributeDefinitionDraftBuilder - .of( + final AttributeDefinitionDraft withSetOfSetOfLEnumsOld = + AttributeDefinitionDraftBuilder.of( SetAttributeType.of( LocalizedEnumAttributeType.of( asList( LocalizedEnumValue.of("d", ofEnglish("d")), LocalizedEnumValue.of("b", ofEnglish("newB")), LocalizedEnumValue.of("a", ofEnglish("a")), - LocalizedEnumValue.of("c", ofEnglish("c")) - ))), + LocalizedEnumValue.of("c", ofEnglish("c"))))), "bar", ofEnglish("bar"), - false - ) + false) .build(); - final ProductTypeDraft oldDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft oldDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( "withSetOfEnums", "withSetOfEnums", "withSetOfEnums", - asList(withSetOfEnumsOld, withSetOfSetOfLEnumsOld) - ); - - - CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(oldDraft)).toCompletableFuture().join(); + asList(withSetOfEnumsOld, withSetOfSetOfLEnumsOld)); + CTP_TARGET_CLIENT.execute(ProductTypeCreateCommand.of(oldDraft)).toCompletableFuture().join(); - final AttributeDefinitionDraft withSetOfEnumsNew = AttributeDefinitionDraftBuilder - .of( - SetAttributeType.of(EnumAttributeType.of( - asList( - EnumValue.of("a", "a"), - EnumValue.of("b", "b"), - EnumValue.of("c", "c") - ))), + final AttributeDefinitionDraft withSetOfEnumsNew = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of( + EnumAttributeType.of( + asList( + EnumValue.of("a", "a"), + EnumValue.of("b", "b"), + EnumValue.of("c", "c")))), "foo", ofEnglish("foo"), - false - ) + false) .build(); - final AttributeDefinitionDraft withSetOfSetOfLEnumsNew = AttributeDefinitionDraftBuilder - .of( + final AttributeDefinitionDraft withSetOfSetOfLEnumsNew = + AttributeDefinitionDraftBuilder.of( SetAttributeType.of( LocalizedEnumAttributeType.of( asList( LocalizedEnumValue.of("a", ofEnglish("a")), LocalizedEnumValue.of("b", ofEnglish("newB")), - LocalizedEnumValue.of("c", ofEnglish("c")) - ))), + LocalizedEnumValue.of("c", ofEnglish("c"))))), "bar", ofEnglish("bar"), - false - ) + false) .build(); - - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( "withSetOfEnums", "withSetOfEnums", "withSetOfEnums", - asList(withSetOfEnumsNew, withSetOfSetOfLEnumsNew) - ); + asList(withSetOfEnumsNew, withSetOfSetOfLEnumsNew)); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - // tests - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); + // tests + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - // assertions - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + // assertions + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); - final Optional oldProductTypeAfter = getProductTypeByKey(CTP_TARGET_CLIENT, "withSetOfEnums"); + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, "withSetOfEnums"); - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> - assertAttributesAreEqual(productType.getAttributes(), - asList(withSetOfEnumsNew, withSetOfSetOfLEnumsNew) - )); - } + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> + assertAttributesAreEqual( + productType.getAttributes(), + asList(withSetOfEnumsNew, withSetOfSetOfLEnumsNew))); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/producttypes/ProductTypeWithNestedAttributeSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/producttypes/ProductTypeWithNestedAttributeSyncIT.java index 48e0b22e9c..1b02cce86e 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/producttypes/ProductTypeWithNestedAttributeSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/producttypes/ProductTypeWithNestedAttributeSyncIT.java @@ -1,35 +1,5 @@ package com.commercetools.sync.integration.externalsource.producttypes; - -import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; -import com.commercetools.sync.producttypes.ProductTypeSync; -import com.commercetools.sync.producttypes.ProductTypeSyncOptions; -import com.commercetools.sync.producttypes.ProductTypeSyncOptionsBuilder; -import com.commercetools.sync.producttypes.helpers.ProductTypeSyncStatistics; -import io.sphere.sdk.client.BadGatewayException; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.models.Resource; -import io.sphere.sdk.products.attributes.AttributeDefinition; -import io.sphere.sdk.products.attributes.AttributeDefinitionBuilder; -import io.sphere.sdk.products.attributes.AttributeDefinitionDraft; -import io.sphere.sdk.products.attributes.AttributeDefinitionDraftBuilder; -import io.sphere.sdk.products.attributes.NestedAttributeType; -import io.sphere.sdk.products.attributes.SetAttributeType; -import io.sphere.sdk.producttypes.ProductType; -import io.sphere.sdk.producttypes.ProductTypeDraft; -import io.sphere.sdk.producttypes.commands.updateactions.AddAttributeDefinition; -import io.sphere.sdk.producttypes.commands.updateactions.RemoveAttributeDefinition; -import io.sphere.sdk.producttypes.queries.ProductTypeQuery; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_1; import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_2; @@ -44,9 +14,9 @@ import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_NAME_3; import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_NAME_4; import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.assertAttributesAreEqual; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.removeAttributeReferencesAndDeleteProductTypes; import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.getProductTypeByKey; import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.populateTargetProjectWithNestedAttributes; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.removeAttributeReferencesAndDeleteProductTypes; import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; import static io.sphere.sdk.models.LocalizedString.ofEnglish; import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; @@ -58,488 +28,592 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; +import com.commercetools.sync.producttypes.ProductTypeSync; +import com.commercetools.sync.producttypes.ProductTypeSyncOptions; +import com.commercetools.sync.producttypes.ProductTypeSyncOptionsBuilder; +import com.commercetools.sync.producttypes.helpers.ProductTypeSyncStatistics; +import io.sphere.sdk.client.BadGatewayException; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.models.Resource; +import io.sphere.sdk.products.attributes.AttributeDefinition; +import io.sphere.sdk.products.attributes.AttributeDefinitionBuilder; +import io.sphere.sdk.products.attributes.AttributeDefinitionDraft; +import io.sphere.sdk.products.attributes.AttributeDefinitionDraftBuilder; +import io.sphere.sdk.products.attributes.NestedAttributeType; +import io.sphere.sdk.products.attributes.SetAttributeType; +import io.sphere.sdk.producttypes.ProductType; +import io.sphere.sdk.producttypes.ProductTypeDraft; +import io.sphere.sdk.producttypes.commands.updateactions.AddAttributeDefinition; +import io.sphere.sdk.producttypes.commands.updateactions.RemoveAttributeDefinition; +import io.sphere.sdk.producttypes.queries.ProductTypeQuery; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class ProductTypeWithNestedAttributeSyncIT { - private ProductTypeSyncOptions productTypeSyncOptions; - private List> builtUpdateActions; - private List errorMessages; - private List exceptions; - - /** - * Deletes product types from the target CTP project. - * Populates target CTP project with test data. - */ - @BeforeEach - void setup() { - removeAttributeReferencesAndDeleteProductTypes(CTP_TARGET_CLIENT); - populateTargetProjectWithNestedAttributes(); - - builtUpdateActions = new ArrayList<>(); - errorMessages = new ArrayList<>(); - exceptions = new ArrayList<>(); - - productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .beforeUpdateCallback((actions, draft, oldProductType) -> { - builtUpdateActions.addAll(actions); - return actions; - }) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + private ProductTypeSyncOptions productTypeSyncOptions; + private List> builtUpdateActions; + private List errorMessages; + private List exceptions; + + /** + * Deletes product types from the target CTP project. Populates target CTP project with test data. + */ + @BeforeEach + void setup() { + removeAttributeReferencesAndDeleteProductTypes(CTP_TARGET_CLIENT); + populateTargetProjectWithNestedAttributes(); + + builtUpdateActions = new ArrayList<>(); + errorMessages = new ArrayList<>(); + exceptions = new ArrayList<>(); + + productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .beforeUpdateCallback( + (actions, draft, oldProductType) -> { + builtUpdateActions.addAll(actions); + return actions; + }) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - } - - /** - * Deletes all the test data from the {@code CTP_TARGET_CLIENT} project that - * were set up in this test class. - */ - @AfterAll - static void tearDown() { - removeAttributeReferencesAndDeleteProductTypes(CTP_TARGET_CLIENT); - } - - @Test - void sync_WithUpdatedProductType_ShouldUpdateProductType() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - PRODUCT_TYPE_KEY_3, - PRODUCT_TYPE_NAME_3, - PRODUCT_TYPE_DESCRIPTION_3, - emptyList() - ); - - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).containsExactly( + } + + /** + * Deletes all the test data from the {@code CTP_TARGET_CLIENT} project that were set up in this + * test class. + */ + @AfterAll + static void tearDown() { + removeAttributeReferencesAndDeleteProductTypes(CTP_TARGET_CLIENT); + } + + @Test + void sync_WithUpdatedProductType_ShouldUpdateProductType() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( + PRODUCT_TYPE_KEY_3, PRODUCT_TYPE_NAME_3, PRODUCT_TYPE_DESCRIPTION_3, emptyList()); + + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertion + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions) + .containsExactly( RemoveAttributeDefinition.of("nestedattr"), - RemoveAttributeDefinition.of("nestedattr2") - ); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 product types were processed in total" + RemoveAttributeDefinition.of("nestedattr2")); + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 product types were processed in total" + " (0 created, 1 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - final Optional oldProductTypeAfter = - getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_3); - - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> { - assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_3); - assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_3); - assertAttributesAreEqual(productType.getAttributes(), emptyList()); - }); - } - - @Test - void sync_WithNewProductTypeWithAnExistingReference_ShouldCreateProductType() { - // preparation - final AttributeDefinitionDraft nestedTypeAttr = AttributeDefinitionDraftBuilder - .of(AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), - NestedAttributeType.of(ProductType.referenceOfId(PRODUCT_TYPE_KEY_1))) - .build()) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets it to + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_3); + + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> { + assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_3); + assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_3); + assertAttributesAreEqual(productType.getAttributes(), emptyList()); + }); + } + + @Test + void sync_WithNewProductTypeWithAnExistingReference_ShouldCreateProductType() { + // preparation + final AttributeDefinitionDraft nestedTypeAttr = + AttributeDefinitionDraftBuilder.of( + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), + NestedAttributeType.of(ProductType.referenceOfId(PRODUCT_TYPE_KEY_1))) + .build()) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets it to // true by default .searchable(false) .build(); - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - PRODUCT_TYPE_KEY_4, - PRODUCT_TYPE_NAME_4, - PRODUCT_TYPE_DESCRIPTION_4, - singletonList(nestedTypeAttr) - ); - - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // tests - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture() - .join(); - - // assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).isEmpty(); - assertThat(productTypeSyncStatistics).hasValues(1, 1, 0, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 product types were processed in total" + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( + PRODUCT_TYPE_KEY_4, + PRODUCT_TYPE_NAME_4, + PRODUCT_TYPE_DESCRIPTION_4, + singletonList(nestedTypeAttr)); + + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + + // tests + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions).isEmpty(); + assertThat(productTypeSyncStatistics).hasValues(1, 1, 0, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 product types were processed in total" + " (1 created, 0 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - final Optional oldProductTypeAfter = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); - - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> { - assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); - assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); - assertThat(productType.getAttributes()) - .hasSize(1) - .extracting(AttributeDefinition::getAttributeType) - .first() - .satisfies(attributeType -> { - assertThat(attributeType).isInstanceOf(NestedAttributeType.class); - final NestedAttributeType nestedType = (NestedAttributeType) attributeType; - assertThat(nestedType.getTypeReference().getId()) - .isEqualTo( - getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1) - .map(Resource::getId) - .orElse(null)); - }); - }); - } - - @Test - void sync_WithNewProductTypeWithANonExistingReference_ShouldCreateProductType() { - // preparation - final AttributeDefinitionDraft nestedTypeAttr = AttributeDefinitionDraftBuilder - .of(AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), - SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("non-existing-ref")))) - .build()) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets it to + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); + + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> { + assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); + assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); + assertThat(productType.getAttributes()) + .hasSize(1) + .extracting(AttributeDefinition::getAttributeType) + .first() + .satisfies( + attributeType -> { + assertThat(attributeType).isInstanceOf(NestedAttributeType.class); + final NestedAttributeType nestedType = (NestedAttributeType) attributeType; + assertThat(nestedType.getTypeReference().getId()) + .isEqualTo( + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1) + .map(Resource::getId) + .orElse(null)); + }); + }); + } + + @Test + void sync_WithNewProductTypeWithANonExistingReference_ShouldCreateProductType() { + // preparation + final AttributeDefinitionDraft nestedTypeAttr = + AttributeDefinitionDraftBuilder.of( + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), + SetAttributeType.of( + NestedAttributeType.of(ProductType.referenceOfId("non-existing-ref")))) + .build()) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets it to // true by default .searchable(false) .build(); - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_4, PRODUCT_TYPE_NAME_4, PRODUCT_TYPE_DESCRIPTION_4, - singletonList(nestedTypeAttr) - ); - - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // tests - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture() - .join(); - - // assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).isEmpty(); - assertThat(productTypeSyncStatistics).hasValues(1, 1, 0, 0, 1); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 product types were processed in total" + singletonList(nestedTypeAttr)); + + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + + // tests + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions).isEmpty(); + assertThat(productTypeSyncStatistics).hasValues(1, 1, 0, 0, 1); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 product types were processed in total" + " (1 created, 0 updated, 0 failed to sync and 1 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("non-existing-ref")) - .containsOnlyKeys(PRODUCT_TYPE_KEY_4); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents() - .get("non-existing-ref") - .get(PRODUCT_TYPE_KEY_4)).containsExactly(nestedTypeAttr); - - final Optional oldProductTypeAfter = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); - - assertThat(oldProductTypeAfter).hasValueSatisfying(productType -> { - assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); - assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); - assertThat(productType.getAttributes()).isEmpty(); - }); - } - - @Test - void sync_WithNewProductTypeWithFailedFetchOnReferenceResolution_ShouldFail() { - // preparation - final AttributeDefinitionDraft nestedTypeAttr = AttributeDefinitionDraftBuilder - .of(AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), - NestedAttributeType.of(ProductType.referenceOfId(PRODUCT_TYPE_KEY_4))) - .build()) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets it to + assertThat( + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get("non-existing-ref")) + .containsOnlyKeys(PRODUCT_TYPE_KEY_4); + assertThat( + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get("non-existing-ref") + .get(PRODUCT_TYPE_KEY_4)) + .containsExactly(nestedTypeAttr); + + final Optional oldProductTypeAfter = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); + + assertThat(oldProductTypeAfter) + .hasValueSatisfying( + productType -> { + assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); + assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); + assertThat(productType.getAttributes()).isEmpty(); + }); + } + + @Test + void sync_WithNewProductTypeWithFailedFetchOnReferenceResolution_ShouldFail() { + // preparation + final AttributeDefinitionDraft nestedTypeAttr = + AttributeDefinitionDraftBuilder.of( + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), + NestedAttributeType.of(ProductType.referenceOfId(PRODUCT_TYPE_KEY_4))) + .build()) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets it to // true by default .searchable(false) .build(); - final ProductTypeDraft withMissingNestedTypeRef = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft withMissingNestedTypeRef = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2, nestedTypeAttr)); - final ProductTypeDraft productTypeDraft4 = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft productTypeDraft4 = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_4, PRODUCT_TYPE_NAME_4, PRODUCT_TYPE_DESCRIPTION_4, singletonList(ATTRIBUTE_DEFINITION_DRAFT_3)); - final SphereClient ctpClient = spy(CTP_TARGET_CLIENT); - final BadGatewayException badGatewayException = new BadGatewayException(); - when(ctpClient.execute(any(ResourceKeyIdGraphQlRequest.class))).thenCallRealMethod(); // should work on caching - when(ctpClient.execute(any(ProductTypeQuery.class))) - .thenCallRealMethod() // should work when fetching matching product types - .thenCallRealMethod() // should work when second fetching matching product types - .thenReturn(exceptionallyCompletedFuture(badGatewayException)) // fail on fetching during resolution - .thenCallRealMethod(); // call the real method for the rest of the calls - - productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(ctpClient) + final SphereClient ctpClient = spy(CTP_TARGET_CLIENT); + final BadGatewayException badGatewayException = new BadGatewayException(); + when(ctpClient.execute(any(ResourceKeyIdGraphQlRequest.class))) + .thenCallRealMethod(); // should work on caching + when(ctpClient.execute(any(ProductTypeQuery.class))) + .thenCallRealMethod() // should work when fetching matching product types + .thenCallRealMethod() // should work when second fetching matching product types + .thenReturn( + exceptionallyCompletedFuture(badGatewayException)) // fail on fetching during resolution + .thenCallRealMethod(); // call the real method for the rest of the calls + + productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(ctpClient) .batchSize(1) // this ensures the drafts are in separate batches. - .beforeUpdateCallback((actions, draft, oldProductType) -> { - builtUpdateActions.addAll(actions); - return actions; - }) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + .beforeUpdateCallback( + (actions, draft, oldProductType) -> { + builtUpdateActions.addAll(actions); + return actions; + }) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // tests - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync + // tests + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync .sync(asList(withMissingNestedTypeRef, productTypeDraft4)) - .toCompletableFuture().join(); - - // assertions - final Optional productType1 = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); - assert productType1.isPresent(); - assertThat(errorMessages).containsExactly("Failed to fetch existing product types with keys: '[key_1]'."); - assertThat(exceptions).hasOnlyOneElementSatisfying(exception -> - assertThat(exception.getCause()).hasCauseExactlyInstanceOf(BadGatewayException.class)); - assertThat(builtUpdateActions).isEmpty(); - assertThat(productTypeSyncStatistics).hasValues(2, 1, 0, 0, 1); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 product types were processed in total" + .toCompletableFuture() + .join(); + + // assertions + final Optional productType1 = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); + assert productType1.isPresent(); + assertThat(errorMessages) + .containsExactly("Failed to fetch existing product types with keys: '[key_1]'."); + assertThat(exceptions) + .hasOnlyOneElementSatisfying( + exception -> + assertThat(exception.getCause()) + .hasCauseExactlyInstanceOf(BadGatewayException.class)); + assertThat(builtUpdateActions).isEmpty(); + assertThat(productTypeSyncStatistics).hasValues(2, 1, 0, 0, 1); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 product types were processed in total" + " (1 created, 0 updated, 0 failed to sync and 1 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); - final ConcurrentHashMap> - children = productTypeSyncStatistics - .getProductTypeKeysWithMissingParents().get(PRODUCT_TYPE_KEY_4); - - assertThat(children).hasSize(1); - assertThat(children.get(PRODUCT_TYPE_KEY_1)).containsExactly(nestedTypeAttr); - - final Optional productType4 = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); - assertThat(productType4).hasValueSatisfying(productType -> { - assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); - assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); - assertThat(productType.getAttributes()).hasSize(1); - }); - - assertThat(productType1).hasValueSatisfying(productType -> assertThat(productType.getAttributes()).hasSize(2)); - } - - @Test - void sync_WithUpdatedProductType_WithNewNestedAttributeInSameBatch_ShouldUpdateProductTypeAddingAttribute() { - // preparation - final AttributeDefinitionDraft nestedTypeAttr = AttributeDefinitionDraftBuilder - .of(AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), - NestedAttributeType.of(ProductType.referenceOfId(PRODUCT_TYPE_KEY_1))) - .build()) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets it to + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); + final ConcurrentHashMap> + children = + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get(PRODUCT_TYPE_KEY_4); + + assertThat(children).hasSize(1); + assertThat(children.get(PRODUCT_TYPE_KEY_1)).containsExactly(nestedTypeAttr); + + final Optional productType4 = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); + assertThat(productType4) + .hasValueSatisfying( + productType -> { + assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); + assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); + assertThat(productType.getAttributes()).hasSize(1); + }); + + assertThat(productType1) + .hasValueSatisfying(productType -> assertThat(productType.getAttributes()).hasSize(2)); + } + + @Test + void + sync_WithUpdatedProductType_WithNewNestedAttributeInSameBatch_ShouldUpdateProductTypeAddingAttribute() { + // preparation + final AttributeDefinitionDraft nestedTypeAttr = + AttributeDefinitionDraftBuilder.of( + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), + NestedAttributeType.of(ProductType.referenceOfId(PRODUCT_TYPE_KEY_1))) + .build()) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets it to // true by default .searchable(false) .build(); - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - PRODUCT_TYPE_KEY_1, - PRODUCT_TYPE_NAME_1, - PRODUCT_TYPE_DESCRIPTION_1, - asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2, nestedTypeAttr)); + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( + PRODUCT_TYPE_KEY_1, + PRODUCT_TYPE_NAME_1, + PRODUCT_TYPE_DESCRIPTION_1, + asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2, nestedTypeAttr)); - final ProductTypeDraft productTypeDraft4 = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft productTypeDraft4 = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_4, PRODUCT_TYPE_NAME_4, PRODUCT_TYPE_DESCRIPTION_4, singletonList(ATTRIBUTE_DEFINITION_DRAFT_3)); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // tests - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(asList(newProductTypeDraft, productTypeDraft4)) - .toCompletableFuture().join(); + // tests + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync + .sync(asList(newProductTypeDraft, productTypeDraft4)) + .toCompletableFuture() + .join(); - // assertions - final Optional productType1 = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); - assert productType1.isPresent(); - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).containsExactly( - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), - NestedAttributeType.of(productType1.get())) - .build()) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets - // it to true by default - .searchable(false) - .build()) - ); - assertThat(productTypeSyncStatistics).hasValues(2, 1, 1, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 product types were processed in total" + // assertions + final Optional productType1 = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); + assert productType1.isPresent(); + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions) + .containsExactly( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), + NestedAttributeType.of(productType1.get())) + .build()) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets + // it to true by default + .searchable(false) + .build())); + assertThat(productTypeSyncStatistics).hasValues(2, 1, 1, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 product types were processed in total" + " (1 created, 1 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).isEmpty(); - final Optional productType4 = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); - assertThat(productType4).hasValueSatisfying(productType -> { - assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); - assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); - assertThat(productType.getAttributes()).hasSize(1); - }); - - assertThat(productType1).hasValueSatisfying(productType -> assertThat(productType.getAttributes()).hasSize(3)); - } - - @Test - void sync_WithUpdatedProductType_WithNewNestedAttributeInSeparateBatch_ShouldUpdateProductTypeAddingAttribute() { - // preparation - final AttributeDefinitionDraft nestedTypeAttr = AttributeDefinitionDraftBuilder - .of(AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), - NestedAttributeType.of(ProductType.referenceOfId(PRODUCT_TYPE_KEY_1))) - .build()) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets it to + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).isEmpty(); + final Optional productType4 = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); + assertThat(productType4) + .hasValueSatisfying( + productType -> { + assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); + assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); + assertThat(productType.getAttributes()).hasSize(1); + }); + + assertThat(productType1) + .hasValueSatisfying(productType -> assertThat(productType.getAttributes()).hasSize(3)); + } + + @Test + void + sync_WithUpdatedProductType_WithNewNestedAttributeInSeparateBatch_ShouldUpdateProductTypeAddingAttribute() { + // preparation + final AttributeDefinitionDraft nestedTypeAttr = + AttributeDefinitionDraftBuilder.of( + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), + NestedAttributeType.of(ProductType.referenceOfId(PRODUCT_TYPE_KEY_1))) + .build()) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets it to // true by default .searchable(false) .build(); - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2, nestedTypeAttr)); - final ProductTypeDraft productTypeDraft4 = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft productTypeDraft4 = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_4, PRODUCT_TYPE_NAME_4, PRODUCT_TYPE_DESCRIPTION_4, singletonList(ATTRIBUTE_DEFINITION_DRAFT_3)); - productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) .batchSize(1) - .beforeUpdateCallback((actions, draft, oldProductType) -> { - builtUpdateActions.addAll(actions); - return actions; - }) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + .beforeUpdateCallback( + (actions, draft, oldProductType) -> { + builtUpdateActions.addAll(actions); + return actions; + }) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - - // tests - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync + // tests + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync .sync(asList(newProductTypeDraft, productTypeDraft4)) - .toCompletableFuture().join(); - - // assertions - final Optional productType1 = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); - assert productType1.isPresent(); - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).containsExactly( - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), - NestedAttributeType.of(productType1.get())) - .build()) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets - // it to true by default - .searchable(false) - .build()) - ); - assertThat(productTypeSyncStatistics).hasValues(2, 1, 1, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 product types were processed in total" + .toCompletableFuture() + .join(); + + // assertions + final Optional productType1 = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); + assert productType1.isPresent(); + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions) + .containsExactly( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), + NestedAttributeType.of(productType1.get())) + .build()) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets + // it to true by default + .searchable(false) + .build())); + assertThat(productTypeSyncStatistics).hasValues(2, 1, 1, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 product types were processed in total" + " (1 created, 1 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).isEmpty(); - final Optional productType4 = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); - assertThat(productType4).hasValueSatisfying(productType -> { - assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); - assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); - assertThat(productType.getAttributes()).hasSize(1); - }); - - assertThat(productType1).hasValueSatisfying(productType -> assertThat(productType.getAttributes()).hasSize(3)); - } - - @Test - void sync_WithUpdatedProductType_WithRemovedNestedAttributeInLaterBatch_ShouldReturnProperStatistics() { - // preparation - final AttributeDefinitionDraft nestedTypeAttr = AttributeDefinitionDraftBuilder - .of(AttributeDefinitionBuilder - .of("newNested", ofEnglish("nestedattr"), - NestedAttributeType.of(ProductType.referenceOfId("non-existing-product-type"))) - .build()) - // isSearchable=true is not supported for attribute type 'nested' and AttributeDefinitionBuilder sets it to + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).isEmpty(); + final Optional productType4 = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_4); + assertThat(productType4) + .hasValueSatisfying( + productType -> { + assertThat(productType.getName()).isEqualTo(PRODUCT_TYPE_NAME_4); + assertThat(productType.getDescription()).isEqualTo(PRODUCT_TYPE_DESCRIPTION_4); + assertThat(productType.getAttributes()).hasSize(1); + }); + + assertThat(productType1) + .hasValueSatisfying(productType -> assertThat(productType.getAttributes()).hasSize(3)); + } + + @Test + void + sync_WithUpdatedProductType_WithRemovedNestedAttributeInLaterBatch_ShouldReturnProperStatistics() { + // preparation + final AttributeDefinitionDraft nestedTypeAttr = + AttributeDefinitionDraftBuilder.of( + AttributeDefinitionBuilder.of( + "newNested", + ofEnglish("nestedattr"), + NestedAttributeType.of( + ProductType.referenceOfId("non-existing-product-type"))) + .build()) + // isSearchable=true is not supported for attribute type 'nested' and + // AttributeDefinitionBuilder sets it to // true by default .searchable(false) .build(); - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2, nestedTypeAttr)); - final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); + final ProductTypeSync productTypeSync = new ProductTypeSync(productTypeSyncOptions); - // tests - productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); + // tests + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - final ProductTypeDraft newProductTypeDraftWithoutNested = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeDraft newProductTypeDraftWithoutNested = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, asList(ATTRIBUTE_DEFINITION_DRAFT_1, ATTRIBUTE_DEFINITION_DRAFT_2)); - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync .sync(singletonList(newProductTypeDraftWithoutNested)) - .toCompletableFuture().join(); - - // assertions - final Optional productType1 = getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); - assert productType1.isPresent(); - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(builtUpdateActions).isEmpty(); - assertThat(productTypeSyncStatistics).hasValues(2, 0, 0, 0, 0); - assertThat(productTypeSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 2 product types were processed in total" + .toCompletableFuture() + .join(); + + // assertions + final Optional productType1 = + getProductTypeByKey(CTP_TARGET_CLIENT, PRODUCT_TYPE_KEY_1); + assert productType1.isPresent(); + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(builtUpdateActions).isEmpty(); + assertThat(productTypeSyncStatistics).hasValues(2, 0, 0, 0, 0); + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 2 product types were processed in total" + " (0 created, 0 updated, 0 failed to sync and 0 product types with at least one NestedType or a Set" + " of NestedType attribute definition(s) referencing a missing product type)."); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).isEmpty(); - assertThat(productType1).hasValueSatisfying(productType -> assertThat(productType.getAttributes()).hasSize(2)); - } + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).isEmpty(); + assertThat(productType1) + .hasValueSatisfying(productType -> assertThat(productType.getAttributes()).hasSize(2)); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/shoppinglists/ShoppingListSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/shoppinglists/ShoppingListSyncIT.java index 7d62f7499e..c4a88a729f 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/shoppinglists/ShoppingListSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/shoppinglists/ShoppingListSyncIT.java @@ -1,5 +1,16 @@ package com.commercetools.sync.integration.externalsource.shoppinglists; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.buildIngredientCustomType; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.buildUtensilsCustomType; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.createSampleShoppingListCarrotCake; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.deleteShoppingListTestData; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.buildShoppingListQuery; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.shoppinglists.ShoppingListSync; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptionsBuilder; @@ -33,322 +44,340 @@ import io.sphere.sdk.shoppinglists.commands.updateactions.SetTextLineItemCustomField; import io.sphere.sdk.shoppinglists.commands.updateactions.SetTextLineItemDescription; import io.sphere.sdk.types.CustomFieldsDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.buildIngredientCustomType; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.buildUtensilsCustomType; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.createSampleShoppingListCarrotCake; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.deleteShoppingListTestData; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.buildShoppingListQuery; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ShoppingListSyncIT { - private List errorMessages; - private List warningMessages; - private List exceptions; - private List> updateActionList; - - private ShoppingList shoppingListSampleCarrotCake; - private ShoppingListDraft shoppingListDraftSampleCarrotCake; - private ShoppingListSync shoppingListSync; - - @BeforeEach - void setup() { - deleteShoppingListTestData(CTP_TARGET_CLIENT); - setUpShoppingListSync(); - - final ImmutablePair sampleShoppingListCarrotCake - = createSampleShoppingListCarrotCake(CTP_TARGET_CLIENT); - - shoppingListSampleCarrotCake = sampleShoppingListCarrotCake.getLeft(); - shoppingListDraftSampleCarrotCake = sampleShoppingListCarrotCake.getRight(); - } - - private void setUpShoppingListSync() { - errorMessages = new ArrayList<>(); - warningMessages = new ArrayList<>(); - exceptions = new ArrayList<>(); - updateActionList = new ArrayList<>(); - - final ShoppingListSyncOptions shoppingListSyncOptions = ShoppingListSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .warningCallback((exception, oldResource, newResource) - -> warningMessages.add(exception.getMessage())) - .beforeUpdateCallback((updateActions, customerDraft, customer) -> { - updateActionList.addAll(Objects.requireNonNull(updateActions)); - return updateActions; - }) + private List errorMessages; + private List warningMessages; + private List exceptions; + private List> updateActionList; + + private ShoppingList shoppingListSampleCarrotCake; + private ShoppingListDraft shoppingListDraftSampleCarrotCake; + private ShoppingListSync shoppingListSync; + + @BeforeEach + void setup() { + deleteShoppingListTestData(CTP_TARGET_CLIENT); + setUpShoppingListSync(); + + final ImmutablePair sampleShoppingListCarrotCake = + createSampleShoppingListCarrotCake(CTP_TARGET_CLIENT); + + shoppingListSampleCarrotCake = sampleShoppingListCarrotCake.getLeft(); + shoppingListDraftSampleCarrotCake = sampleShoppingListCarrotCake.getRight(); + } + + private void setUpShoppingListSync() { + errorMessages = new ArrayList<>(); + warningMessages = new ArrayList<>(); + exceptions = new ArrayList<>(); + updateActionList = new ArrayList<>(); + + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .warningCallback( + (exception, oldResource, newResource) -> + warningMessages.add(exception.getMessage())) + .beforeUpdateCallback( + (updateActions, customerDraft, customer) -> { + updateActionList.addAll(Objects.requireNonNull(updateActions)); + return updateActions; + }) .build(); - shoppingListSync = new ShoppingListSync(shoppingListSyncOptions); - } + shoppingListSync = new ShoppingListSync(shoppingListSyncOptions); + } - @AfterAll - static void tearDown() { - deleteShoppingListTestData(CTP_TARGET_CLIENT); - } + @AfterAll + static void tearDown() { + deleteShoppingListTestData(CTP_TARGET_CLIENT); + } - @Test - void sync_WithSameShoppingList_ShouldNotUpdateShoppingList() { - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync + @Test + void sync_WithSameShoppingList_ShouldNotUpdateShoppingList() { + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync .sync(singletonList(shoppingListDraftSampleCarrotCake)) .toCompletableFuture() .join(); - assertThat(errorMessages).isEmpty(); - assertThat(warningMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 0); - assertThat(shoppingListSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 shopping lists were processed in total " + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 0); + assertThat(shoppingListSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 shopping lists were processed in total " + "(0 created, 0 updated and 0 failed to sync)."); - } + } - @Test - void sync_WithModifiedShoppingList_ShouldUpdateShoppingList() { - final ShoppingListDraft modifiedShoppingListDraft = prepareUpdatedDraft(); + @Test + void sync_WithModifiedShoppingList_ShouldUpdateShoppingList() { + final ShoppingListDraft modifiedShoppingListDraft = prepareUpdatedDraft(); - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync .sync(singletonList(modifiedShoppingListDraft)) .toCompletableFuture() .join(); - assertThat(errorMessages).isEmpty(); - assertThat(warningMessages).isEmpty(); - assertThat(exceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + assertThat(warningMessages).isEmpty(); + assertThat(exceptions).isEmpty(); - assertUpdateActions(); + assertUpdateActions(); - assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); - assertThat(shoppingListSyncStatistics - .getReportMessage()) - .isEqualTo("Summary: 1 shopping lists were processed in total " + assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); + assertThat(shoppingListSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 1 shopping lists were processed in total " + "(0 created, 1 updated and 0 failed to sync)."); - assertShoppingListUpdatedCorrectly(modifiedShoppingListDraft); - } - - /** - * To understand the reasoning behind the ordering changes, it would be useful to check - * `docs/adr/0002-shopping-lists-lineitem-and-textlineitem-update-actions.md` - */ - @Nonnull - private ShoppingListDraft prepareUpdatedDraft() { - final List newLineItemDrafts = new ArrayList<>(); - final LineItemDraft updatedLineItemDraft = LineItemDraftBuilder - .ofSku("SKU-3", 2L) // was 1 + assertShoppingListUpdatedCorrectly(modifiedShoppingListDraft); + } + + /** + * To understand the reasoning behind the ordering changes, it would be useful to check + * `docs/adr/0002-shopping-lists-lineitem-and-textlineitem-update-actions.md` + */ + @Nonnull + private ShoppingListDraft prepareUpdatedDraft() { + final List newLineItemDrafts = new ArrayList<>(); + final LineItemDraft updatedLineItemDraft = + LineItemDraftBuilder.ofSku("SKU-3", 2L) // was 1 .custom(buildIngredientCustomType("sugar", "150g")) // was 100g .addedAt(ZonedDateTime.now()) .build(); - final LineItemDraft newLineItemDraft = LineItemDraftBuilder - .ofSku("SKU-5", 1L) + final LineItemDraft newLineItemDraft = + LineItemDraftBuilder.ofSku("SKU-5", 1L) .custom(buildIngredientCustomType("nuts", "100g")) .addedAt(ZonedDateTime.parse("2020-11-05T10:00:00.000Z")) .build(); - for (int i = 0; i < Objects.requireNonNull(shoppingListDraftSampleCarrotCake.getLineItems()).size(); i++) { - if (i == 2) { - newLineItemDrafts.add(updatedLineItemDraft); - continue; - } - - if (i == 4) { - newLineItemDrafts.add(newLineItemDraft); - } - - newLineItemDrafts.add(shoppingListDraftSampleCarrotCake.getLineItems().get(i)); - } - - final TextLineItemDraftDsl updatedTextLineItemDraft = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 1 - updated"), 2L) - .description(LocalizedString.ofEnglish( - "Peel carrots and set aside, crack the nuts, separate eggs into small balls.")) - .custom(buildUtensilsCustomType("Peeler, nuts cracker, 2 small bowls")) - .build(); - - final TextLineItemDraftDsl newTextLineItemDraft = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("before step 5"), 1L) - .description(LocalizedString.ofEnglish( - "Pre-heat oven to 180 C degree.")) - .custom(buildUtensilsCustomType("Oven")) - .addedAt(ZonedDateTime.parse("2020-11-05T10:00:00.000Z")) - .build(); - - final List newTextLineItemDrafts = new ArrayList<>(); - for (int i = 0; i < Objects.requireNonNull(shoppingListDraftSampleCarrotCake.getTextLineItems()).size(); i++) { - if (i == 0) { - newTextLineItemDrafts.add(updatedTextLineItemDraft); - continue; - } - - if (i == 4) { - newTextLineItemDrafts.add(newTextLineItemDraft); - } - - newTextLineItemDrafts.add(shoppingListDraftSampleCarrotCake.getTextLineItems().get(i)); - } - - final Map servingsFields = new HashMap<>(); - servingsFields.put("nutrition", - JsonNodeFactory.instance.textNode("Per servings: 600 cal, 11g protein, 30g fat, 56g carb")); - servingsFields.put("servings", JsonNodeFactory.instance.numberNode(14)); - - return ShoppingListDraftBuilder - .of(shoppingListDraftSampleCarrotCake) - .name(LocalizedString.ofEnglish("Carrot Cake - (for xmas)")) - .slug(LocalizedString.ofEnglish("carrot-cake-for-xmas")) - .description(LocalizedString.ofEnglish("Carrot cake recipe - ingredients (for xmas)")) - .anonymousId("public-carrot-cake-shopping-list-xmas") - .deleteDaysAfterLastModification(15) - .custom( - CustomFieldsDraft.ofTypeKeyAndJson("custom-type-shopping-list", servingsFields)) - .lineItems(newLineItemDrafts) - .textLineItems(newTextLineItemDrafts) + for (int i = 0; + i < Objects.requireNonNull(shoppingListDraftSampleCarrotCake.getLineItems()).size(); + i++) { + if (i == 2) { + newLineItemDrafts.add(updatedLineItemDraft); + continue; + } + + if (i == 4) { + newLineItemDrafts.add(newLineItemDraft); + } + + newLineItemDrafts.add(shoppingListDraftSampleCarrotCake.getLineItems().get(i)); + } + + final TextLineItemDraftDsl updatedTextLineItemDraft = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 1 - updated"), 2L) + .description( + LocalizedString.ofEnglish( + "Peel carrots and set aside, crack the nuts, separate eggs into small balls.")) + .custom(buildUtensilsCustomType("Peeler, nuts cracker, 2 small bowls")) .build(); + + final TextLineItemDraftDsl newTextLineItemDraft = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("before step 5"), 1L) + .description(LocalizedString.ofEnglish("Pre-heat oven to 180 C degree.")) + .custom(buildUtensilsCustomType("Oven")) + .addedAt(ZonedDateTime.parse("2020-11-05T10:00:00.000Z")) + .build(); + + final List newTextLineItemDrafts = new ArrayList<>(); + for (int i = 0; + i < Objects.requireNonNull(shoppingListDraftSampleCarrotCake.getTextLineItems()).size(); + i++) { + if (i == 0) { + newTextLineItemDrafts.add(updatedTextLineItemDraft); + continue; + } + + if (i == 4) { + newTextLineItemDrafts.add(newTextLineItemDraft); + } + + newTextLineItemDrafts.add(shoppingListDraftSampleCarrotCake.getTextLineItems().get(i)); } - private void assertUpdateActions() { - // copy references / manual ref resolution for testing purpose - final String lineItemId_Sku3Sugar = shoppingListSampleCarrotCake.getLineItems().get(2).getId(); - final String lineItemId_Sku5BakingPowder = shoppingListSampleCarrotCake.getLineItems().get(4).getId(); - final String lineItemId_Sku6Cinnamon = shoppingListSampleCarrotCake.getLineItems().get(5).getId(); - final String lineItemTypeId = shoppingListSampleCarrotCake.getLineItems().get(2).getCustom().getType().getId(); - final String textLineItemId_Step1 = shoppingListSampleCarrotCake.getTextLineItems().get(0).getId(); - final String textLineItemId_Step5 = shoppingListSampleCarrotCake.getTextLineItems().get(4).getId(); - final String textLineItemId_Step6 = shoppingListSampleCarrotCake.getTextLineItems().get(5).getId(); - final String textLineItemId = shoppingListSampleCarrotCake.getTextLineItems().get(0) - .getCustom().getType().getId(); - - assertThat(updateActionList).contains( + final Map servingsFields = new HashMap<>(); + servingsFields.put( + "nutrition", + JsonNodeFactory.instance.textNode("Per servings: 600 cal, 11g protein, 30g fat, 56g carb")); + servingsFields.put("servings", JsonNodeFactory.instance.numberNode(14)); + + return ShoppingListDraftBuilder.of(shoppingListDraftSampleCarrotCake) + .name(LocalizedString.ofEnglish("Carrot Cake - (for xmas)")) + .slug(LocalizedString.ofEnglish("carrot-cake-for-xmas")) + .description(LocalizedString.ofEnglish("Carrot cake recipe - ingredients (for xmas)")) + .anonymousId("public-carrot-cake-shopping-list-xmas") + .deleteDaysAfterLastModification(15) + .custom(CustomFieldsDraft.ofTypeKeyAndJson("custom-type-shopping-list", servingsFields)) + .lineItems(newLineItemDrafts) + .textLineItems(newTextLineItemDrafts) + .build(); + } + + private void assertUpdateActions() { + // copy references / manual ref resolution for testing purpose + final String lineItemId_Sku3Sugar = shoppingListSampleCarrotCake.getLineItems().get(2).getId(); + final String lineItemId_Sku5BakingPowder = + shoppingListSampleCarrotCake.getLineItems().get(4).getId(); + final String lineItemId_Sku6Cinnamon = + shoppingListSampleCarrotCake.getLineItems().get(5).getId(); + final String lineItemTypeId = + shoppingListSampleCarrotCake.getLineItems().get(2).getCustom().getType().getId(); + final String textLineItemId_Step1 = + shoppingListSampleCarrotCake.getTextLineItems().get(0).getId(); + final String textLineItemId_Step5 = + shoppingListSampleCarrotCake.getTextLineItems().get(4).getId(); + final String textLineItemId_Step6 = + shoppingListSampleCarrotCake.getTextLineItems().get(5).getId(); + final String textLineItemId = + shoppingListSampleCarrotCake.getTextLineItems().get(0).getCustom().getType().getId(); + + assertThat(updateActionList) + .contains( SetSlug.of(LocalizedString.ofEnglish("carrot-cake-for-xmas")), ChangeName.of(LocalizedString.ofEnglish("Carrot Cake - (for xmas)")), - SetDescription.of(LocalizedString.ofEnglish("Carrot cake recipe - ingredients (for xmas)")), + SetDescription.of( + LocalizedString.ofEnglish("Carrot cake recipe - ingredients (for xmas)")), SetAnonymousId.of("public-carrot-cake-shopping-list-xmas"), SetDeleteDaysAfterLastModification.of(15), - SetCustomField.ofJson("nutrition", - JsonNodeFactory.instance.textNode("Per servings: 600 cal, 11g protein, 30g fat, 56g carb")), + SetCustomField.ofJson( + "nutrition", + JsonNodeFactory.instance.textNode( + "Per servings: 600 cal, 11g protein, 30g fat, 56g carb")), SetCustomField.ofJson("servings", JsonNodeFactory.instance.numberNode(14)), - ChangeLineItemQuantity.of(lineItemId_Sku3Sugar, 2L), - SetLineItemCustomField.ofJson("amount", - JsonNodeFactory.instance.textNode("150g"), lineItemId_Sku3Sugar), - - SetLineItemCustomField.ofJson("amount", - JsonNodeFactory.instance.textNode("100g"), lineItemId_Sku5BakingPowder), - SetLineItemCustomField.ofJson("ingredient", - JsonNodeFactory.instance.textNode("nuts"), lineItemId_Sku5BakingPowder), - + SetLineItemCustomField.ofJson( + "amount", JsonNodeFactory.instance.textNode("150g"), lineItemId_Sku3Sugar), + SetLineItemCustomField.ofJson( + "amount", JsonNodeFactory.instance.textNode("100g"), lineItemId_Sku5BakingPowder), + SetLineItemCustomField.ofJson( + "ingredient", + JsonNodeFactory.instance.textNode("nuts"), + lineItemId_Sku5BakingPowder), RemoveLineItem.of(lineItemId_Sku6Cinnamon), - AddLineItemWithSku.of(LineItemDraftBuilder - .ofSku("SKU-5", 1L) - .custom(CustomFieldsDraft.ofTypeIdAndJson(lineItemTypeId, - buildIngredientCustomType("baking powder", "1 tsp").getFields())) - .build()), - AddLineItemWithSku.of(LineItemDraftBuilder - .ofSku("SKU-6", 1L) - .custom(CustomFieldsDraft.ofTypeIdAndJson(lineItemTypeId, - buildIngredientCustomType("cinnamon", "2 tsp").getFields())) - .build()), - - ChangeTextLineItemName.of(textLineItemId_Step1, LocalizedString.ofEnglish("step 1 - updated")), - SetTextLineItemDescription.of(textLineItemId_Step1).withDescription( - LocalizedString.ofEnglish("Peel carrots and set aside, crack the nuts, " - + "separate eggs into small balls.")), + AddLineItemWithSku.of( + LineItemDraftBuilder.ofSku("SKU-5", 1L) + .custom( + CustomFieldsDraft.ofTypeIdAndJson( + lineItemTypeId, + buildIngredientCustomType("baking powder", "1 tsp").getFields())) + .build()), + AddLineItemWithSku.of( + LineItemDraftBuilder.ofSku("SKU-6", 1L) + .custom( + CustomFieldsDraft.ofTypeIdAndJson( + lineItemTypeId, + buildIngredientCustomType("cinnamon", "2 tsp").getFields())) + .build()), + ChangeTextLineItemName.of( + textLineItemId_Step1, LocalizedString.ofEnglish("step 1 - updated")), + SetTextLineItemDescription.of(textLineItemId_Step1) + .withDescription( + LocalizedString.ofEnglish( + "Peel carrots and set aside, crack the nuts, " + + "separate eggs into small balls.")), ChangeTextLineItemQuantity.of(textLineItemId_Step1, 2L), - SetTextLineItemCustomField.ofJson("utensils", - JsonNodeFactory.instance.textNode("Peeler, nuts cracker, 2 small bowls"), textLineItemId_Step1), - - ChangeTextLineItemName.of(textLineItemId_Step5, LocalizedString.ofEnglish("before step 5")), - SetTextLineItemDescription.of(textLineItemId_Step5).withDescription( - LocalizedString.ofEnglish("Pre-heat oven to 180 C degree.")), - SetTextLineItemCustomField.ofJson("utensils", - JsonNodeFactory.instance.textNode("Oven"), textLineItemId_Step5), - + SetTextLineItemCustomField.ofJson( + "utensils", + JsonNodeFactory.instance.textNode("Peeler, nuts cracker, 2 small bowls"), + textLineItemId_Step1), + ChangeTextLineItemName.of( + textLineItemId_Step5, LocalizedString.ofEnglish("before step 5")), + SetTextLineItemDescription.of(textLineItemId_Step5) + .withDescription(LocalizedString.ofEnglish("Pre-heat oven to 180 C degree.")), + SetTextLineItemCustomField.ofJson( + "utensils", JsonNodeFactory.instance.textNode("Oven"), textLineItemId_Step5), ChangeTextLineItemName.of(textLineItemId_Step6, LocalizedString.ofEnglish("step 5")), - SetTextLineItemDescription.of(textLineItemId_Step6).withDescription( - LocalizedString.ofEnglish("Put cake mixture into cake pan, bake appr 40 min with 180 C degree")), - SetTextLineItemCustomField.ofJson("utensils", - JsonNodeFactory.instance.textNode("Cake pan, oven"), textLineItemId_Step6), - - AddTextLineItemWithAddedAt.of(TextLineItemDraftBuilder - .of(LocalizedString.ofEnglish("step 6"), 1L) - .description(LocalizedString.ofEnglish("Decorate as you wish and serve, enjoy!")) - .custom(CustomFieldsDraft.ofTypeIdAndJson(textLineItemId, - buildUtensilsCustomType("Knife, cake plate.").getFields())) - .addedAt(ZonedDateTime.parse("2020-11-06T10:00:00.000Z")) - .build()) - ); - } - - private void assertShoppingListUpdatedCorrectly(@Nonnull final ShoppingListDraft expectedShoppingListDraft) { - - final List shoppingLists = CTP_TARGET_CLIENT + SetTextLineItemDescription.of(textLineItemId_Step6) + .withDescription( + LocalizedString.ofEnglish( + "Put cake mixture into cake pan, bake appr 40 min with 180 C degree")), + SetTextLineItemCustomField.ofJson( + "utensils", + JsonNodeFactory.instance.textNode("Cake pan, oven"), + textLineItemId_Step6), + AddTextLineItemWithAddedAt.of( + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("step 6"), 1L) + .description( + LocalizedString.ofEnglish("Decorate as you wish and serve, enjoy!")) + .custom( + CustomFieldsDraft.ofTypeIdAndJson( + textLineItemId, + buildUtensilsCustomType("Knife, cake plate.").getFields())) + .addedAt(ZonedDateTime.parse("2020-11-06T10:00:00.000Z")) + .build())); + } + + private void assertShoppingListUpdatedCorrectly( + @Nonnull final ShoppingListDraft expectedShoppingListDraft) { + + final List shoppingLists = + CTP_TARGET_CLIENT .execute(buildShoppingListQuery()) .toCompletableFuture() .join() .getResults(); - final List shoppingListDrafts = - ShoppingListReferenceResolutionUtils.mapToShoppingListDrafts(shoppingLists); + final List shoppingListDrafts = + ShoppingListReferenceResolutionUtils.mapToShoppingListDrafts(shoppingLists); - assertThat( - ShoppingListDraftBuilder - .of(shoppingListDrafts.get(0)) - .lineItems(setNullToAddedAtValuesForLineItems(shoppingListDrafts.get(0).getLineItems())) - .textLineItems(setNullToAddedAtValuesForTextLineItems(shoppingListDrafts.get(0).getTextLineItems())) + assertThat( + ShoppingListDraftBuilder.of(shoppingListDrafts.get(0)) + .lineItems( + setNullToAddedAtValuesForLineItems(shoppingListDrafts.get(0).getLineItems())) + .textLineItems( + setNullToAddedAtValuesForTextLineItems( + shoppingListDrafts.get(0).getTextLineItems())) .build()) - .isEqualTo( - ShoppingListDraftBuilder - .of(expectedShoppingListDraft) - .lineItems(setNullToAddedAtValuesForLineItems(expectedShoppingListDraft.getLineItems())) - .textLineItems(setNullToAddedAtValuesForTextLineItems(expectedShoppingListDraft.getTextLineItems())) - .build() - ); - } - - @Nonnull - private List setNullToAddedAtValuesForTextLineItems( - @Nonnull final List textLineItemDrafts) { - - return textLineItemDrafts.stream() - .map(textLineItemDraft -> TextLineItemDraftBuilder - .of(textLineItemDraft) - .addedAt(null) - .build()) - .collect(toList()); - } - - @Nonnull - private List setNullToAddedAtValuesForLineItems( - @Nonnull final List lineItemDrafts) { - - return lineItemDrafts.stream() - .map(lineItemDraft -> LineItemDraftBuilder - .of(lineItemDraft) - .addedAt(null) - .build()) - .collect(toList()); - } + .isEqualTo( + ShoppingListDraftBuilder.of(expectedShoppingListDraft) + .lineItems( + setNullToAddedAtValuesForLineItems(expectedShoppingListDraft.getLineItems())) + .textLineItems( + setNullToAddedAtValuesForTextLineItems( + expectedShoppingListDraft.getTextLineItems())) + .build()); + } + + @Nonnull + private List setNullToAddedAtValuesForTextLineItems( + @Nonnull final List textLineItemDrafts) { + + return textLineItemDrafts.stream() + .map( + textLineItemDraft -> + TextLineItemDraftBuilder.of(textLineItemDraft).addedAt(null).build()) + .collect(toList()); + } + + @Nonnull + private List setNullToAddedAtValuesForLineItems( + @Nonnull final List lineItemDrafts) { + + return lineItemDrafts.stream() + .map(lineItemDraft -> LineItemDraftBuilder.of(lineItemDraft).addedAt(null).build()) + .collect(toList()); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/states/StateSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/states/StateSyncIT.java index 77b3d0c139..8fa284bcb3 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/states/StateSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/states/StateSyncIT.java @@ -1,5 +1,26 @@ package com.commercetools.sync.integration.externalsource.states; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.deleteStates; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.deleteStatesFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.getStateByKey; +import static com.commercetools.sync.states.utils.StateReferenceResolutionUtils.mapToStateDrafts; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.states.State.referenceOfId; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.ThreadLocalRandom.current; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.WaitingToBeResolvedTransitions; import com.commercetools.sync.services.impl.StateServiceImpl; @@ -28,12 +49,6 @@ import io.sphere.sdk.states.expansion.StateExpansionModel; import io.sphere.sdk.states.queries.StateQuery; import io.sphere.sdk.states.queries.StateQueryBuilder; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -44,1094 +59,1053 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.deleteStates; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.deleteStatesFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.getStateByKey; -import static com.commercetools.sync.states.utils.StateReferenceResolutionUtils.mapToStateDrafts; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.states.State.referenceOfId; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.ThreadLocalRandom.current; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class StateSyncIT { - String keyA; - String keyB; - String keyC; - - List errorCallBackMessages; - List warningCallBackMessages; - List errorCallBackExceptions; - String key = ""; - - @AfterAll - static void tearDown() { - deleteStatesFromTargetAndSource(); - } - - @BeforeEach - void setup() { - key = "state-" + current().nextInt(); - keyA = "state-A-" + current().nextInt(); - keyB = "state-B-" + current().nextInt(); - keyC = "state-C-" + current().nextInt(); - - final StateDraft stateDraft = StateDraftBuilder - .of(key, StateType.LINE_ITEM_STATE) + String keyA; + String keyB; + String keyC; + + List errorCallBackMessages; + List warningCallBackMessages; + List errorCallBackExceptions; + String key = ""; + + @AfterAll + static void tearDown() { + deleteStatesFromTargetAndSource(); + } + + @BeforeEach + void setup() { + key = "state-" + current().nextInt(); + keyA = "state-A-" + current().nextInt(); + keyB = "state-B-" + current().nextInt(); + keyC = "state-C-" + current().nextInt(); + + final StateDraft stateDraft = + StateDraftBuilder.of(key, StateType.LINE_ITEM_STATE) .name(LocalizedString.ofEnglish("state-name")) .description(LocalizedString.ofEnglish("state-desc")) .roles(Collections.singleton(StateRole.RETURN)) .initial(false) .build(); - executeBlocking(CTP_TARGET_CLIENT.execute(StateCreateCommand.of(stateDraft))); - errorCallBackMessages = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - } - - @Test - void sync_withNewState_shouldCreateState() { - final StateDraft stateDraft = StateDraftBuilder - .of("new-state", StateType.REVIEW_STATE) - .roles(Collections.singleton( - StateRole.REVIEW_INCLUDED_IN_STATISTICS)) - .build(); - - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final StateSync stateSync = new StateSync(stateSyncOptions); - - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(1, 1, 0, 0, 0); - } - - @Test - void sync_withCreateStateException_shouldPrintMessage() { - final StateDraft stateDraft = StateDraftBuilder - .of(keyA, StateType.REVIEW_STATE) - .roles(Collections.singleton( - StateRole.REVIEW_INCLUDED_IN_STATISTICS)) - .build(); - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final StateCreateCommand command = any(StateCreateCommand.class); - when(spyClient.execute(command)) - .thenReturn(exceptionallyCompletedFuture(new BadRequestException("test error message"))); + executeBlocking(CTP_TARGET_CLIENT.execute(StateCreateCommand.of(stateDraft))); + errorCallBackMessages = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + } - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + @Test + void sync_withNewState_shouldCreateState() { + final StateDraft stateDraft = + StateDraftBuilder.of("new-state", StateType.REVIEW_STATE) + .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .build(); - final StateSync stateSync = new StateSync(stateSyncOptions); + final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture() - .join(); + final StateSync stateSync = new StateSync(stateSyncOptions); - assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1, 0); - Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages.get(0)) - .contains(format("Failed to create draft with key: '%s'", keyA)); - } + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); - @Test - void sync_withNewStateWithoutRole_shouldRemoveRole() { - final StateDraft stateDraft = StateDraftBuilder - .of(keyA, StateType.REVIEW_STATE) - .roles(Collections.emptySet()) - .build(); + assertThat(stateSyncStatistics).hasValues(1, 1, 0, 0, 0); + } - final StateDraft stateDraftTarget = StateDraftBuilder - .of(keyA, StateType.REVIEW_STATE) + @Test + void sync_withCreateStateException_shouldPrintMessage() { + final StateDraft stateDraft = + StateDraftBuilder.of(keyA, StateType.REVIEW_STATE) .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .build(); - createStateInTarget(stateDraftTarget); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final StateCreateCommand command = any(StateCreateCommand.class); + when(spyClient.execute(command)) + .thenReturn(exceptionallyCompletedFuture(new BadRequestException("test error message"))); + + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final StateSync stateSync = new StateSync(stateSyncOptions); + final StateSync stateSync = new StateSync(stateSyncOptions); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture() - .join(); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); - assertThat(stateSyncStatistics).hasValues(1, 0, 1, 0, 0); - QueryExecutionUtils.queryAll(CTP_TARGET_CLIENT, StateQueryBuilder - .of() - .plusPredicates(q -> q.key().is(keyA)).build()) - .thenAccept(resultStates -> { - Assertions.assertThat(resultStates.size()).isEqualTo(1); - Assertions.assertThat(resultStates.get(0).getRoles().isEmpty()).isTrue(); - }).toCompletableFuture().join(); - } + assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1, 0); + Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains(format("Failed to create draft with key: '%s'", keyA)); + } - @Test - void sync_withNewStateWithoutRole_shouldDoNothing() { - final StateDraft stateDraft = StateDraftBuilder - .of(keyA, StateType.REVIEW_STATE) - .roles(Collections.emptySet()) - .build(); + @Test + void sync_withNewStateWithoutRole_shouldRemoveRole() { + final StateDraft stateDraft = + StateDraftBuilder.of(keyA, StateType.REVIEW_STATE).roles(Collections.emptySet()).build(); - final StateDraft stateDraftTarget = StateDraftBuilder - .of(keyA, StateType.REVIEW_STATE) - .roles(Collections.emptySet()) + final StateDraft stateDraftTarget = + StateDraftBuilder.of(keyA, StateType.REVIEW_STATE) + .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .build(); - createStateInTarget(stateDraftTarget); + createStateInTarget(stateDraftTarget); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final StateSync stateSync = new StateSync(stateSyncOptions); + final StateSync stateSync = new StateSync(stateSyncOptions); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture() - .join(); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); - assertThat(stateSyncStatistics).hasValues(1, 0, 1, 0, 0); - QueryExecutionUtils.queryAll(CTP_TARGET_CLIENT, StateQueryBuilder - .of() - .plusPredicates(q -> q.key().is(keyA)).build()) - .thenAccept(resultStates -> { - Assertions.assertThat(resultStates.size()).isEqualTo(1); - Assertions.assertThat(resultStates.get(0).getRoles().isEmpty()).isTrue(); - }).toCompletableFuture().join(); - } - - @Test - void sync_withNewStateWithNewRole_shouldAddRole() { - final StateDraft stateDraft = StateDraftBuilder - .of(keyA, StateType.REVIEW_STATE) + assertThat(stateSyncStatistics).hasValues(1, 0, 1, 0, 0); + QueryExecutionUtils.queryAll( + CTP_TARGET_CLIENT, StateQueryBuilder.of().plusPredicates(q -> q.key().is(keyA)).build()) + .thenAccept( + resultStates -> { + Assertions.assertThat(resultStates.size()).isEqualTo(1); + Assertions.assertThat(resultStates.get(0).getRoles().isEmpty()).isTrue(); + }) + .toCompletableFuture() + .join(); + } + + @Test + void sync_withNewStateWithoutRole_shouldDoNothing() { + final StateDraft stateDraft = + StateDraftBuilder.of(keyA, StateType.REVIEW_STATE).roles(Collections.emptySet()).build(); + + final StateDraft stateDraftTarget = + StateDraftBuilder.of(keyA, StateType.REVIEW_STATE).roles(Collections.emptySet()).build(); + createStateInTarget(stateDraftTarget); + + final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + + final StateSync stateSync = new StateSync(stateSyncOptions); + + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(1, 0, 1, 0, 0); + QueryExecutionUtils.queryAll( + CTP_TARGET_CLIENT, StateQueryBuilder.of().plusPredicates(q -> q.key().is(keyA)).build()) + .thenAccept( + resultStates -> { + Assertions.assertThat(resultStates.size()).isEqualTo(1); + Assertions.assertThat(resultStates.get(0).getRoles().isEmpty()).isTrue(); + }) + .toCompletableFuture() + .join(); + } + + @Test + void sync_withNewStateWithNewRole_shouldAddRole() { + final StateDraft stateDraft = + StateDraftBuilder.of(keyA, StateType.REVIEW_STATE) .roles(asSet(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .build(); - final StateDraft stateDraftTarget = StateDraftBuilder - .of(keyA, StateType.REVIEW_STATE) - .roles(Collections.emptySet()) - .build(); - createStateInTarget(stateDraftTarget); + final StateDraft stateDraftTarget = + StateDraftBuilder.of(keyA, StateType.REVIEW_STATE).roles(Collections.emptySet()).build(); + createStateInTarget(stateDraftTarget); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final StateSync stateSync = new StateSync(stateSyncOptions); + final StateSync stateSync = new StateSync(stateSyncOptions); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(1, 0, 1, 0, 0); - QueryExecutionUtils.queryAll(CTP_TARGET_CLIENT, StateQueryBuilder - .of() - .plusPredicates(q -> q.key().is(keyA)).build()) - .thenAccept(resultStates -> { - Assertions.assertThat(resultStates.size()).isEqualTo(1); - Assertions.assertThat(resultStates.get(0).getRoles().size()).isEqualTo(1); - }).toCompletableFuture().join(); - } + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); - @Test - void sync_withNewStateWihCreationException_shouldPrintErrorMessage() { - final StateDraft stateDraft = StateDraftBuilder - .of(keyA, StateType.REVIEW_STATE) + assertThat(stateSyncStatistics).hasValues(1, 0, 1, 0, 0); + QueryExecutionUtils.queryAll( + CTP_TARGET_CLIENT, StateQueryBuilder.of().plusPredicates(q -> q.key().is(keyA)).build()) + .thenAccept( + resultStates -> { + Assertions.assertThat(resultStates.size()).isEqualTo(1); + Assertions.assertThat(resultStates.get(0).getRoles().size()).isEqualTo(1); + }) + .toCompletableFuture() + .join(); + } + + @Test + void sync_withNewStateWihCreationException_shouldPrintErrorMessage() { + final StateDraft stateDraft = + StateDraftBuilder.of(keyA, StateType.REVIEW_STATE) .roles(asSet(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .build(); - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final StateCreateCommand command = any(StateCreateCommand.class); - when(spyClient.execute(command)) - .thenReturn(completedFuture(any(State.class))); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final StateCreateCommand command = any(StateCreateCommand.class); + when(spyClient.execute(command)).thenReturn(completedFuture(any(State.class))); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final StateSync stateSync = new StateSync(stateSyncOptions); - - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1, 0); - Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages.get(0)) - .contains(format("Failed to process the StateDraft with key: '%s'", keyA)); - Assertions.assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_WithUpdatedState_ShouldUpdateState() { - // preparation - String key = this.key; - final StateDraft stateDraft = StateDraftBuilder - .of(key, StateType.REVIEW_STATE) + final StateSync stateSync = new StateSync(stateSyncOptions); + + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1, 0); + Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains(format("Failed to process the StateDraft with key: '%s'", keyA)); + Assertions.assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_WithUpdatedState_ShouldUpdateState() { + // preparation + String key = this.key; + final StateDraft stateDraft = + StateDraftBuilder.of(key, StateType.REVIEW_STATE) .name(ofEnglish("state-name-updated")) .description(ofEnglish("state-desc-updated")) .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .initial(true) .build(); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final StateSync stateSync = new StateSync(stateSyncOptions); - - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(stateSyncStatistics).hasValues(1, 0, 1, 0, 0); - - final Optional oldStateAfter = getStateByKey(CTP_TARGET_CLIENT, key); - - Assertions.assertThat(oldStateAfter).hasValueSatisfying(state -> { - Assertions.assertThat(state.getType()).isEqualTo(StateType.REVIEW_STATE); - Assertions.assertThat(state.getName()).isEqualTo(ofEnglish("state-name-updated")); - Assertions.assertThat(state.getDescription()).isEqualTo(ofEnglish("state-desc-updated")); - Assertions.assertThat(state.getRoles()) - .isEqualTo(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)); - Assertions.assertThat(state.isInitial()).isEqualTo(true); - }); - deleteStates(CTP_TARGET_CLIENT); - } - - @Test - void sync_withEqualState_shouldNotUpdateState() { - StateDraft stateDraft = StateDraftBuilder - .of(key, StateType.LINE_ITEM_STATE) + final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + + final StateSync stateSync = new StateSync(stateSyncOptions); + + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); + + // assertion + assertThat(stateSyncStatistics).hasValues(1, 0, 1, 0, 0); + + final Optional oldStateAfter = getStateByKey(CTP_TARGET_CLIENT, key); + + Assertions.assertThat(oldStateAfter) + .hasValueSatisfying( + state -> { + Assertions.assertThat(state.getType()).isEqualTo(StateType.REVIEW_STATE); + Assertions.assertThat(state.getName()).isEqualTo(ofEnglish("state-name-updated")); + Assertions.assertThat(state.getDescription()) + .isEqualTo(ofEnglish("state-desc-updated")); + Assertions.assertThat(state.getRoles()) + .isEqualTo(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)); + Assertions.assertThat(state.isInitial()).isEqualTo(true); + }); + deleteStates(CTP_TARGET_CLIENT); + } + + @Test + void sync_withEqualState_shouldNotUpdateState() { + StateDraft stateDraft = + StateDraftBuilder.of(key, StateType.LINE_ITEM_STATE) .name(ofEnglish("state-name")) .description(ofEnglish("state-desc")) .roles(Collections.singleton(StateRole.RETURN)) .initial(false) .build(); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final StateSync stateSync = new StateSync(stateSyncOptions); + final StateSync stateSync = new StateSync(stateSyncOptions); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture() - .join(); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); - assertThat(stateSyncStatistics).hasValues(1, 0, 0, 0, 0); - } + assertThat(stateSyncStatistics).hasValues(1, 0, 0, 0, 0); + } - @Test - void sync_withChangedStateButConcurrentModificationException_shouldRetryAndUpdateState() { - // preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); + @Test + void sync_withChangedStateButConcurrentModificationException_shouldRetryAndUpdateState() { + // preparation + final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); - List errorCallBackMessages = new ArrayList<>(); - List warningCallBackMessages = new ArrayList<>(); - List errorCallBackExceptions = new ArrayList<>(); - final StateSyncOptions spyOptions = StateSyncOptionsBuilder - .of(spyClient) - .build(); + List errorCallBackMessages = new ArrayList<>(); + List warningCallBackMessages = new ArrayList<>(); + List errorCallBackExceptions = new ArrayList<>(); + final StateSyncOptions spyOptions = StateSyncOptionsBuilder.of(spyClient).build(); - final StateSync stateSync = new StateSync(spyOptions); + final StateSync stateSync = new StateSync(spyOptions); - final StateDraft stateDraft = StateDraftBuilder - .of(key, StateType.REVIEW_STATE) + final StateDraft stateDraft = + StateDraftBuilder.of(key, StateType.REVIEW_STATE) .name(ofEnglish("state-name-updated")) .description(ofEnglish("state-desc-updated")) .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .initial(true) .build(); - final StateSyncStatistics syncStatistics = - executeBlocking(stateSync.sync(singletonList(stateDraft))); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); - Assertions.assertThat(errorCallBackExceptions).isEmpty(); - Assertions.assertThat(errorCallBackMessages).isEmpty(); - Assertions.assertThat(warningCallBackMessages).isEmpty(); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdate() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final StateUpdateCommand updateCommand = any(StateUpdateCommand.class); - when(spyClient.execute(updateCommand)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - return spyClient; - } - - @Test - void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { - // preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); - - - final StateSyncOptions spyOptions = StateSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) + final StateSyncStatistics syncStatistics = + executeBlocking(stateSync.sync(singletonList(stateDraft))); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0, 0); + Assertions.assertThat(errorCallBackExceptions).isEmpty(); + Assertions.assertThat(errorCallBackMessages).isEmpty(); + Assertions.assertThat(warningCallBackMessages).isEmpty(); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdate() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final StateUpdateCommand updateCommand = any(StateUpdateCommand.class); + when(spyClient.execute(updateCommand)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); + + return spyClient; + } + + @Test + void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); + + final StateSyncOptions spyOptions = + StateSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - final StateSync stateSync = new StateSync(spyOptions); + final StateSync stateSync = new StateSync(spyOptions); - final StateDraft stateDraft = StateDraftBuilder - .of(key, StateType.REVIEW_STATE) + final StateDraft stateDraft = + StateDraftBuilder.of(key, StateType.REVIEW_STATE) .name(ofEnglish("state-name-updated")) .description(ofEnglish("state-desc-updated")) .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .initial(true) .build(); - final StateSyncStatistics syncStatistics = - executeBlocking(stateSync.sync(singletonList(stateDraft))); - - // Test and assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - Assertions.assertThat(errorCallBackMessages).hasSize(1); - Assertions.assertThat(errorCallBackExceptions).hasSize(1); - - Assertions.assertThat(errorCallBackExceptions.get(0).getCause()).isExactlyInstanceOf(BadGatewayException.class); - Assertions.assertThat(errorCallBackMessages.get(0)).contains( - format("Failed to update state with key: '%s'. Reason: Failed to fetch from CTP while retrying " - + "after concurrency modification.", stateDraft.getKey())); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final StateUpdateCommand updateCommand = any(StateUpdateCommand.class); - when(spyClient.execute(updateCommand)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - final StateQuery stateQuery = any(StateQuery.class); - when(spyClient.execute(stateQuery)) - .thenCallRealMethod() // Call real fetch on fetching matching states - .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); - - return spyClient; - } - - @Test - void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { - // preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); - - List errorCallBackMessages = new ArrayList<>(); - List warningCallBackMessages = new ArrayList<>(); - List errorCallBackExceptions = new ArrayList<>(); - final StateSyncOptions spyOptions = StateSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) + final StateSyncStatistics syncStatistics = + executeBlocking(stateSync.sync(singletonList(stateDraft))); + + // Test and assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + Assertions.assertThat(errorCallBackMessages).hasSize(1); + Assertions.assertThat(errorCallBackExceptions).hasSize(1); + + Assertions.assertThat(errorCallBackExceptions.get(0).getCause()) + .isExactlyInstanceOf(BadGatewayException.class); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to update state with key: '%s'. Reason: Failed to fetch from CTP while retrying " + + "after concurrency modification.", + stateDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final StateUpdateCommand updateCommand = any(StateUpdateCommand.class); + when(spyClient.execute(updateCommand)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); + + final StateQuery stateQuery = any(StateQuery.class); + when(spyClient.execute(stateQuery)) + .thenCallRealMethod() // Call real fetch on fetching matching states + .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); + + return spyClient; + } + + @Test + void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); + + List errorCallBackMessages = new ArrayList<>(); + List warningCallBackMessages = new ArrayList<>(); + List errorCallBackExceptions = new ArrayList<>(); + final StateSyncOptions spyOptions = + StateSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - final StateSync stateSync = new StateSync(spyOptions); + final StateSync stateSync = new StateSync(spyOptions); - final StateDraft stateDraft = StateDraftBuilder - .of(key, StateType.REVIEW_STATE) + final StateDraft stateDraft = + StateDraftBuilder.of(key, StateType.REVIEW_STATE) .name(ofEnglish("state-name-updated")) .description(ofEnglish("state-desc-updated")) .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .initial(true) .build(); - final StateSyncStatistics syncStatistics = - executeBlocking(stateSync.sync(singletonList(stateDraft))); - - // Test and assertion - assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); - Assertions.assertThat(errorCallBackMessages).hasSize(1); - Assertions.assertThat(errorCallBackExceptions).hasSize(1); - - Assertions.assertThat(errorCallBackMessages.get(0)).contains( - format("Failed to update state with key: '%s'. Reason: Not found when attempting to fetch while" - + " retrying after concurrency modification.", stateDraft.getKey())); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final StateUpdateCommand stateUpdateCommand = any(StateUpdateCommand.class); - when(spyClient.execute(stateUpdateCommand)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - final StateQuery stateQuery = any(StateQuery.class); - - when(spyClient.execute(stateQuery)) - .thenCallRealMethod() // Call real fetch on fetching matching states - .thenReturn(completedFuture(PagedQueryResult.empty())); - - return spyClient; - } - - @Test - void sync_WithSeveralBatches_ShouldReturnProperStatistics() { - // 2 batches - final List stateDrafts = IntStream - .range(0, 10) - .mapToObj(i -> StateDraft - .of("key" + i, StateType.REVIEW_STATE) - .withName(ofEnglish("name" + i))) + final StateSyncStatistics syncStatistics = + executeBlocking(stateSync.sync(singletonList(stateDraft))); + + // Test and assertion + assertThat(syncStatistics).hasValues(1, 0, 0, 1, 0); + Assertions.assertThat(errorCallBackMessages).hasSize(1); + Assertions.assertThat(errorCallBackExceptions).hasSize(1); + + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to update state with key: '%s'. Reason: Not found when attempting to fetch while" + + " retrying after concurrency modification.", + stateDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final StateUpdateCommand stateUpdateCommand = any(StateUpdateCommand.class); + when(spyClient.execute(stateUpdateCommand)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); + + final StateQuery stateQuery = any(StateQuery.class); + + when(spyClient.execute(stateQuery)) + .thenCallRealMethod() // Call real fetch on fetching matching states + .thenReturn(completedFuture(PagedQueryResult.empty())); + + return spyClient; + } + + @Test + void sync_WithSeveralBatches_ShouldReturnProperStatistics() { + // 2 batches + final List stateDrafts = + IntStream.range(0, 10) + .mapToObj( + i -> + StateDraft.of("key" + i, StateType.REVIEW_STATE) + .withName(ofEnglish("name" + i))) .collect(Collectors.toList()); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .batchSize(5) - .build(); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).batchSize(5).build(); - final StateSync stateSync = new StateSync(stateSyncOptions); + final StateSync stateSync = new StateSync(stateSyncOptions); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(stateDrafts) - .toCompletableFuture() - .join(); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(stateDrafts).toCompletableFuture().join(); - assertThat(stateSyncStatistics).hasValues(10, 10, 0, 0, 0); - } + assertThat(stateSyncStatistics).hasValues(10, 10, 0, 0, 0); + } - @Test - void sync_WithNotExistentStates_ShouldResolveStateLater() { - final StateDraft stateCDraft = createStateDraft(keyC); - final State stateC = createStateInSource(stateCDraft); + @Test + void sync_WithNotExistentStates_ShouldResolveStateLater() { + final StateDraft stateCDraft = createStateDraft(keyC); + final State stateC = createStateInSource(stateCDraft); - final StateDraft stateBDraft = createStateDraft(keyB, stateC); - final State stateB = createStateInSource(stateBDraft); + final StateDraft stateBDraft = createStateDraft(keyB, stateC); + final State stateB = createStateInSource(stateBDraft); - StateDraft[] draftsWithReplacesKeys = - mapToStateDrafts(asList(stateB, stateC)).toArray(new StateDraft[2]); - final StateDraft stateADraft = createStateDraftReferencingStateDrafts(keyA, draftsWithReplacesKeys); - final List stateDrafts = asList(stateADraft); + StateDraft[] draftsWithReplacesKeys = + mapToStateDrafts(asList(stateB, stateC)).toArray(new StateDraft[2]); + final StateDraft stateADraft = + createStateDraftReferencingStateDrafts(keyA, draftsWithReplacesKeys); + final List stateDrafts = asList(stateADraft); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .batchSize(1) - .build(); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).batchSize(1).build(); - final StateSync stateSync = new StateSync(stateSyncOptions); + final StateSync stateSync = new StateSync(stateSyncOptions); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(stateDrafts) - .toCompletableFuture() - .join(); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(stateDrafts).toCompletableFuture().join(); - assertThat(stateSyncStatistics).hasValues(1, 0, 0, 0, 1); - UnresolvedTransitionsServiceImpl unresolvedTransitionsService = - new UnresolvedTransitionsServiceImpl(stateSyncOptions); - Set result = - unresolvedTransitionsService.fetch(new HashSet<>(asList(keyA))).toCompletableFuture().join(); - Assertions.assertThat(result.size()).isEqualTo(1); - WaitingToBeResolvedTransitions waitingToBeResolvedTransitions = result.iterator().next(); - Assertions - .assertThat(waitingToBeResolvedTransitions.getMissingTransitionStateKeys().containsAll(asList(keyB, keyC))) - .isTrue(); - Assertions.assertThat(waitingToBeResolvedTransitions.getStateDraft().getKey()).isEqualTo(keyA); - } - - @Test - void sync_WithAllExistentStates_ShouldResolveAllStates() { - - final StateDraft stateCDraft = createStateDraft(keyC); - final State stateC = createStateInSource(stateCDraft); - - final StateDraft stateBDraft = createStateDraft(keyB, stateC); - final State stateB = createStateInSource(stateBDraft); - - final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); - final State stateA = createStateInSource(stateADraft); - - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .batchSize(3) - .build(); - - final StateSync stateSync = new StateSync(stateSyncOptions); - final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(stateDrafts) + assertThat(stateSyncStatistics).hasValues(1, 0, 0, 0, 1); + UnresolvedTransitionsServiceImpl unresolvedTransitionsService = + new UnresolvedTransitionsServiceImpl(stateSyncOptions); + Set result = + unresolvedTransitionsService + .fetch(new HashSet<>(asList(keyA))) .toCompletableFuture() .join(); - - assertThat(stateSyncStatistics).hasValues(3, 3, 0, 0, 0); - Assertions.assertThat(stateSyncStatistics.getReportMessage()) - .isEqualTo("Summary: 3 state(s) were processed in total " + Assertions.assertThat(result.size()).isEqualTo(1); + WaitingToBeResolvedTransitions waitingToBeResolvedTransitions = result.iterator().next(); + Assertions.assertThat( + waitingToBeResolvedTransitions + .getMissingTransitionStateKeys() + .containsAll(asList(keyB, keyC))) + .isTrue(); + Assertions.assertThat(waitingToBeResolvedTransitions.getStateDraft().getKey()).isEqualTo(keyA); + } + + @Test + void sync_WithAllExistentStates_ShouldResolveAllStates() { + + final StateDraft stateCDraft = createStateDraft(keyC); + final State stateC = createStateInSource(stateCDraft); + + final StateDraft stateBDraft = createStateDraft(keyB, stateC); + final State stateB = createStateInSource(stateBDraft); + + final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); + final State stateA = createStateInSource(stateADraft); + + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).batchSize(3).build(); + + final StateSync stateSync = new StateSync(stateSyncOptions); + final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(stateDrafts).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(3, 3, 0, 0, 0); + Assertions.assertThat(stateSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 3 state(s) were processed in total " + "(3 created, 0 updated, 0 failed to sync and 0 state(s) with missing transition(s))."); - UnresolvedTransitionsServiceImpl unresolvedTransitionsService = - new UnresolvedTransitionsServiceImpl(stateSyncOptions); - Set result = - unresolvedTransitionsService.fetch(new HashSet<>(asList(keyA))).toCompletableFuture().join(); - Assertions.assertThat(result.size()).isEqualTo(0); - } - - @Test - void sync_WithExceptionWhenFetchingUnresolvedTransition_ShouldPrintErrorMessage() { - - final StateDraft stateCDraft = createStateDraft(keyC); - final State stateC = createStateInSource(stateCDraft); - - final StateDraft stateBDraft = createStateDraft(keyB, stateC); - final State stateB = createStateInSource(stateBDraft); - - final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); - final State stateA = createStateInSource(stateADraft); - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - when(spyClient.execute(any(CustomObjectQuery.class))) - .thenReturn( - exceptionallyCompletedFuture(new BadRequestException("a test exception"))) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(spyClient) - .batchSize(3) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) - .build(); - - final StateSync stateSync = new StateSync(stateSyncOptions); - final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(stateDrafts) + UnresolvedTransitionsServiceImpl unresolvedTransitionsService = + new UnresolvedTransitionsServiceImpl(stateSyncOptions); + Set result = + unresolvedTransitionsService + .fetch(new HashSet<>(asList(keyA))) .toCompletableFuture() .join(); + Assertions.assertThat(result.size()).isEqualTo(0); + } - assertThat(stateSyncStatistics).hasValues(3, 1, 0, 2, 1); - Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages.get(0)) - .contains(format("Failed to fetch StateDrafts waiting to be resolved with keys")); - } - - @Test - void sync_WithListOfNullElements_ShouldPrintErrorMessage() { - - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .batchSize(3) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) - .build(); - - final StateSync stateSync = new StateSync(stateSyncOptions); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(Collections.singletonList(null)) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(1 , 0, 0, 1, 0); - Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages.get(0)).contains("StateDraft is null."); - } - - @Test - void sync_WithUpdatedTransition_ShouldUpdateTransitions() { - - final StateDraft stateCDraft = createStateDraft(keyC); - final State stateC = createStateInSource(stateCDraft); - final StateDraft tagetStateCDraft = createStateDraft(keyC); - final State targetStateC = createStateInTarget(tagetStateCDraft); - - final StateDraft stateBDraft = createStateDraft(keyB, stateC); - final State stateB = createStateInSource(stateBDraft); - final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); - final State targetStateB = createStateInTarget(tagetStateBDraft); - - final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); - final State stateA = createStateInSource(stateADraft); - final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB); - final State targetStateA = createStateInTarget(tagetStateADraft); - Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); - Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(1); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .batchSize(3) - .build(); + @Test + void sync_WithExceptionWhenFetchingUnresolvedTransition_ShouldPrintErrorMessage() { - final StateSync stateSync = new StateSync(stateSyncOptions); - final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(stateDrafts) - .toCompletableFuture() - .join(); + final StateDraft stateCDraft = createStateDraft(keyC); + final State stateC = createStateInSource(stateCDraft); - assertThat(stateSyncStatistics).hasValues(3, 0, 1, 0, 0); + final StateDraft stateBDraft = createStateDraft(keyB, stateC); + final State stateB = createStateInSource(stateBDraft); - QueryExecutionUtils.queryAll(CTP_TARGET_CLIENT, StateQueryBuilder - .of() - .plusPredicates(q -> q.key().is(keyA)).build()) - .thenAccept(resultStates -> { - Assertions.assertThat(resultStates.size()).isEqualTo(1); - Assertions.assertThat(resultStates.get(0).getTransitions().size()).isEqualTo(2); - }).toCompletableFuture().join(); - } + final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); + final State stateA = createStateInSource(stateADraft); - @Test - void sync_WithExceptionOnResolvingTransition_ShouldUpdateTransitions() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final StateDraft stateCDraft = createStateDraft(keyC); - final State stateC = createStateInSource(stateCDraft); - final StateDraft stateBDraft = createStateDraft(keyB, stateC); - final State stateB = createStateInSource(stateBDraft); - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any())) - .thenCallRealMethod() - .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); + when(spyClient.execute(any(CustomObjectQuery.class))) + .thenReturn(exceptionallyCompletedFuture(new BadRequestException("a test exception"))) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(spyClient) + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(spyClient) .batchSize(3) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - final StateSync stateSync = new StateSync(stateSyncOptions); - final List stateDrafts = mapToStateDrafts(Arrays.asList(stateB, stateC)); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(stateDrafts) - .toCompletableFuture() - .join(); + final StateSync stateSync = new StateSync(stateSyncOptions); + final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(stateDrafts).toCompletableFuture().join(); - assertThat(stateSyncStatistics).hasValues(2, 0, 0, 2, 0); - Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages.get(0)) - .contains("Failed to fetch existing states with keys"); - Assertions.assertThat(warningCallBackMessages).isEmpty(); - } + assertThat(stateSyncStatistics).hasValues(3, 1, 0, 2, 1); + Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains(format("Failed to fetch StateDrafts waiting to be resolved with keys")); + } + + @Test + void sync_WithListOfNullElements_ShouldPrintErrorMessage() { - @Test - void sync_WithDeletedTransition_ShouldRemoveTransitions() { - - final StateDraft stateCDraft = createStateDraft(keyC); - final State stateC = createStateInSource(stateCDraft); - final StateDraft tagetStateCDraft = createStateDraft(keyC); - final State targetStateC = createStateInTarget(tagetStateCDraft); - - final StateDraft stateBDraft = createStateDraft(keyB, stateC); - final State stateB = createStateInSource(stateBDraft); - final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); - final State targetStateB = createStateInTarget(tagetStateBDraft); - - final StateDraft stateADraft = createStateDraft(keyA, stateB); - final State stateA = createStateInSource(stateADraft); - final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB, targetStateC); - final State targetStateA = createStateInTarget(tagetStateADraft); - Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); - Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(2); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT) .batchSize(3) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - final StateSync stateSync = new StateSync(stateSyncOptions); - final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(stateDrafts) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(3, 0, 1, 0, 0); - - QueryExecutionUtils.queryAll(CTP_TARGET_CLIENT, StateQueryBuilder - .of() - .plusPredicates(q -> q.key().is(keyA)).build()) - .thenAccept(resultStates -> { - Assertions.assertThat(resultStates.size()).isEqualTo(1); - Assertions.assertThat(resultStates.get(0).getTransitions().size()).isEqualTo(1); - }).toCompletableFuture().join(); - } - - @Test - void sync_WithEmptyNewTransition_ShouldRemoveTransitions() { - - final StateDraft stateCDraft = createStateDraft(keyC); - final State stateC = createStateInSource(stateCDraft); - final StateDraft tagetStateCDraft = createStateDraft(keyC); - final State targetStateC = createStateInTarget(tagetStateCDraft); - - final StateDraft stateBDraft = createStateDraft(keyB, stateC); - final State stateB = createStateInSource(stateBDraft); - final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); - final State targetStateB = createStateInTarget(tagetStateBDraft); - - final StateDraft stateADraft = createStateDraft(keyA); - final State stateA = createStateInSource(stateADraft); - final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB, targetStateC); - final State targetStateA = createStateInTarget(tagetStateADraft); - Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); - Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(2); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + final StateSync stateSync = new StateSync(stateSyncOptions); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(Collections.singletonList(null)).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1, 0); + Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages.get(0)).contains("StateDraft is null."); + } + + @Test + void sync_WithUpdatedTransition_ShouldUpdateTransitions() { + + final StateDraft stateCDraft = createStateDraft(keyC); + final State stateC = createStateInSource(stateCDraft); + final StateDraft tagetStateCDraft = createStateDraft(keyC); + final State targetStateC = createStateInTarget(tagetStateCDraft); + + final StateDraft stateBDraft = createStateDraft(keyB, stateC); + final State stateB = createStateInSource(stateBDraft); + final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); + final State targetStateB = createStateInTarget(tagetStateBDraft); + + final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); + final State stateA = createStateInSource(stateADraft); + final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB); + final State targetStateA = createStateInTarget(tagetStateADraft); + Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); + Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(1); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).batchSize(3).build(); + + final StateSync stateSync = new StateSync(stateSyncOptions); + final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(stateDrafts).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(3, 0, 1, 0, 0); + + QueryExecutionUtils.queryAll( + CTP_TARGET_CLIENT, StateQueryBuilder.of().plusPredicates(q -> q.key().is(keyA)).build()) + .thenAccept( + resultStates -> { + Assertions.assertThat(resultStates.size()).isEqualTo(1); + Assertions.assertThat(resultStates.get(0).getTransitions().size()).isEqualTo(2); + }) + .toCompletableFuture() + .join(); + } + + @Test + void sync_WithExceptionOnResolvingTransition_ShouldUpdateTransitions() { + + final StateDraft stateCDraft = createStateDraft(keyC); + final State stateC = createStateInSource(stateCDraft); + final StateDraft stateBDraft = createStateDraft(keyB, stateC); + final State stateB = createStateInSource(stateBDraft); + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any())) + .thenCallRealMethod() + .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); + + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(spyClient) .batchSize(3) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - final StateSync stateSync = new StateSync(stateSyncOptions); - final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(stateDrafts) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(3, 0, 1, 0, 0); - - QueryExecutionUtils.queryAll(CTP_TARGET_CLIENT, StateQueryBuilder - .of() - .plusPredicates(q -> q.key().is(keyA)).build()) - .thenAccept(resultStates -> { - Assertions.assertThat(resultStates.size()).isEqualTo(1); - Assertions.assertThat(resultStates.get(0).getTransitions()).isNull(); - }).toCompletableFuture().join(); - } - - @Test - void sync_WithStateWithoutKey_ShouldAddErrorMessage() { - String nameA = "state-A"; - final StateDraft stateADraft = StateDraftBuilder - .of(null, StateType.REVIEW_STATE) + final StateSync stateSync = new StateSync(stateSyncOptions); + final List stateDrafts = mapToStateDrafts(Arrays.asList(stateB, stateC)); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(stateDrafts).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(2, 0, 0, 2, 0); + Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains("Failed to fetch existing states with keys"); + Assertions.assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_WithDeletedTransition_ShouldRemoveTransitions() { + + final StateDraft stateCDraft = createStateDraft(keyC); + final State stateC = createStateInSource(stateCDraft); + final StateDraft tagetStateCDraft = createStateDraft(keyC); + final State targetStateC = createStateInTarget(tagetStateCDraft); + + final StateDraft stateBDraft = createStateDraft(keyB, stateC); + final State stateB = createStateInSource(stateBDraft); + final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); + final State targetStateB = createStateInTarget(tagetStateBDraft); + + final StateDraft stateADraft = createStateDraft(keyA, stateB); + final State stateA = createStateInSource(stateADraft); + final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB, targetStateC); + final State targetStateA = createStateInTarget(tagetStateADraft); + Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); + Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(2); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).batchSize(3).build(); + + final StateSync stateSync = new StateSync(stateSyncOptions); + final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(stateDrafts).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(3, 0, 1, 0, 0); + + QueryExecutionUtils.queryAll( + CTP_TARGET_CLIENT, StateQueryBuilder.of().plusPredicates(q -> q.key().is(keyA)).build()) + .thenAccept( + resultStates -> { + Assertions.assertThat(resultStates.size()).isEqualTo(1); + Assertions.assertThat(resultStates.get(0).getTransitions().size()).isEqualTo(1); + }) + .toCompletableFuture() + .join(); + } + + @Test + void sync_WithEmptyNewTransition_ShouldRemoveTransitions() { + + final StateDraft stateCDraft = createStateDraft(keyC); + final State stateC = createStateInSource(stateCDraft); + final StateDraft tagetStateCDraft = createStateDraft(keyC); + final State targetStateC = createStateInTarget(tagetStateCDraft); + + final StateDraft stateBDraft = createStateDraft(keyB, stateC); + final State stateB = createStateInSource(stateBDraft); + final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); + final State targetStateB = createStateInTarget(tagetStateBDraft); + + final StateDraft stateADraft = createStateDraft(keyA); + final State stateA = createStateInSource(stateADraft); + final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB, targetStateC); + final State targetStateA = createStateInTarget(tagetStateADraft); + Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); + Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(2); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).batchSize(3).build(); + + final StateSync stateSync = new StateSync(stateSyncOptions); + final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(stateDrafts).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(3, 0, 1, 0, 0); + + QueryExecutionUtils.queryAll( + CTP_TARGET_CLIENT, StateQueryBuilder.of().plusPredicates(q -> q.key().is(keyA)).build()) + .thenAccept( + resultStates -> { + Assertions.assertThat(resultStates.size()).isEqualTo(1); + Assertions.assertThat(resultStates.get(0).getTransitions()).isNull(); + }) + .toCompletableFuture() + .join(); + } + + @Test + void sync_WithStateWithoutKey_ShouldAddErrorMessage() { + String nameA = "state-A"; + final StateDraft stateADraft = + StateDraftBuilder.of(null, StateType.REVIEW_STATE) .name(LocalizedString.ofEnglish(nameA)) .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .initial(true) .build(); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT) .batchSize(3) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - final StateSync stateSync = new StateSync(stateSyncOptions); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(Arrays.asList(stateADraft)) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1, 0); - Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages.get(0)).contains("StateDraft with name:" - + " LocalizedString(en -> state-A) doesn't have a key."); - Assertions.assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_WithUpdatedTransitionAndClientThrowsError_ShouldAddErrorMessage() { - final StateDraft stateCDraft = createStateDraft(keyC); - final State stateC = createStateInSource(stateCDraft); - final StateDraft tagetStateCDraft = createStateDraft(keyC); - final State targetStateC = createStateInTarget(tagetStateCDraft); - - final StateDraft stateBDraft = createStateDraft(keyB, stateC); - final State stateB = createStateInSource(stateBDraft); - final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); - final State targetStateB = createStateInTarget(tagetStateBDraft); - - final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); - final State stateA = createStateInSource(stateADraft); - final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB); - final State targetStateA = createStateInTarget(tagetStateADraft); - Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); - Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(1); - - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final StateUpdateCommand updateCommand = any(StateUpdateCommand.class); - when(spyClient.execute(updateCommand)) - .thenReturn( - exceptionallyCompletedFuture(new BadRequestException("a test exception"))) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(spyClient) + final StateSync stateSync = new StateSync(stateSyncOptions); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(Arrays.asList(stateADraft)).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1, 0); + Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains("StateDraft with name:" + " LocalizedString(en -> state-A) doesn't have a key."); + Assertions.assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_WithUpdatedTransitionAndClientThrowsError_ShouldAddErrorMessage() { + final StateDraft stateCDraft = createStateDraft(keyC); + final State stateC = createStateInSource(stateCDraft); + final StateDraft tagetStateCDraft = createStateDraft(keyC); + final State targetStateC = createStateInTarget(tagetStateCDraft); + + final StateDraft stateBDraft = createStateDraft(keyB, stateC); + final State stateB = createStateInSource(stateBDraft); + final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); + final State targetStateB = createStateInTarget(tagetStateBDraft); + + final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); + final State stateA = createStateInSource(stateADraft); + final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB); + final State targetStateA = createStateInTarget(tagetStateADraft); + Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); + Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(1); + + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final StateUpdateCommand updateCommand = any(StateUpdateCommand.class); + when(spyClient.execute(updateCommand)) + .thenReturn(exceptionallyCompletedFuture(new BadRequestException("a test exception"))) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); + + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(spyClient) .batchSize(3) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - final StateSync stateSync = new StateSync(stateSyncOptions); - final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(stateDrafts) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(3, 0, 0, 1, 0); - Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages.get(0)).contains(" detailMessage: a test exception"); - Assertions.assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_WithoutAnyNewStateDraft_ShouldProcessNothing() { - - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + final StateSync stateSync = new StateSync(stateSyncOptions); + final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(stateDrafts).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(3, 0, 0, 1, 0); + Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains(" detailMessage: a test exception"); + Assertions.assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_WithoutAnyNewStateDraft_ShouldProcessNothing() { + + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT).batchSize(3).build(); + final StateSync stateSync = new StateSync(stateSyncOptions); + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(Collections.emptyList()).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(0, 0, 0, 0, 0); + } + + @Test + void sync_WithFailureInKeysToIdCreation_ShouldAddErrorMessage() { + final StateDraft stateCDraft = createStateDraft(keyC); + final State stateC = createStateInSource(stateCDraft); + final StateDraft tagetStateCDraft = createStateDraft(keyC); + final State targetStateC = createStateInTarget(tagetStateCDraft); + + final StateDraft stateBDraft = createStateDraft(keyB, stateC); + final State stateB = createStateInSource(stateBDraft); + final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); + final State targetStateB = createStateInTarget(tagetStateBDraft); + + final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); + final State stateA = createStateInSource(stateADraft); + final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB); + final State targetStateA = createStateInTarget(tagetStateADraft); + Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); + Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(1); + + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(ResourceKeyIdGraphQlRequest.class))) + .thenReturn(exceptionallyCompletedFuture(new BadRequestException("a test exception"))) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); + + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(spyClient) .batchSize(3) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - final StateSync stateSync = new StateSync(stateSyncOptions); - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(Collections.emptyList()) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(0, 0, 0, 0, 0); - - } - - @Test - void sync_WithFailureInKeysToIdCreation_ShouldAddErrorMessage() { - final StateDraft stateCDraft = createStateDraft(keyC); - final State stateC = createStateInSource(stateCDraft); - final StateDraft tagetStateCDraft = createStateDraft(keyC); - final State targetStateC = createStateInTarget(tagetStateCDraft); - final StateDraft stateBDraft = createStateDraft(keyB, stateC); - final State stateB = createStateInSource(stateBDraft); - final StateDraft tagetStateBDraft = createStateDraft(keyB, targetStateC); - final State targetStateB = createStateInTarget(tagetStateBDraft); - - final StateDraft stateADraft = createStateDraft(keyA, stateB, stateC); - final State stateA = createStateInSource(stateADraft); - final StateDraft tagetStateADraft = createStateDraft(keyA, targetStateB); - final State targetStateA = createStateInTarget(tagetStateADraft); - Assertions.assertThat(targetStateB.getTransitions().size()).isEqualTo(1); - Assertions.assertThat(targetStateA.getTransitions().size()).isEqualTo(1); - - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(ResourceKeyIdGraphQlRequest.class))) - .thenReturn(exceptionallyCompletedFuture(new BadRequestException("a test exception"))) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(spyClient) - .batchSize(3) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) - .build(); - - final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); - // test - final StateSyncStatistics stateSyncStatistics = new StateSync(stateSyncOptions) - .sync(stateDrafts) - .toCompletableFuture() - .join(); - - assertThat(stateSyncStatistics).hasValues(3, 0, 0, 3, 0); - Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages.get(0)).isEqualTo("Failed to build a cache of keys to ids."); - Assertions.assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_WithStateWithEmptyTransition_ShouldAddErrorMessage() { - final StateDraft stateCDraft = StateDraftBuilder - .of(keyC, StateType.REVIEW_STATE) + final List stateDrafts = mapToStateDrafts(Arrays.asList(stateA, stateB, stateC)); + // test + final StateSyncStatistics stateSyncStatistics = + new StateSync(stateSyncOptions).sync(stateDrafts).toCompletableFuture().join(); + + assertThat(stateSyncStatistics).hasValues(3, 0, 0, 3, 0); + Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages.get(0)) + .isEqualTo("Failed to build a cache of keys to ids."); + Assertions.assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_WithStateWithEmptyTransition_ShouldAddErrorMessage() { + final StateDraft stateCDraft = + StateDraftBuilder.of(keyC, StateType.REVIEW_STATE) .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .transitions(new HashSet<>(Arrays.asList(Reference.of(State.referenceTypeId(), "")))) .initial(true) .build(); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT) .batchSize(3) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - // test - final StateSyncStatistics stateSyncStatistics = new StateSync(stateSyncOptions) + // test + final StateSyncStatistics stateSyncStatistics = + new StateSync(stateSyncOptions) .sync(Arrays.asList(stateCDraft)) .toCompletableFuture() .join(); - assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1, 0); - Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages).isNotEmpty(); - Assertions.assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format("StateDraft with key: '%s' has invalid state transitions", keyC)); - Assertions.assertThat(warningCallBackMessages).isEmpty(); - } - - @Test - void sync_WithStateWithEmptyTransitionShouldBeResolved_ShouldAddErrorMessage() { - final StateDraft stateCDraft = StateDraftBuilder - .of(keyC, StateType.REVIEW_STATE) + assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1, 0); + Assertions.assertThat(errorCallBackExceptions).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages).isNotEmpty(); + Assertions.assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format("StateDraft with key: '%s' has invalid state transitions", keyC)); + Assertions.assertThat(warningCallBackMessages).isEmpty(); + } + + @Test + void sync_WithStateWithEmptyTransitionShouldBeResolved_ShouldAddErrorMessage() { + final StateDraft stateCDraft = + StateDraftBuilder.of(keyC, StateType.REVIEW_STATE) .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) .transitions(new HashSet<>(Arrays.asList(Reference.of(State.referenceTypeId(), "")))) .initial(true) .build(); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT) .batchSize(3) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - // test - StateServiceImpl stateService = new StateServiceImpl(stateSyncOptions); - final StateReferenceResolver stateReferenceResolver = new StateReferenceResolver(stateSyncOptions, - stateService); - - CompletionStage result = stateReferenceResolver.resolveReferences(stateCDraft); - result.exceptionally(exception -> { - Assertions.assertThat(exception.getMessage()) - .contains(format("Failed to resolve 'transition' reference on StateDraft with key:'%s", keyC)); - return null; - } - ).toCompletableFuture().join(); - } - - private State createStateInSource(final StateDraft draft) { - return executeBlocking(CTP_SOURCE_CLIENT.execute(StateCreateCommand.of(draft) - .withExpansionPaths(StateExpansionModel::transitions))); - } - - private State createStateInTarget(final StateDraft draft) { - return executeBlocking(CTP_TARGET_CLIENT.execute(StateCreateCommand.of(draft) - .withExpansionPaths(StateExpansionModel::transitions))); - } - - - private StateDraft createStateDraftReferencingStateDrafts(final String key, - final StateDraft... transitionStatesDraft) { - List> references = new ArrayList<>(); - if (transitionStatesDraft.length > 0) { - for (StateDraft transitionState : transitionStatesDraft) { - references.add(referenceOfId(transitionState.getKey())); - - } - } - return createStateDraftWithReference(key, references); - } - - private StateDraft createStateDraft(final String key, final State... transitionStates) { - List> references = new ArrayList<>(); - if (transitionStates.length > 0) { - for (State transitionState : transitionStates) { - references.add(referenceOfId(transitionState.getId())); - } - } - return createStateDraftWithReference(key, references); + // test + StateServiceImpl stateService = new StateServiceImpl(stateSyncOptions); + final StateReferenceResolver stateReferenceResolver = + new StateReferenceResolver(stateSyncOptions, stateService); + + CompletionStage result = stateReferenceResolver.resolveReferences(stateCDraft); + result + .exceptionally( + exception -> { + Assertions.assertThat(exception.getMessage()) + .contains( + format( + "Failed to resolve 'transition' reference on StateDraft with key:'%s", + keyC)); + return null; + }) + .toCompletableFuture() + .join(); + } + + private State createStateInSource(final StateDraft draft) { + return executeBlocking( + CTP_SOURCE_CLIENT.execute( + StateCreateCommand.of(draft).withExpansionPaths(StateExpansionModel::transitions))); + } + + private State createStateInTarget(final StateDraft draft) { + return executeBlocking( + CTP_TARGET_CLIENT.execute( + StateCreateCommand.of(draft).withExpansionPaths(StateExpansionModel::transitions))); + } + + private StateDraft createStateDraftReferencingStateDrafts( + final String key, final StateDraft... transitionStatesDraft) { + List> references = new ArrayList<>(); + if (transitionStatesDraft.length > 0) { + for (StateDraft transitionState : transitionStatesDraft) { + references.add(referenceOfId(transitionState.getKey())); + } } - - private StateDraft createStateDraftWithReference(final String key, final List> references) { - return StateDraftBuilder - .of(key, StateType.REVIEW_STATE) - .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) - .transitions(new HashSet<>(references)) - .initial(true) - .build(); + return createStateDraftWithReference(key, references); + } + + private StateDraft createStateDraft(final String key, final State... transitionStates) { + List> references = new ArrayList<>(); + if (transitionStates.length > 0) { + for (State transitionState : transitionStates) { + references.add(referenceOfId(transitionState.getId())); + } } + return createStateDraftWithReference(key, references); + } + + private StateDraft createStateDraftWithReference( + final String key, final List> references) { + return StateDraftBuilder.of(key, StateType.REVIEW_STATE) + .roles(Collections.singleton(StateRole.REVIEW_INCLUDED_IN_STATISTICS)) + .transitions(new HashSet<>(references)) + .initial(true) + .build(); + } } - diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/taxcategories/TaxCategorySyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/taxcategories/TaxCategorySyncIT.java index 397eeb2a6d..39adcc43c6 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/taxcategories/TaxCategorySyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/taxcategories/TaxCategorySyncIT.java @@ -1,5 +1,17 @@ package com.commercetools.sync.integration.externalsource.taxcategories; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.deleteTaxCategories; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.getTaxCategoryByKey; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import com.commercetools.sync.taxcategories.TaxCategorySync; import com.commercetools.sync.taxcategories.TaxCategorySyncOptions; import com.commercetools.sync.taxcategories.TaxCategorySyncOptionsBuilder; @@ -20,317 +32,323 @@ import io.sphere.sdk.taxcategories.commands.TaxCategoryUpdateCommand; import io.sphere.sdk.taxcategories.queries.TaxCategoryQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.deleteTaxCategories; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.getTaxCategoryByKey; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class TaxCategorySyncIT { - @BeforeEach - void setup() { - deleteTaxCategories(CTP_TARGET_CLIENT); + @BeforeEach + void setup() { + deleteTaxCategories(CTP_TARGET_CLIENT); - final SubRate subRate1 = SubRate.of("subRate-1", 0.08); - final SubRate subRate2 = SubRate.of("subRate-2", 0.11); + final SubRate subRate1 = SubRate.of("subRate-1", 0.08); + final SubRate subRate2 = SubRate.of("subRate-2", 0.11); - final TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("%19 VAT DE", 0.19, false, CountryCode.DE) + final TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("%19 VAT DE", 0.19, false, CountryCode.DE) .subRates(asList(subRate1, subRate2)) .build(); - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("tax-category-name", singletonList(taxRateDraft), "tax-category-description") + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "tax-category-name", singletonList(taxRateDraft), "tax-category-description") .key("tax-category-key") .build(); - executeBlocking(CTP_TARGET_CLIENT.execute(TaxCategoryCreateCommand.of(taxCategoryDraft))); - } + executeBlocking(CTP_TARGET_CLIENT.execute(TaxCategoryCreateCommand.of(taxCategoryDraft))); + } - @AfterAll - static void tearDown() { - deleteTaxCategories(CTP_TARGET_CLIENT); - } + @AfterAll + static void tearDown() { + deleteTaxCategories(CTP_TARGET_CLIENT); + } - @Test - void sync_withNewTaxCategory_shouldCreateTaxCategory() { - final SubRate subRate1 = SubRate.of("subRate-1", 0.05); - final SubRate subRate2 = SubRate.of("subRate-2", 0.06); + @Test + void sync_withNewTaxCategory_shouldCreateTaxCategory() { + final SubRate subRate1 = SubRate.of("subRate-1", 0.05); + final SubRate subRate2 = SubRate.of("subRate-2", 0.06); - final TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("%11 US", 0.11, false, CountryCode.US) + final TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("%11 US", 0.11, false, CountryCode.US) .subRates(asList(subRate1, subRate2)) .build(); - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("tax-category-name-new", singletonList(taxRateDraft), "tax-category-description-new") + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "tax-category-name-new", + singletonList(taxRateDraft), + "tax-category-description-new") .key("tax-category-key-new") .build(); - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final TaxCategorySync taxCategorySync = new TaxCategorySync(taxCategorySyncOptions); + final TaxCategorySync taxCategorySync = new TaxCategorySync(taxCategorySyncOptions); - // test - final TaxCategorySyncStatistics taxCategorySyncStatistics = taxCategorySync - .sync(singletonList(taxCategoryDraft)) - .toCompletableFuture() - .join(); + // test + final TaxCategorySyncStatistics taxCategorySyncStatistics = + taxCategorySync.sync(singletonList(taxCategoryDraft)).toCompletableFuture().join(); - assertThat(taxCategorySyncStatistics).hasValues(1, 1, 0, 0); - } + assertThat(taxCategorySyncStatistics).hasValues(1, 1, 0, 0); + } - @Test - void sync_WithUpdatedTaxCategory_ShouldUpdateTaxCategory() { - // preparation - final SubRate subRate1 = SubRate.of("subRate-1", 0.07); - final SubRate subRate2 = SubRate.of("subRate-2", 0.09); + @Test + void sync_WithUpdatedTaxCategory_ShouldUpdateTaxCategory() { + // preparation + final SubRate subRate1 = SubRate.of("subRate-1", 0.07); + final SubRate subRate2 = SubRate.of("subRate-2", 0.09); - final TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("%16 VAT", 0.16, true, CountryCode.DE) + final TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("%16 VAT", 0.16, true, CountryCode.DE) .subRates(asList(subRate1, subRate2)) .build(); - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("tax-category-name-updated", singletonList(taxRateDraft), "tax-category-description-updated") + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "tax-category-name-updated", + singletonList(taxRateDraft), + "tax-category-description-updated") .key("tax-category-key") .build(); - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final TaxCategorySync taxCategorySync = new TaxCategorySync(taxCategorySyncOptions); - - // test - final TaxCategorySyncStatistics taxCategorySyncStatistics = taxCategorySync - .sync(singletonList(taxCategoryDraft)) - .toCompletableFuture() - .join(); - - // assertion - assertThat(taxCategorySyncStatistics).hasValues(1, 0, 1, 0); - - final Optional oldTaxCategoryAfter = getTaxCategoryByKey(CTP_TARGET_CLIENT, "tax-category-key"); - - Assertions.assertThat(oldTaxCategoryAfter).hasValueSatisfying(taxCategory -> { - Assertions.assertThat(taxCategory.getName()).isEqualTo("tax-category-name-updated"); - Assertions.assertThat(taxCategory.getDescription()).isEqualTo("tax-category-description-updated"); - final TaxRate taxRate = taxCategory.getTaxRates().get(0); - Assertions.assertThat(taxRate.getName()).isEqualTo("%16 VAT"); - Assertions.assertThat(taxRate.getAmount()).isEqualTo(0.16); - Assertions.assertThat(taxRate.getCountry()).isEqualTo(CountryCode.DE); - Assertions.assertThat(taxRate.isIncludedInPrice()).isEqualTo(true); - Assertions.assertThat(taxRate.getSubRates()).isEqualTo(asList(subRate1, subRate2)); - }); - } - - @Test - void sync_withEqualTaxCategory_shouldNotUpdateTaxCategory() { - final SubRate subRate1 = SubRate.of("subRate-1", 0.08); - final SubRate subRate2 = SubRate.of("subRate-2", 0.11); - - final TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("%19 VAT DE", 0.19, false, CountryCode.DE) + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + + final TaxCategorySync taxCategorySync = new TaxCategorySync(taxCategorySyncOptions); + + // test + final TaxCategorySyncStatistics taxCategorySyncStatistics = + taxCategorySync.sync(singletonList(taxCategoryDraft)).toCompletableFuture().join(); + + // assertion + assertThat(taxCategorySyncStatistics).hasValues(1, 0, 1, 0); + + final Optional oldTaxCategoryAfter = + getTaxCategoryByKey(CTP_TARGET_CLIENT, "tax-category-key"); + + Assertions.assertThat(oldTaxCategoryAfter) + .hasValueSatisfying( + taxCategory -> { + Assertions.assertThat(taxCategory.getName()).isEqualTo("tax-category-name-updated"); + Assertions.assertThat(taxCategory.getDescription()) + .isEqualTo("tax-category-description-updated"); + final TaxRate taxRate = taxCategory.getTaxRates().get(0); + Assertions.assertThat(taxRate.getName()).isEqualTo("%16 VAT"); + Assertions.assertThat(taxRate.getAmount()).isEqualTo(0.16); + Assertions.assertThat(taxRate.getCountry()).isEqualTo(CountryCode.DE); + Assertions.assertThat(taxRate.isIncludedInPrice()).isEqualTo(true); + Assertions.assertThat(taxRate.getSubRates()).isEqualTo(asList(subRate1, subRate2)); + }); + } + + @Test + void sync_withEqualTaxCategory_shouldNotUpdateTaxCategory() { + final SubRate subRate1 = SubRate.of("subRate-1", 0.08); + final SubRate subRate2 = SubRate.of("subRate-2", 0.11); + + final TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("%19 VAT DE", 0.19, false, CountryCode.DE) .subRates(asList(subRate1, subRate2)) .build(); - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("tax-category-name", singletonList(taxRateDraft), "tax-category-description") + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "tax-category-name", singletonList(taxRateDraft), "tax-category-description") .key("tax-category-key") .build(); - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final TaxCategorySync taxCategorySync = new TaxCategorySync(taxCategorySyncOptions); - - // test - final TaxCategorySyncStatistics taxCategorySyncStatistics = taxCategorySync - .sync(singletonList(taxCategoryDraft)) - .toCompletableFuture() - .join(); - - assertThat(taxCategorySyncStatistics).hasValues(1, 0, 0, 0); - } - - @Test - void sync_withChangedTaxCategoryButConcurrentModificationException_shouldRetryAndUpdateTaxCategory() { - // preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); - - List errorCallBackMessages = new ArrayList<>(); - List warningCallBackMessages = new ArrayList<>(); - List errorCallBackExceptions = new ArrayList<>(); - final TaxCategorySyncOptions spyOptions = TaxCategorySyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + + final TaxCategorySync taxCategorySync = new TaxCategorySync(taxCategorySyncOptions); + + // test + final TaxCategorySyncStatistics taxCategorySyncStatistics = + taxCategorySync.sync(singletonList(taxCategoryDraft)).toCompletableFuture().join(); + + assertThat(taxCategorySyncStatistics).hasValues(1, 0, 0, 0); + } + + @Test + void + sync_withChangedTaxCategoryButConcurrentModificationException_shouldRetryAndUpdateTaxCategory() { + // preparation + final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); + + List errorCallBackMessages = new ArrayList<>(); + List warningCallBackMessages = new ArrayList<>(); + List errorCallBackExceptions = new ArrayList<>(); + final TaxCategorySyncOptions spyOptions = + TaxCategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final TaxCategorySync taxCategorySync = new TaxCategorySync(spyOptions); + final TaxCategorySync taxCategorySync = new TaxCategorySync(spyOptions); - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("tax-category-name-updated", null, "tax-category-description-updated") + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "tax-category-name-updated", null, "tax-category-description-updated") .key("tax-category-key") .build(); - // test - final TaxCategorySyncStatistics taxCategorySyncStatistics = taxCategorySync - .sync(singletonList(taxCategoryDraft)) - .toCompletableFuture() - .join(); - - assertThat(taxCategorySyncStatistics).hasValues(1, 0, 1, 0); - Assertions.assertThat(errorCallBackExceptions).isEmpty(); - Assertions.assertThat(errorCallBackMessages).isEmpty(); - Assertions.assertThat(warningCallBackMessages).isEmpty(); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdate() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final TaxCategoryUpdateCommand updateCommand = any(TaxCategoryUpdateCommand.class); - when(spyClient.execute(updateCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - return spyClient; - } - - @Test - void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { - // preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); - - List errorCallBackMessages = new ArrayList<>(); - List errorCallBackExceptions = new ArrayList<>(); - final TaxCategorySyncOptions spyOptions = TaxCategorySyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - //.warningCallback(warningCallBackMessages::add) + // test + final TaxCategorySyncStatistics taxCategorySyncStatistics = + taxCategorySync.sync(singletonList(taxCategoryDraft)).toCompletableFuture().join(); + + assertThat(taxCategorySyncStatistics).hasValues(1, 0, 1, 0); + Assertions.assertThat(errorCallBackExceptions).isEmpty(); + Assertions.assertThat(errorCallBackMessages).isEmpty(); + Assertions.assertThat(warningCallBackMessages).isEmpty(); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdate() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final TaxCategoryUpdateCommand updateCommand = any(TaxCategoryUpdateCommand.class); + when(spyClient.execute(updateCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + return spyClient; + } + + @Test + void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); + + List errorCallBackMessages = new ArrayList<>(); + List errorCallBackExceptions = new ArrayList<>(); + final TaxCategorySyncOptions spyOptions = + TaxCategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + // .warningCallback(warningCallBackMessages::add) .build(); - final TaxCategorySync taxCategorySync = new TaxCategorySync(spyOptions); + final TaxCategorySync taxCategorySync = new TaxCategorySync(spyOptions); - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("tax-category-name-updated", null, "tax-category-description-updated") + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "tax-category-name-updated", null, "tax-category-description-updated") .key("tax-category-key") .build(); - // test - final TaxCategorySyncStatistics taxCategorySyncStatistics = taxCategorySync - .sync(singletonList(taxCategoryDraft)) - .toCompletableFuture() - .join(); - - // Test and assertion - assertThat(taxCategorySyncStatistics).hasValues(1, 0, 0, 1); - Assertions.assertThat(errorCallBackMessages).hasSize(1); - Assertions.assertThat(errorCallBackExceptions).hasSize(1); - - Assertions.assertThat(errorCallBackExceptions.get(0).getCause()).isExactlyInstanceOf(BadGatewayException.class); - Assertions.assertThat(errorCallBackMessages.get(0)).contains( - format("Failed to update tax category with key: '%s'. Reason: Failed to fetch from CTP while retrying " - + "after concurrency modification.", taxCategoryDraft.getKey())); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final TaxCategoryUpdateCommand updateCommand = any(TaxCategoryUpdateCommand.class); - when(spyClient.execute(updateCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - final TaxCategoryQuery taxCategoryQuery = any(TaxCategoryQuery.class); - when(spyClient.execute(taxCategoryQuery)) - .thenCallRealMethod() // Call real fetch on fetching matching tax categories - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); - - return spyClient; - } - - @Test - void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { - // preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); - - List errorCallBackMessages = new ArrayList<>(); - List errorCallBackExceptions = new ArrayList<>(); - final TaxCategorySyncOptions spyOptions = TaxCategorySyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + // test + final TaxCategorySyncStatistics taxCategorySyncStatistics = + taxCategorySync.sync(singletonList(taxCategoryDraft)).toCompletableFuture().join(); + + // Test and assertion + assertThat(taxCategorySyncStatistics).hasValues(1, 0, 0, 1); + Assertions.assertThat(errorCallBackMessages).hasSize(1); + Assertions.assertThat(errorCallBackExceptions).hasSize(1); + + Assertions.assertThat(errorCallBackExceptions.get(0).getCause()) + .isExactlyInstanceOf(BadGatewayException.class); + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to update tax category with key: '%s'. Reason: Failed to fetch from CTP while retrying " + + "after concurrency modification.", + taxCategoryDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final TaxCategoryUpdateCommand updateCommand = any(TaxCategoryUpdateCommand.class); + when(spyClient.execute(updateCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + final TaxCategoryQuery taxCategoryQuery = any(TaxCategoryQuery.class); + when(spyClient.execute(taxCategoryQuery)) + .thenCallRealMethod() // Call real fetch on fetching matching tax categories + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); + + return spyClient; + } + + @Test + void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); + + List errorCallBackMessages = new ArrayList<>(); + List errorCallBackExceptions = new ArrayList<>(); + final TaxCategorySyncOptions spyOptions = + TaxCategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final TaxCategorySync taxCategorySync = new TaxCategorySync(spyOptions); + final TaxCategorySync taxCategorySync = new TaxCategorySync(spyOptions); - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("tax-category-name-updated", null, "tax-category-description-updated") + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "tax-category-name-updated", null, "tax-category-description-updated") .key("tax-category-key") .build(); - final TaxCategorySyncStatistics taxCategorySyncStatistics = taxCategorySync - .sync(singletonList(taxCategoryDraft)) - .toCompletableFuture() - .join(); - - // Test and assertion - assertThat(taxCategorySyncStatistics).hasValues(1, 0, 0, 1); - Assertions.assertThat(errorCallBackMessages).hasSize(1); - Assertions.assertThat(errorCallBackExceptions).hasSize(1); - - Assertions.assertThat(errorCallBackMessages.get(0)).contains( - format("Failed to update tax category with key: '%s'. Reason: Not found when attempting to fetch while" - + " retrying after concurrency modification.", taxCategoryDraft.getKey())); - } - - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final TaxCategoryUpdateCommand taxCategoryUpdateCommand = any(TaxCategoryUpdateCommand.class); - when(spyClient.execute(taxCategoryUpdateCommand)) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); - - final TaxCategoryQuery taxCategoryQuery = any(TaxCategoryQuery.class); - - when(spyClient.execute(taxCategoryQuery)) - .thenCallRealMethod() // Call real fetch on fetching matching tax categories - .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); - - return spyClient; - } + final TaxCategorySyncStatistics taxCategorySyncStatistics = + taxCategorySync.sync(singletonList(taxCategoryDraft)).toCompletableFuture().join(); + + // Test and assertion + assertThat(taxCategorySyncStatistics).hasValues(1, 0, 0, 1); + Assertions.assertThat(errorCallBackMessages).hasSize(1); + Assertions.assertThat(errorCallBackExceptions).hasSize(1); + + Assertions.assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to update tax category with key: '%s'. Reason: Not found when attempting to fetch while" + + " retrying after concurrency modification.", + taxCategoryDraft.getKey())); + } + + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final TaxCategoryUpdateCommand taxCategoryUpdateCommand = any(TaxCategoryUpdateCommand.class); + when(spyClient.execute(taxCategoryUpdateCommand)) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new ConcurrentModificationException())) + .thenCallRealMethod(); + + final TaxCategoryQuery taxCategoryQuery = any(TaxCategoryQuery.class); + + when(spyClient.execute(taxCategoryQuery)) + .thenCallRealMethod() // Call real fetch on fetching matching tax categories + .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); + + return spyClient; + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/externalsource/types/TypeSyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/externalsource/types/TypeSyncIT.java index 859f22e22a..6fd6a7179a 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/externalsource/types/TypeSyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/externalsource/types/TypeSyncIT.java @@ -1,5 +1,31 @@ package com.commercetools.sync.integration.externalsource.types; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_2; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_3; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_LABEL_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_NAME_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_DESCRIPTION_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_DESCRIPTION_2; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_KEY_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_KEY_2; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_NAME_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_NAME_2; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.getTypeByKey; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.populateTargetProject; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import com.commercetools.sync.types.TypeSync; import com.commercetools.sync.types.TypeSyncOptions; import com.commercetools.sync.types.TypeSyncOptionsBuilder; @@ -25,11 +51,6 @@ import io.sphere.sdk.types.commands.TypeCreateCommand; import io.sphere.sdk.types.commands.TypeUpdateCommand; import io.sphere.sdk.types.queries.TypeQuery; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -38,797 +59,766 @@ import java.util.concurrent.CompletionException; import java.util.stream.Collectors; import java.util.stream.IntStream; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_2; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_3; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_LABEL_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_NAME_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_DESCRIPTION_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_DESCRIPTION_2; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_KEY_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_KEY_2; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_NAME_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_NAME_2; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.getTypeByKey; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.populateTargetProject; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +class TypeSyncIT { + /** + * Deletes types from the target CTP projects. Populates the target CTP project with test data. + */ + @BeforeEach + void setup() { + deleteTypes(CTP_TARGET_CLIENT); + populateTargetProject(); + } + + /** + * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} + * projects that were set up in this test class. + */ + @AfterAll + static void tearDown() { + deleteTypes(CTP_TARGET_CLIENT); + } + + @Test + void sync_WithUpdatedType_ShouldUpdateType() { + // preparation + final Optional oldTypeBefore = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); + assertThat(oldTypeBefore).isNotEmpty(); + + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + TYPE_KEY_1, TYPE_NAME_2, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_2) + .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) + .build(); -class TypeSyncIT { + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - /** - * Deletes types from the target CTP projects. - * Populates the target CTP project with test data. - */ - @BeforeEach - void setup() { - deleteTypes(CTP_TARGET_CLIENT); - populateTargetProject(); - } - - /** - * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} projects that - * were set up in this test class. - */ - @AfterAll - static void tearDown() { - deleteTypes(CTP_TARGET_CLIENT); - } - - - @Test - void sync_WithUpdatedType_ShouldUpdateType() { - // preparation - final Optional oldTypeBefore = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); - assertThat(oldTypeBefore).isNotEmpty(); - - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - TYPE_KEY_1, - TYPE_NAME_2, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_2) - .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) - .build(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - // test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture() - .join(); - - //assertions - assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); - - - final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); - - assertThat(oldTypeAfter).isNotEmpty(); - assertThat(oldTypeAfter).hasValueSatisfying(type -> { - assertThat(type.getName()).isEqualTo(TYPE_NAME_2); - assertThat(type.getDescription()).isEqualTo(TYPE_DESCRIPTION_2); - assertFieldDefinitionsAreEqual(type.getFieldDefinitions(), singletonList(FIELD_DEFINITION_1)); - }); - } - - @Test - void sync_WithNewType_ShouldCreateType() { - //preparation - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - TYPE_KEY_2, - TYPE_NAME_2, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_2) - .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) - .build(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - //test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture() - .join(); - - //assertions - assertThat(typeSyncStatistics).hasValues(1, 1, 0, 0); - - final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_2); - - assertThat(oldTypeAfter).isNotEmpty(); - assertThat(oldTypeAfter).hasValueSatisfying(type -> { - assertThat(type.getName()).isEqualTo(TYPE_NAME_2); - assertThat(type.getDescription()).isEqualTo(TYPE_DESCRIPTION_2); - assertFieldDefinitionsAreEqual(type.getFieldDefinitions(), singletonList(FIELD_DEFINITION_1)); - }); - } - - @Test - void sync_WithUpdatedType_WithNewFieldDefinitions_ShouldUpdateTypeAddingFieldDefinition() { - //preparation - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - TYPE_KEY_1, - TYPE_NAME_1, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2, FIELD_DEFINITION_3)) - .build(); + final TypeSync typeSync = new TypeSync(typeSyncOptions); + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + // assertions + assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); - final TypeSync typeSync = new TypeSync(typeSyncOptions); + final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); - //test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture() - .join(); + assertThat(oldTypeAfter).isNotEmpty(); + assertThat(oldTypeAfter) + .hasValueSatisfying( + type -> { + assertThat(type.getName()).isEqualTo(TYPE_NAME_2); + assertThat(type.getDescription()).isEqualTo(TYPE_DESCRIPTION_2); + assertFieldDefinitionsAreEqual( + type.getFieldDefinitions(), singletonList(FIELD_DEFINITION_1)); + }); + } + + @Test + void sync_WithNewType_ShouldCreateType() { + // preparation + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + TYPE_KEY_2, TYPE_NAME_2, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_2) + .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) + .build(); - //assertions - assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); + final TypeSync typeSync = new TypeSync(typeSyncOptions); - assertThat(oldTypeAfter).hasValueSatisfying(type -> - assertFieldDefinitionsAreEqual(type.getFieldDefinitions(), - asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2, FIELD_DEFINITION_3))); - } + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); - @Test - void sync_WithUpdatedType_WithoutOldFieldDefinition_ShouldUpdateTypeRemovingFieldDefinition() { - final Optional oldTypeBefore = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); - assertThat(oldTypeBefore).isNotEmpty(); + // assertions + assertThat(typeSyncStatistics).hasValues(1, 1, 0, 0); - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - TYPE_KEY_1, - TYPE_NAME_1, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) - .build(); + final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_2); + + assertThat(oldTypeAfter).isNotEmpty(); + assertThat(oldTypeAfter) + .hasValueSatisfying( + type -> { + assertThat(type.getName()).isEqualTo(TYPE_NAME_2); + assertThat(type.getDescription()).isEqualTo(TYPE_DESCRIPTION_2); + assertFieldDefinitionsAreEqual( + type.getFieldDefinitions(), singletonList(FIELD_DEFINITION_1)); + }); + } + + @Test + void sync_WithUpdatedType_WithNewFieldDefinitions_ShouldUpdateTypeAddingFieldDefinition() { + // preparation + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + TYPE_KEY_1, TYPE_NAME_1, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2, FIELD_DEFINITION_3)) + .build(); - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final TypeSync typeSync = new TypeSync(typeSyncOptions); + final TypeSync typeSync = new TypeSync(typeSyncOptions); - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture().join(); + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); - assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); + // assertions + assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); - final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); + final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); - assertThat(oldTypeAfter).hasValueSatisfying(type -> - assertFieldDefinitionsAreEqual(type.getFieldDefinitions(), singletonList(FIELD_DEFINITION_1))); - } + assertThat(oldTypeAfter) + .hasValueSatisfying( + type -> + assertFieldDefinitionsAreEqual( + type.getFieldDefinitions(), + asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2, FIELD_DEFINITION_3))); + } - @Test - void sync_WithUpdatedType_ChangingFieldDefinitionOrder_ShouldUpdateTypeChangingFieldDefinitionOrder() { - //preparation - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - TYPE_KEY_1, - TYPE_NAME_1, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(asList(FIELD_DEFINITION_2, FIELD_DEFINITION_3, FIELD_DEFINITION_1)) - .build(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - //test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture().join(); - - //assertions - assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); - - final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); - - assertThat(oldTypeAfter).hasValueSatisfying(type -> - assertFieldDefinitionsAreEqual(type.getFieldDefinitions(), - asList(FIELD_DEFINITION_2, FIELD_DEFINITION_3, FIELD_DEFINITION_1))); - } - - @Test - void sync_WithUpdatedType_WithUpdatedFieldDefinition_ShouldUpdateTypeUpdatingFieldDefinition() { - //preparation - final FieldDefinition fieldDefinitionUpdated = FieldDefinition.of( - StringFieldType.of(), - FIELD_DEFINITION_NAME_1, - LocalizedString.ofEnglish("label_1_updated"), - true, - TextInputHint.MULTI_LINE); - - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - TYPE_KEY_1, - TYPE_NAME_1, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(singletonList(fieldDefinitionUpdated)) - .build(); + @Test + void sync_WithUpdatedType_WithoutOldFieldDefinition_ShouldUpdateTypeRemovingFieldDefinition() { + final Optional oldTypeBefore = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); + assertThat(oldTypeBefore).isNotEmpty(); - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + TYPE_KEY_1, TYPE_NAME_1, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) + .build(); - final TypeSync typeSync = new TypeSync(typeSyncOptions); + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - //test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture().join(); + final TypeSync typeSync = new TypeSync(typeSyncOptions); - //assertions - assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); - final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); + assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); - assertThat(oldTypeAfter).hasValueSatisfying(type -> - assertFieldDefinitionsAreEqual(type.getFieldDefinitions(), - singletonList(fieldDefinitionUpdated))); - } + final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); - @Test - void sync_WithoutKey_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - null, - TYPE_NAME_1, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2)) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); + assertThat(oldTypeAfter) + .hasValueSatisfying( + type -> + assertFieldDefinitionsAreEqual( + type.getFieldDefinitions(), singletonList(FIELD_DEFINITION_1))); + } + + @Test + void + sync_WithUpdatedType_ChangingFieldDefinitionOrder_ShouldUpdateTypeChangingFieldDefinitionOrder() { + // preparation + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + TYPE_KEY_1, TYPE_NAME_1, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(asList(FIELD_DEFINITION_2, FIELD_DEFINITION_3, FIELD_DEFINITION_1)) + .build(); + + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); + + final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); + + assertThat(oldTypeAfter) + .hasValueSatisfying( + type -> + assertFieldDefinitionsAreEqual( + type.getFieldDefinitions(), + asList(FIELD_DEFINITION_2, FIELD_DEFINITION_3, FIELD_DEFINITION_1))); + } + + @Test + void sync_WithUpdatedType_WithUpdatedFieldDefinition_ShouldUpdateTypeUpdatingFieldDefinition() { + // preparation + final FieldDefinition fieldDefinitionUpdated = + FieldDefinition.of( + StringFieldType.of(), + FIELD_DEFINITION_NAME_1, + LocalizedString.ofEnglish("label_1_updated"), + true, + TextInputHint.MULTI_LINE); + + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + TYPE_KEY_1, TYPE_NAME_1, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(singletonList(fieldDefinitionUpdated)) + .build(); + + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); + + final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, TYPE_KEY_1); + + assertThat(oldTypeAfter) + .hasValueSatisfying( + type -> + assertFieldDefinitionsAreEqual( + type.getFieldDefinitions(), singletonList(fieldDefinitionUpdated))); + } + + @Test + void sync_WithoutKey_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + null, TYPE_NAME_1, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2)) + .build(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); }) - .build(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - //test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture() - .join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo("TypeDraft with name: LocalizedString(en -> name_1) doesn't have a key. " - + "Please make sure all type drafts have keys.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> assertThat(throwable).isNull()); - - assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //preparation - final TypeDraft newTypeDraft = null; - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); + .build(); + + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .isEqualTo( + "TypeDraft with name: LocalizedString(en -> name_1) doesn't have a key. " + + "Please make sure all type drafts have keys.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying(throwable -> assertThat(throwable).isNull()); + + assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final TypeDraft newTypeDraft = null; + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); }) - .build(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - //test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture() - .join(); - - //assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo("TypeDraft is null.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> assertThat(throwable).isNull()); - - assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithoutName_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - // Draft without "name" throws a commercetools exception because "name" is a required value - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - TYPE_KEY_1, - null, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2)) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); + .build(); + + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).isEqualTo("TypeDraft is null.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying(throwable -> assertThat(throwable).isNull()); + + assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithoutName_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + // Draft without "name" throws a commercetools exception because "name" is a required value + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + TYPE_KEY_1, null, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(asList(FIELD_DEFINITION_1, FIELD_DEFINITION_2)) + .build(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); }) - .build(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - // test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture() - .join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to update type with key: 'key_1'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); - assertThat(throwable).hasMessageContaining("Missing required value"); + .build(); + + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to update type with key: 'key_1'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); + assertThat(throwable).hasMessageContaining("Missing required value"); }); - assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithoutFieldDefinitionType_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //preparation - final FieldDefinition fieldDefinition = FieldDefinition.of( - null, - FIELD_DEFINITION_NAME_1, - FIELD_DEFINITION_LABEL_1, - true, - TextInputHint.SINGLE_LINE); - - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - TYPE_KEY_1, - null, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(singletonList(fieldDefinition)) - .build(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); + assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithoutFieldDefinitionType_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final FieldDefinition fieldDefinition = + FieldDefinition.of( + null, + FIELD_DEFINITION_NAME_1, + FIELD_DEFINITION_LABEL_1, + true, + TextInputHint.SINGLE_LINE); + + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + TYPE_KEY_1, null, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(singletonList(fieldDefinition)) + .build(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); }) - .build(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - //test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture() - .join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to update type with key: 'key_1'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); - assertThat(throwable).hasMessageContaining("Missing required value"); + .build(); + + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to update type with key: 'key_1'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(ErrorResponseException.class); + assertThat(throwable).hasMessageContaining("Missing required value"); }); - assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithSeveralBatches_ShouldReturnProperStatistics() { - // preparation - // Default batch size is 50 (check TypeSyncOptionsBuilder) so we have 2 batches of 50 - final List typeDrafts = IntStream - .range(0, 100) - .mapToObj(i -> TypeDraftBuilder.of( - "key__" + Integer.toString(i), - LocalizedString.ofEnglish("name__" + Integer.toString(i)), - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(LocalizedString.ofEnglish("description__" + Integer.toString(i))) + assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithSeveralBatches_ShouldReturnProperStatistics() { + // preparation + // Default batch size is 50 (check TypeSyncOptionsBuilder) so we have 2 batches of 50 + final List typeDrafts = + IntStream.range(0, 100) + .mapToObj( + i -> + TypeDraftBuilder.of( + "key__" + Integer.toString(i), + LocalizedString.ofEnglish("name__" + Integer.toString(i)), + ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description( + LocalizedString.ofEnglish("description__" + Integer.toString(i))) .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) .build()) - .collect(Collectors.toList()); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .build(); - - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - //test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(typeDrafts) - .toCompletableFuture() - .join(); - - //assertion - assertThat(typeSyncStatistics) - .hasValues(100, 100, 0, 0); - } - - private static void assertFieldDefinitionsAreEqual(@Nonnull final List oldFields, - @Nonnull final List newFields) { - - IntStream.range(0, newFields.size()) - .forEach(index -> { - final FieldDefinition oldFieldDefinition = oldFields.get(index); - final FieldDefinition newFieldDefinition = newFields.get(index); - - assertThat(oldFieldDefinition.getName()).isEqualTo(newFieldDefinition.getName()); - assertThat(oldFieldDefinition.getLabel()).isEqualTo(newFieldDefinition.getLabel()); - assertThat(oldFieldDefinition.getType()).isEqualTo(newFieldDefinition.getType()); - //TODO: GITHUB ISSUE#339 no update action exists for changing the input hint - assertThat(oldFieldDefinition.isRequired()).isEqualTo(newFieldDefinition.isRequired()); - - if (oldFieldDefinition.getType().getClass() == EnumFieldType.class) { - assertPlainEnumsValuesAreEqual( - ((EnumFieldType) oldFieldDefinition.getType()).getValues(), - ((EnumFieldType) newFieldDefinition.getType()).getValues() - ); - } else if (oldFieldDefinition.getType().getClass() == LocalizedEnumFieldType.class) { - assertLocalizedEnumsValuesAreEqual( - ((LocalizedEnumFieldType) oldFieldDefinition.getType()).getValues(), - ((LocalizedEnumFieldType) newFieldDefinition.getType()).getValues() - ); - } - }); - } - - private static void assertPlainEnumsValuesAreEqual(@Nonnull final List enumValues, - @Nonnull final List enumValuesDrafts) { - - IntStream.range(0, enumValuesDrafts.size()) - .forEach(index -> { - final EnumValue enumValue = enumValues.get(index); - final EnumValue enumValueDraft = enumValuesDrafts.get(index); - - assertThat(enumValue.getKey()).isEqualTo(enumValueDraft.getKey()); - assertThat(enumValue.getLabel()).isEqualTo(enumValueDraft.getLabel()); - }); - } - - private static void assertLocalizedEnumsValuesAreEqual(@Nonnull final List enumValues, - @Nonnull final List enumValuesDrafts) { - - IntStream.range(0, enumValuesDrafts.size()) - .forEach(index -> { - final LocalizedEnumValue enumValue = enumValues.get(index); - final LocalizedEnumValue enumValueDraft = enumValuesDrafts.get(index); - - assertThat(enumValue.getKey()).isEqualTo(enumValueDraft.getKey()); - assertThat(enumValue.getLabel()).isEqualTo(enumValueDraft.getLabel()); - }); - } - - @Test - void sync_WithConcurrentModificationException_ShouldRetryToUpdateNewTypeWithSuccess() { - // Preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); - - final TypeDraft typeDraft = TypeDraftBuilder - .of(TYPE_KEY_2, TYPE_NAME_2, ResourceTypeIdsSetBuilder.of().addChannels()) + .collect(Collectors.toList()); + + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(typeDrafts).toCompletableFuture().join(); + + // assertion + assertThat(typeSyncStatistics).hasValues(100, 100, 0, 0); + } + + private static void assertFieldDefinitionsAreEqual( + @Nonnull final List oldFields, + @Nonnull final List newFields) { + + IntStream.range(0, newFields.size()) + .forEach( + index -> { + final FieldDefinition oldFieldDefinition = oldFields.get(index); + final FieldDefinition newFieldDefinition = newFields.get(index); + + assertThat(oldFieldDefinition.getName()).isEqualTo(newFieldDefinition.getName()); + assertThat(oldFieldDefinition.getLabel()).isEqualTo(newFieldDefinition.getLabel()); + assertThat(oldFieldDefinition.getType()).isEqualTo(newFieldDefinition.getType()); + // TODO: GITHUB ISSUE#339 no update action exists for changing the input hint + assertThat(oldFieldDefinition.isRequired()) + .isEqualTo(newFieldDefinition.isRequired()); + + if (oldFieldDefinition.getType().getClass() == EnumFieldType.class) { + assertPlainEnumsValuesAreEqual( + ((EnumFieldType) oldFieldDefinition.getType()).getValues(), + ((EnumFieldType) newFieldDefinition.getType()).getValues()); + } else if (oldFieldDefinition.getType().getClass() == LocalizedEnumFieldType.class) { + assertLocalizedEnumsValuesAreEqual( + ((LocalizedEnumFieldType) oldFieldDefinition.getType()).getValues(), + ((LocalizedEnumFieldType) newFieldDefinition.getType()).getValues()); + } + }); + } + + private static void assertPlainEnumsValuesAreEqual( + @Nonnull final List enumValues, @Nonnull final List enumValuesDrafts) { + + IntStream.range(0, enumValuesDrafts.size()) + .forEach( + index -> { + final EnumValue enumValue = enumValues.get(index); + final EnumValue enumValueDraft = enumValuesDrafts.get(index); + + assertThat(enumValue.getKey()).isEqualTo(enumValueDraft.getKey()); + assertThat(enumValue.getLabel()).isEqualTo(enumValueDraft.getLabel()); + }); + } + + private static void assertLocalizedEnumsValuesAreEqual( + @Nonnull final List enumValues, + @Nonnull final List enumValuesDrafts) { + + IntStream.range(0, enumValuesDrafts.size()) + .forEach( + index -> { + final LocalizedEnumValue enumValue = enumValues.get(index); + final LocalizedEnumValue enumValueDraft = enumValuesDrafts.get(index); + + assertThat(enumValue.getKey()).isEqualTo(enumValueDraft.getKey()); + assertThat(enumValue.getLabel()).isEqualTo(enumValueDraft.getLabel()); + }); + } + + @Test + void sync_WithConcurrentModificationException_ShouldRetryToUpdateNewTypeWithSuccess() { + // Preparation + final SphereClient spyClient = buildClientWithConcurrentModificationUpdate(); + + final TypeDraft typeDraft = + TypeDraftBuilder.of(TYPE_KEY_2, TYPE_NAME_2, ResourceTypeIdsSetBuilder.of().addChannels()) .build(); - CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(typeDraft)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(typeDraft)).toCompletableFuture().join(); - final TypeDraft updatedDraft = TypeDraftBuilder.of(typeDraft).name(TYPE_NAME_1).build(); + final TypeDraft updatedDraft = TypeDraftBuilder.of(typeDraft).name(TYPE_NAME_1).build(); - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(spyClient).build(); - final TypeSync typeSync = new TypeSync(typeSyncOptions); + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(spyClient).build(); + final TypeSync typeSync = new TypeSync(typeSyncOptions); - //test - final TypeSyncStatistics statistics = typeSync.sync(Collections.singletonList(updatedDraft)) - .toCompletableFuture() - .join(); + // test + final TypeSyncStatistics statistics = + typeSync.sync(Collections.singletonList(updatedDraft)).toCompletableFuture().join(); - // assertion - assertThat(statistics).hasValues(1, 0, 1, 0); + // assertion + assertThat(statistics).hasValues(1, 0, 1, 0); - // Assert CTP state. - final PagedQueryResult queryResult = - CTP_TARGET_CLIENT.execute(TypeQuery.of().plusPredicates(queryModel -> - queryModel.key().is(typeDraft.getKey()))) - .toCompletableFuture() - .join(); + // Assert CTP state. + final PagedQueryResult queryResult = + CTP_TARGET_CLIENT + .execute( + TypeQuery.of() + .plusPredicates(queryModel -> queryModel.key().is(typeDraft.getKey()))) + .toCompletableFuture() + .join(); - assertThat(queryResult.head()).hasValueSatisfying(type -> - assertThat(type.getName()).isEqualTo(TYPE_NAME_1)); - } + assertThat(queryResult.head()) + .hasValueSatisfying(type -> assertThat(type.getName()).isEqualTo(TYPE_NAME_1)); + } - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdate() { + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdate() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final TypeUpdateCommand anyTypeUpdate = any(TypeUpdateCommand.class); + final TypeUpdateCommand anyTypeUpdate = any(TypeUpdateCommand.class); - when(spyClient.execute(anyTypeUpdate)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); + when(spyClient.execute(anyTypeUpdate)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); - return spyClient; - } + return spyClient; + } - @Test - void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { - //preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); + @Test + void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry(); - final TypeDraft typeDraft = TypeDraftBuilder - .of("typeKey", LocalizedString.ofEnglish( "typeName"), ResourceTypeIdsSetBuilder.of().addChannels()) + final TypeDraft typeDraft = + TypeDraftBuilder.of( + "typeKey", + LocalizedString.ofEnglish("typeName"), + ResourceTypeIdsSetBuilder.of().addChannels()) .build(); - CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(typeDraft)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(typeDraft)).toCompletableFuture().join(); - final LocalizedString newTypeName = LocalizedString.ofEnglish( "typeName_updated"); - final TypeDraft updatedDraft = TypeDraftBuilder.of(typeDraft).name(newTypeName).build(); + final LocalizedString newTypeName = LocalizedString.ofEnglish("typeName_updated"); + final TypeDraft updatedDraft = TypeDraftBuilder.of(typeDraft).name(newTypeName).build(); - final List errorMessages = new ArrayList<>(); - final List errors = new ArrayList<>(); + final List errorMessages = new ArrayList<>(); + final List errors = new ArrayList<>(); - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errors.add(exception.getCause()); - }) + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errors.add(exception.getCause()); + }) .build(); - final TypeSync typeSync = new TypeSync(typeSyncOptions); - - //test - final TypeSyncStatistics statistics = typeSync.sync(Collections.singletonList(updatedDraft)) - .toCompletableFuture() - .join(); + final TypeSync typeSync = new TypeSync(typeSyncOptions); - //assertion - assertThat(statistics).hasValues(1, 0, 0, 1); + // test + final TypeSyncStatistics statistics = + typeSync.sync(Collections.singletonList(updatedDraft)).toCompletableFuture().join(); - assertThat(errorMessages).hasSize(1); - assertThat(errors).hasSize(1); + // assertion + assertThat(statistics).hasValues(1, 0, 0, 1); - assertThat(errors.get(0).getCause()).isExactlyInstanceOf(BadGatewayException.class); - assertThat(errorMessages.get(0)).contains( - format("Failed to update type with key: '%s'. Reason: Failed to fetch from CTP while retrying " - + "after concurrency modification.", typeDraft.getKey())); + assertThat(errorMessages).hasSize(1); + assertThat(errors).hasSize(1); - } + assertThat(errors.get(0).getCause()).isExactlyInstanceOf(BadGatewayException.class); + assertThat(errorMessages.get(0)) + .contains( + format( + "Failed to update type with key: '%s'. Reason: Failed to fetch from CTP while retrying " + + "after concurrency modification.", + typeDraft.getKey())); + } - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndFailedFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(TypeQuery.class))) - .thenCallRealMethod() // Call real fetch on fetching matching types - .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(TypeQuery.class))) + .thenCallRealMethod() // Call real fetch on fetching matching types + .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())); - final TypeUpdateCommand anyTypeUpdate = any(TypeUpdateCommand.class); + final TypeUpdateCommand anyTypeUpdate = any(TypeUpdateCommand.class); - when(spyClient.execute(anyTypeUpdate)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); + when(spyClient.execute(anyTypeUpdate)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); - return spyClient; - } + return spyClient; + } - @Test - void sync__WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { - //preparation - final SphereClient spyClient = buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); + @Test + void sync__WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { + // preparation + final SphereClient spyClient = + buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry(); - final TypeDraft typeDraft = TypeDraftBuilder - .of("typeKey", LocalizedString.ofEnglish( "typeName"), ResourceTypeIdsSetBuilder.of().addChannels()) + final TypeDraft typeDraft = + TypeDraftBuilder.of( + "typeKey", + LocalizedString.ofEnglish("typeName"), + ResourceTypeIdsSetBuilder.of().addChannels()) .build(); - CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(typeDraft)) - .toCompletableFuture() - .join(); + CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(typeDraft)).toCompletableFuture().join(); - final LocalizedString newTypeName = LocalizedString.ofEnglish( "typeName_updated"); - final TypeDraft updatedDraft = TypeDraftBuilder.of(typeDraft).name(newTypeName).build(); + final LocalizedString newTypeName = LocalizedString.ofEnglish("typeName_updated"); + final TypeDraft updatedDraft = TypeDraftBuilder.of(typeDraft).name(newTypeName).build(); - final List errorMessages = new ArrayList<>(); - final List errors = new ArrayList<>(); + final List errorMessages = new ArrayList<>(); + final List errors = new ArrayList<>(); - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errors.add(exception.getCause()); - }) + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errors.add(exception.getCause()); + }) .build(); - final TypeSync typeSync = new TypeSync(typeSyncOptions); - //test - final TypeSyncStatistics statistics = typeSync.sync(Collections.singletonList(updatedDraft)) - .toCompletableFuture() - .join(); + final TypeSync typeSync = new TypeSync(typeSyncOptions); + // test + final TypeSyncStatistics statistics = + typeSync.sync(Collections.singletonList(updatedDraft)).toCompletableFuture().join(); - // Assertion - assertThat(statistics).hasValues(1, 0, 0, 1); + // Assertion + assertThat(statistics).hasValues(1, 0, 0, 1); - assertThat(errorMessages).hasSize(1); - assertThat(errors).hasSize(1); - assertThat(errorMessages.get(0)).contains( - format("Failed to update type with key: '%s'. Reason: Not found when attempting to fetch while " - + "retrying after concurrency modification.", typeDraft.getKey())); - } + assertThat(errorMessages).hasSize(1); + assertThat(errors).hasSize(1); + assertThat(errorMessages.get(0)) + .contains( + format( + "Failed to update type with key: '%s'. Reason: Not found when attempting to fetch while " + + "retrying after concurrency modification.", + typeDraft.getKey())); + } - @Nonnull - private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { + @Nonnull + private SphereClient buildClientWithConcurrentModificationUpdateAndNotFoundFetchOnRetry() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final TypeQuery anyTypeQuery = any(TypeQuery.class); + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final TypeQuery anyTypeQuery = any(TypeQuery.class); - when(spyClient.execute(anyTypeQuery)) - .thenCallRealMethod() // Call real fetch on fetching matching types - .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); + when(spyClient.execute(anyTypeQuery)) + .thenCallRealMethod() // Call real fetch on fetching matching types + .thenReturn(CompletableFuture.completedFuture(PagedQueryResult.empty())); - final TypeUpdateCommand anyTypeUpdate = any(TypeUpdateCommand.class); + final TypeUpdateCommand anyTypeUpdate = any(TypeUpdateCommand.class); - when(spyClient.execute(anyTypeUpdate)) - .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) - .thenCallRealMethod(); + when(spyClient.execute(anyTypeUpdate)) + .thenReturn(exceptionallyCompletedFuture(new ConcurrentModificationException())) + .thenCallRealMethod(); - return spyClient; - } + return spyClient; + } - @Test - void sync_WithSetOfEnumsAndSetOfLenumsChanges_ShouldUpdateTypeAddingFieldDefinition() { - //preparation - final FieldDefinition withSetOfEnumsOld = FieldDefinition.of( + @Test + void sync_WithSetOfEnumsAndSetOfLenumsChanges_ShouldUpdateTypeAddingFieldDefinition() { + // preparation + final FieldDefinition withSetOfEnumsOld = + FieldDefinition.of( SetFieldType.of( - EnumFieldType.of( - asList( - EnumValue.of("b", "b"), - EnumValue.of("a", "a") - - ))), + EnumFieldType.of(asList(EnumValue.of("b", "b"), EnumValue.of("a", "a")))), "foo", ofEnglish("foo"), false); - final FieldDefinition withSetOfSetOfLEnumsOld = FieldDefinition.of( + final FieldDefinition withSetOfSetOfLEnumsOld = + FieldDefinition.of( SetFieldType.of( LocalizedEnumFieldType.of( asList( LocalizedEnumValue.of("b", ofEnglish("b")), - LocalizedEnumValue.of("a", ofEnglish("a")) - ))), + LocalizedEnumValue.of("a", ofEnglish("a"))))), "bar", ofEnglish("bar"), false); - final TypeDraft oldTypeDraft = TypeDraftBuilder.of("withSetOfEnums", ofEnglish("withSetOfEnums"), - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .fieldDefinitions( - asList(withSetOfEnumsOld, withSetOfSetOfLEnumsOld)) - .build(); + final TypeDraft oldTypeDraft = + TypeDraftBuilder.of( + "withSetOfEnums", + ofEnglish("withSetOfEnums"), + ResourceTypeIdsSetBuilder.of().addCategories().build()) + .fieldDefinitions(asList(withSetOfEnumsOld, withSetOfSetOfLEnumsOld)) + .build(); - CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(oldTypeDraft)).toCompletableFuture().join(); + CTP_TARGET_CLIENT.execute(TypeCreateCommand.of(oldTypeDraft)).toCompletableFuture().join(); - final FieldDefinition withSetOfEnumsNew = FieldDefinition.of( + final FieldDefinition withSetOfEnumsNew = + FieldDefinition.of( SetFieldType.of( EnumFieldType.of( asList( - EnumValue.of("a", "a"), - EnumValue.of("b", "b"), - EnumValue.of("c", "c") - ))), + EnumValue.of("a", "a"), EnumValue.of("b", "b"), EnumValue.of("c", "c")))), "foo", ofEnglish("foo"), false); - final FieldDefinition withSetOfSetOfLEnumsNew = FieldDefinition.of( - SetFieldType.of(LocalizedEnumFieldType.of( - asList( - LocalizedEnumValue.of("a", ofEnglish("a")), - LocalizedEnumValue.of("b", ofEnglish("b")), - LocalizedEnumValue.of("c", ofEnglish("c")) - ) - )), "bar", ofEnglish("bar"), false); - - final TypeDraft newTypeDraft = TypeDraftBuilder.of("withSetOfEnums", ofEnglish("withSetOfEnums"), - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .fieldDefinitions( - asList(withSetOfEnumsNew, withSetOfSetOfLEnumsNew)) - .build(); - + final FieldDefinition withSetOfSetOfLEnumsNew = + FieldDefinition.of( + SetFieldType.of( + LocalizedEnumFieldType.of( + asList( + LocalizedEnumValue.of("a", ofEnglish("a")), + LocalizedEnumValue.of("b", ofEnglish("b")), + LocalizedEnumValue.of("c", ofEnglish("c"))))), + "bar", + ofEnglish("bar"), + false); - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + "withSetOfEnums", + ofEnglish("withSetOfEnums"), + ResourceTypeIdsSetBuilder.of().addCategories().build()) + .fieldDefinitions(asList(withSetOfEnumsNew, withSetOfSetOfLEnumsNew)) .build(); - final TypeSync typeSync = new TypeSync(typeSyncOptions); + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - //test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture() - .join(); + final TypeSync typeSync = new TypeSync(typeSyncOptions); + + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); - //assertions - assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); + // assertions + assertThat(typeSyncStatistics).hasValues(1, 0, 1, 0); - final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, "withSetOfEnums"); + final Optional oldTypeAfter = getTypeByKey(CTP_TARGET_CLIENT, "withSetOfEnums"); - assertThat(oldTypeAfter).hasValueSatisfying(type -> - assertFieldDefinitionsAreEqual(type.getFieldDefinitions(), - asList(withSetOfEnumsNew, withSetOfSetOfLEnumsNew))); - } + assertThat(oldTypeAfter) + .hasValueSatisfying( + type -> + assertFieldDefinitionsAreEqual( + type.getFieldDefinitions(), + asList(withSetOfEnumsNew, withSetOfSetOfLEnumsNew))); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/inventories/InventorySyncIT.java b/src/integration-test/java/com/commercetools/sync/integration/inventories/InventorySyncIT.java index 0d52caf715..fd71c2f8bb 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/inventories/InventorySyncIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/inventories/InventorySyncIT.java @@ -1,5 +1,30 @@ package com.commercetools.sync.integration.inventories; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.integration.commons.utils.ChannelITUtils.deleteChannelsFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.EXPECTED_DELIVERY_1; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.EXPECTED_DELIVERY_2; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.QUANTITY_ON_STOCK_1; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.QUANTITY_ON_STOCK_2; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.RESTOCKABLE_IN_DAYS_1; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.RESTOCKABLE_IN_DAYS_2; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.SKU_1; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.SKU_2; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.SUPPLY_CHANNEL_KEY_1; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.SUPPLY_CHANNEL_KEY_2; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.deleteInventoryEntriesFromTargetAndSource; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.getChannelByKey; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.getInventoryEntryBySkuAndSupplyChannel; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.populateSourceProject; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.populateTargetProject; +import static com.commercetools.sync.inventories.utils.InventoryReferenceResolutionUtils.mapToInventoryEntryDrafts; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.inventories.InventorySync; @@ -18,13 +43,6 @@ import io.sphere.sdk.inventory.queries.InventoryEntryQuery; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; @@ -32,388 +50,466 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; import java.util.stream.LongStream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.integration.commons.utils.ChannelITUtils.deleteChannelsFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.EXPECTED_DELIVERY_1; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.EXPECTED_DELIVERY_2; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.QUANTITY_ON_STOCK_1; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.QUANTITY_ON_STOCK_2; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.RESTOCKABLE_IN_DAYS_1; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.RESTOCKABLE_IN_DAYS_2; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.SKU_1; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.SKU_2; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.SUPPLY_CHANNEL_KEY_1; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.SUPPLY_CHANNEL_KEY_2; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.deleteInventoryEntriesFromTargetAndSource; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.getChannelByKey; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.getInventoryEntryBySkuAndSupplyChannel; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.populateSourceProject; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.populateTargetProject; -import static com.commercetools.sync.inventories.utils.InventoryReferenceResolutionUtils.mapToInventoryEntryDrafts; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Contains integration tests of inventory sync. - */ +/** Contains integration tests of inventory sync. */ class InventorySyncIT { - /** - * Deletes inventories and supply channels from source and target CTP projects. - * Populates source and target CTP projects with test data. + /** + * Deletes inventories and supply channels from source and target CTP projects. Populates source + * and target CTP projects with test data. + */ + @BeforeEach + void setup() { + deleteInventoryEntriesFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + deleteChannelsFromTargetAndSource(); + populateSourceProject(); + populateTargetProject(); + } + + /** + * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} + * projects that were set up in this test class. + */ + @AfterAll + static void tearDown() { + deleteInventoryEntriesFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + deleteChannelsFromTargetAndSource(); + } + + @Test + void sync_WithUpdatedInventory_ShouldUpdateInventory() { + // Ensure that old entry has correct values before sync. + final Optional oldInventoryBeforeSync = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); + assertThat(oldInventoryBeforeSync).isNotEmpty(); + assertValues( + oldInventoryBeforeSync.get(), + QUANTITY_ON_STOCK_1, + EXPECTED_DELIVERY_1, + RESTOCKABLE_IN_DAYS_1); + + // Prepare sync data. + final InventoryEntryDraft newInventoryDraft = + InventoryEntryDraftBuilder.of( + SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, null) + .build(); + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + + // Sync and ensure that proper statistics were returned. + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(singletonList(newInventoryDraft)).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(1, 0, 1, 0); + + // Ensure that old entry has correct values after sync. + final Optional oldInventoryAfterSync = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); + assertThat(oldInventoryAfterSync).isNotEmpty(); + assertValues( + oldInventoryAfterSync.get(), + QUANTITY_ON_STOCK_2, + EXPECTED_DELIVERY_2, + RESTOCKABLE_IN_DAYS_2); + } + + @Test + void sync_WithNewInventory_ShouldCreateInventory() { + // Ensure that old entry has correct values before sync. + final Optional oldInventoryBeforeSync = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_2, null); + assertThat(oldInventoryBeforeSync).isEmpty(); + + // Prepare sync data. + final InventoryEntryDraft newInventoryDraft = + InventoryEntryDraftBuilder.of( + SKU_2, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, null) + .build(); + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + + // Sync and ensure that proper statistics were returned. + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(singletonList(newInventoryDraft)).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(1, 1, 0, 0); + + // Ensure that old entry has correct values after sync. + final Optional oldInventoryAfterSync = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_2, null); + assertThat(oldInventoryAfterSync).isNotEmpty(); + assertValues( + oldInventoryAfterSync.get(), + QUANTITY_ON_STOCK_2, + EXPECTED_DELIVERY_2, + RESTOCKABLE_IN_DAYS_2); + } + + @Test + void sync_WithKeyToExistingSupplyChannelInPlaceOfId_ShouldUpdateInventory() { + /* + * Fetch existing Channel of key SUPPLY_CHANNEL_KEY_1 from target project. + * This is done only for test assertion reasons, not necessary for sync. */ - @BeforeEach - void setup() { - deleteInventoryEntriesFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - deleteChannelsFromTargetAndSource(); - populateSourceProject(); - populateTargetProject(); - } - - /** - * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} projects that - * were set up in this test class. + final Optional supplyChannel = + getChannelByKey(CTP_TARGET_CLIENT, SUPPLY_CHANNEL_KEY_1); + assertThat(supplyChannel).isNotEmpty(); + + /* + * Prepare InventoryEntryDraft of sku SKU_1 and reference to supply channel of key SUPPLY_CHANNEL_KEY_1. */ - @AfterAll - static void tearDown() { - deleteInventoryEntriesFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - deleteChannelsFromTargetAndSource(); - } - - @Test - void sync_WithUpdatedInventory_ShouldUpdateInventory() { - //Ensure that old entry has correct values before sync. - final Optional oldInventoryBeforeSync = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); - assertThat(oldInventoryBeforeSync).isNotEmpty(); - assertValues(oldInventoryBeforeSync.get(), QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1); - - //Prepare sync data. - final InventoryEntryDraft newInventoryDraft = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, null).build(); - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - - //Sync and ensure that proper statistics were returned. - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(singletonList(newInventoryDraft)) - .toCompletableFuture().join(); - assertThat(inventorySyncStatistics).hasValues(1, 0, 1, 0); - - //Ensure that old entry has correct values after sync. - final Optional oldInventoryAfterSync = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); - assertThat(oldInventoryAfterSync).isNotEmpty(); - assertValues(oldInventoryAfterSync.get(), QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2); - } - - @Test - void sync_WithNewInventory_ShouldCreateInventory() { - //Ensure that old entry has correct values before sync. - final Optional oldInventoryBeforeSync = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_2, null); - assertThat(oldInventoryBeforeSync).isEmpty(); - - //Prepare sync data. - final InventoryEntryDraft newInventoryDraft = InventoryEntryDraftBuilder - .of(SKU_2, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, null).build(); - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - - //Sync and ensure that proper statistics were returned. - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(singletonList(newInventoryDraft)) - .toCompletableFuture().join(); - assertThat(inventorySyncStatistics).hasValues(1, 1, 0, 0); - - //Ensure that old entry has correct values after sync. - final Optional oldInventoryAfterSync = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_2, null); - assertThat(oldInventoryAfterSync).isNotEmpty(); - assertValues(oldInventoryAfterSync.get(), QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2); - } - - @Test - void sync_WithKeyToExistingSupplyChannelInPlaceOfId_ShouldUpdateInventory() { - /* - * Fetch existing Channel of key SUPPLY_CHANNEL_KEY_1 from target project. - * This is done only for test assertion reasons, not necessary for sync. - */ - final Optional supplyChannel = getChannelByKey(CTP_TARGET_CLIENT, SUPPLY_CHANNEL_KEY_1); - assertThat(supplyChannel).isNotEmpty(); - - /* - * Prepare InventoryEntryDraft of sku SKU_1 and reference to supply channel of key SUPPLY_CHANNEL_KEY_1. - */ - final ResourceIdentifier supplyChannelReference = ResourceIdentifier.ofKey(SUPPLY_CHANNEL_KEY_1); - - final InventoryEntryDraft newInventoryDraft = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, supplyChannelReference).build(); - - //Ensure old entry values before sync. - final Optional oldInventoryBeforeSync = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, supplyChannel.get().toReference()); - assertThat(oldInventoryBeforeSync).isPresent(); - assertValues(oldInventoryBeforeSync.get(), QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1); - - //Prepare sync options and perform sync of draft to target project. - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(singletonList(newInventoryDraft)) - .toCompletableFuture().join(); - assertThat(inventorySyncStatistics).hasValues(1, 0, 1, 0); - - - //Ensure old entry values after sync. - final Optional oldInventoryAfterSync = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, supplyChannel.get().toReference()); - assertThat(oldInventoryAfterSync).isPresent(); - assertValues(oldInventoryAfterSync.get(), QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2); - } - - @Test - void sync_WithNewSupplyChannelAndChannelsEnsured_ShouldCreateNewSupplyChannel() { - //Ensure that supply channel doesn't exist before sync. - final Optional oldSupplyChannelBeforeSync = getChannelByKey(CTP_TARGET_CLIENT, SUPPLY_CHANNEL_KEY_2); - assertThat(oldSupplyChannelBeforeSync).isEmpty(); - - //Prepare sync data. - final ResourceIdentifier newSupplyChannelReference = ResourceIdentifier.ofKey(SUPPLY_CHANNEL_KEY_2); - final InventoryEntryDraft newInventoryDraft = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, newSupplyChannelReference) + final ResourceIdentifier supplyChannelReference = + ResourceIdentifier.ofKey(SUPPLY_CHANNEL_KEY_1); + + final InventoryEntryDraft newInventoryDraft = + InventoryEntryDraftBuilder.of( + SKU_1, + QUANTITY_ON_STOCK_2, + EXPECTED_DELIVERY_2, + RESTOCKABLE_IN_DAYS_2, + supplyChannelReference) + .build(); + + // Ensure old entry values before sync. + final Optional oldInventoryBeforeSync = + getInventoryEntryBySkuAndSupplyChannel( + CTP_TARGET_CLIENT, SKU_1, supplyChannel.get().toReference()); + assertThat(oldInventoryBeforeSync).isPresent(); + assertValues( + oldInventoryBeforeSync.get(), + QUANTITY_ON_STOCK_1, + EXPECTED_DELIVERY_1, + RESTOCKABLE_IN_DAYS_1); + + // Prepare sync options and perform sync of draft to target project. + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(singletonList(newInventoryDraft)).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(1, 0, 1, 0); + + // Ensure old entry values after sync. + final Optional oldInventoryAfterSync = + getInventoryEntryBySkuAndSupplyChannel( + CTP_TARGET_CLIENT, SKU_1, supplyChannel.get().toReference()); + assertThat(oldInventoryAfterSync).isPresent(); + assertValues( + oldInventoryAfterSync.get(), + QUANTITY_ON_STOCK_2, + EXPECTED_DELIVERY_2, + RESTOCKABLE_IN_DAYS_2); + } + + @Test + void sync_WithNewSupplyChannelAndChannelsEnsured_ShouldCreateNewSupplyChannel() { + // Ensure that supply channel doesn't exist before sync. + final Optional oldSupplyChannelBeforeSync = + getChannelByKey(CTP_TARGET_CLIENT, SUPPLY_CHANNEL_KEY_2); + assertThat(oldSupplyChannelBeforeSync).isEmpty(); + + // Prepare sync data. + final ResourceIdentifier newSupplyChannelReference = + ResourceIdentifier.ofKey(SUPPLY_CHANNEL_KEY_2); + final InventoryEntryDraft newInventoryDraft = + InventoryEntryDraftBuilder.of( + SKU_1, + QUANTITY_ON_STOCK_2, + EXPECTED_DELIVERY_2, + RESTOCKABLE_IN_DAYS_2, + newSupplyChannelReference) + .build(); + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).ensureChannels(true).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + + // Sync and ensure that proper statistics were returned. + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(singletonList(newInventoryDraft)).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(1, 1, 0, 0); + + // Ensure that supply channel exists before sync. + final Optional oldSupplyChannelAfterSync = + getChannelByKey(CTP_TARGET_CLIENT, SUPPLY_CHANNEL_KEY_2); + assertThat(oldSupplyChannelAfterSync).isNotEmpty(); + assertThat(oldSupplyChannelAfterSync.get().getKey()).isEqualTo(SUPPLY_CHANNEL_KEY_2); + } + + @Test + void sync_WithReferencesToSourceChannels_ShouldUpdateInventoriesWithoutChannelCreation() { + // Ensure channels in target project before sync + final ChannelQuery targetChannelsQuery = + ChannelQuery.of() + .withPredicates( + channelQueryModel -> + channelQueryModel + .roles() + .containsAny(singletonList(ChannelRole.INVENTORY_SUPPLY))); + final List targetChannelsBeforeSync = + CTP_TARGET_CLIENT.execute(targetChannelsQuery).toCompletableFuture().join().getResults(); + assertThat(targetChannelsBeforeSync).hasSize(1); + assertThat(targetChannelsBeforeSync.get(0).getKey()).isEqualTo(SUPPLY_CHANNEL_KEY_1); + + // Prepare InventoryEntryDraft of sku SKU_1 and reference to above supply channel key. + final InventoryEntryDraft newInventoryDraft = + InventoryEntryDraftBuilder.of( + SKU_1, + QUANTITY_ON_STOCK_2, + EXPECTED_DELIVERY_2, + RESTOCKABLE_IN_DAYS_2, + ResourceIdentifier.ofKey(SUPPLY_CHANNEL_KEY_1)) .build(); - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .ensureChannels(true).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - - //Sync and ensure that proper statistics were returned. - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(singletonList(newInventoryDraft)) - .toCompletableFuture().join(); - assertThat(inventorySyncStatistics).hasValues(1, 1, 0, 0); - - //Ensure that supply channel exists before sync. - final Optional oldSupplyChannelAfterSync = getChannelByKey(CTP_TARGET_CLIENT, SUPPLY_CHANNEL_KEY_2); - assertThat(oldSupplyChannelAfterSync).isNotEmpty(); - assertThat(oldSupplyChannelAfterSync.get().getKey()).isEqualTo(SUPPLY_CHANNEL_KEY_2); - } - - @Test - void sync_WithReferencesToSourceChannels_ShouldUpdateInventoriesWithoutChannelCreation() { - //Ensure channels in target project before sync - final ChannelQuery targetChannelsQuery = ChannelQuery.of().withPredicates(channelQueryModel -> - channelQueryModel.roles().containsAny(singletonList(ChannelRole.INVENTORY_SUPPLY))); - final List targetChannelsBeforeSync = CTP_TARGET_CLIENT.execute(targetChannelsQuery) - .toCompletableFuture().join().getResults(); - assertThat(targetChannelsBeforeSync).hasSize(1); - assertThat(targetChannelsBeforeSync.get(0).getKey()).isEqualTo(SUPPLY_CHANNEL_KEY_1); - - //Prepare InventoryEntryDraft of sku SKU_1 and reference to above supply channel key. - final InventoryEntryDraft newInventoryDraft = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, - ResourceIdentifier.ofKey(SUPPLY_CHANNEL_KEY_1)).build(); - - //Fetch existing Channel of key SUPPLY_CHANNEL_KEY_1 from target project. - final Optional targetSupplyChannel = getChannelByKey(CTP_TARGET_CLIENT, SUPPLY_CHANNEL_KEY_1); - assertThat(targetSupplyChannel).isNotEmpty(); - final Reference targetChannelReference = targetSupplyChannel.get().toReference(); - - //Ensure old entry values before sync. - final Optional oldInventoryBeforeSync = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, targetChannelReference); - assertThat(oldInventoryBeforeSync).isPresent(); - assertValues(oldInventoryBeforeSync.get(), QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1); - assertThat(oldInventoryBeforeSync.get().getSupplyChannel().getId()).isEqualTo(targetChannelReference.getId()); - - //Prepare sync options and perform sync of draft to target project. - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(singletonList(newInventoryDraft)) - .toCompletableFuture().join(); - assertThat(inventorySyncStatistics).hasValues(1, 0, 1, 0); - - //Ensure old entry values after sync. - final Optional oldInventoryAfterSync = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, targetChannelReference); - assertThat(oldInventoryAfterSync).isPresent(); - assertValues(oldInventoryAfterSync.get(), QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2); - assertThat(oldInventoryAfterSync.get().getSupplyChannel().getId()).isEqualTo(targetChannelReference.getId()); - - //Ensure channels in target project after sync. - final List targetChannelsAfterSync = CTP_TARGET_CLIENT.execute(targetChannelsQuery) - .toCompletableFuture().join().getResults(); - assertThat(targetChannelsAfterSync).isNotEmpty(); - assertThat(targetChannelsAfterSync).hasSize(1); - assertThat(targetChannelsAfterSync.get(0)).isEqualTo(targetChannelsBeforeSync.get(0)); - } - - @Test - void sync_FromSourceToTargetProjectWithChannelsEnsured_ShouldReturnProperStatistics() { - //Fetch new inventories from source project. Convert them to drafts. - final List inventoryEntries = CTP_SOURCE_CLIENT.execute( - InventoryEntryQuery.of() - .withExpansionPaths(InventoryEntryExpansionModel::supplyChannel) - .plusExpansionPaths(ExpansionPath.of("custom.type"))) - .toCompletableFuture() - .join() - .getResults(); - - final List newInventories = mapToInventoryEntryDrafts(inventoryEntries); - - //Prepare sync options and perform sync of draft to target project. - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .ensureChannels(true) - .build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(newInventories) - .toCompletableFuture() - .join(); - assertThat(inventorySyncStatistics).hasValues(3, 1, 1, 0); - } - - @Test - void sync_FromSourceToTargetWithoutChannelsEnsured_ShouldReturnProperStatistics() { - //Fetch new inventories from source project. Convert them to drafts. - final List inventoryEntries = CTP_SOURCE_CLIENT - .execute(InventoryEntryQuery.of() - .withExpansionPaths(InventoryEntryExpansionModel::supplyChannel) - .plusExpansionPaths(ExpansionPath.of("custom.type"))) - .toCompletableFuture().join().getResults(); - - final List newInventories = mapToInventoryEntryDrafts(inventoryEntries); - - //Prepare sync options and perform sync of draft to target project. - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .ensureChannels(false).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(newInventories).toCompletableFuture() - .join(); - assertThat(inventorySyncStatistics).hasValues(3, 0, 1, 1); - } - - @Disabled - @Test - void sync_WithBatchProcessing_ShouldCreateAllGivenInventories() { - //Ensure inventory entries amount in target project before sync. - final List oldEntriesBeforeSync = CTP_TARGET_CLIENT.execute(InventoryEntryQuery.of()) - .toCompletableFuture().join().getResults(); - assertThat(oldEntriesBeforeSync).hasSize(2); - - //Create 10 drafts of new inventories. - final List newInventories = LongStream.range(0, 10) - .mapToObj(l -> InventoryEntryDraftBuilder.of(String.valueOf(l), l).build()).collect(toList()); - - //Set batch size of number less that new inventories size. - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .batchSize(3).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - - //Perform sync and ensure its results. - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(newInventories).toCompletableFuture() - .join(); - assertThat(inventorySyncStatistics).hasValues(10, 10, 0, 0); - - //Ensure inventory entries amount in target project after sync. - final List oldEntriesAfterSync = CTP_TARGET_CLIENT.execute(InventoryEntryQuery.of()) - .toCompletableFuture().join().getResults(); - assertThat(oldEntriesAfterSync).hasSize(12); - } - - @Test - void sync_ShouldReturnProperStatisticsObject() { - //Fetch new inventories from source project. Convert them to drafts. - final List inventoryEntries = CTP_SOURCE_CLIENT - .execute(InventoryEntryQuery.of() - .withExpansionPaths(InventoryEntryExpansionModel::supplyChannel) - .plusExpansionPaths(ExpansionPath.of("custom.type"))) - .toCompletableFuture().join().getResults(); - - final List newInventories = mapToInventoryEntryDrafts(inventoryEntries); - - //Prepare sync options and perform sync of draft to target project. - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .ensureChannels(true).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(newInventories).toCompletableFuture() - .join(); - assertThat(inventorySyncStatistics).hasValues(3, 1, 1, 0); - assertThat(inventorySyncStatistics.getLatestBatchProcessingTimeInMillis()).isGreaterThan(0L); - } - - @Test - void sync_WithCustomErrorCallback_ShouldExecuteCallbackOnError() { - //Fetch new inventories from source project. Convert them to drafts. - final List inventoryEntries = CTP_SOURCE_CLIENT - .execute(InventoryEntryQuery.of() - .withExpansionPaths(InventoryEntryExpansionModel::supplyChannel) - .plusExpansionPaths(ExpansionPath.of("custom.type"))) - .toCompletableFuture().join().getResults(); - - final List newInventories = mapToInventoryEntryDrafts(inventoryEntries); - - //Prepare sync options and perform sync of draft to target project. - final AtomicInteger invocationCounter = new AtomicInteger(0); - QuadConsumer, Optional, - List>> countingErrorCallback = - (exception, newResource, oldResource, updateActions) -> invocationCounter.incrementAndGet(); - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) + + // Fetch existing Channel of key SUPPLY_CHANNEL_KEY_1 from target project. + final Optional targetSupplyChannel = + getChannelByKey(CTP_TARGET_CLIENT, SUPPLY_CHANNEL_KEY_1); + assertThat(targetSupplyChannel).isNotEmpty(); + final Reference targetChannelReference = targetSupplyChannel.get().toReference(); + + // Ensure old entry values before sync. + final Optional oldInventoryBeforeSync = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, targetChannelReference); + assertThat(oldInventoryBeforeSync).isPresent(); + assertValues( + oldInventoryBeforeSync.get(), + QUANTITY_ON_STOCK_1, + EXPECTED_DELIVERY_1, + RESTOCKABLE_IN_DAYS_1); + assertThat(oldInventoryBeforeSync.get().getSupplyChannel().getId()) + .isEqualTo(targetChannelReference.getId()); + + // Prepare sync options and perform sync of draft to target project. + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(singletonList(newInventoryDraft)).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(1, 0, 1, 0); + + // Ensure old entry values after sync. + final Optional oldInventoryAfterSync = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, targetChannelReference); + assertThat(oldInventoryAfterSync).isPresent(); + assertValues( + oldInventoryAfterSync.get(), + QUANTITY_ON_STOCK_2, + EXPECTED_DELIVERY_2, + RESTOCKABLE_IN_DAYS_2); + assertThat(oldInventoryAfterSync.get().getSupplyChannel().getId()) + .isEqualTo(targetChannelReference.getId()); + + // Ensure channels in target project after sync. + final List targetChannelsAfterSync = + CTP_TARGET_CLIENT.execute(targetChannelsQuery).toCompletableFuture().join().getResults(); + assertThat(targetChannelsAfterSync).isNotEmpty(); + assertThat(targetChannelsAfterSync).hasSize(1); + assertThat(targetChannelsAfterSync.get(0)).isEqualTo(targetChannelsBeforeSync.get(0)); + } + + @Test + void sync_FromSourceToTargetProjectWithChannelsEnsured_ShouldReturnProperStatistics() { + // Fetch new inventories from source project. Convert them to drafts. + final List inventoryEntries = + CTP_SOURCE_CLIENT + .execute( + InventoryEntryQuery.of() + .withExpansionPaths(InventoryEntryExpansionModel::supplyChannel) + .plusExpansionPaths(ExpansionPath.of("custom.type"))) + .toCompletableFuture() + .join() + .getResults(); + + final List newInventories = mapToInventoryEntryDrafts(inventoryEntries); + + // Prepare sync options and perform sync of draft to target project. + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).ensureChannels(true).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(newInventories).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(3, 1, 1, 0); + } + + @Test + void sync_FromSourceToTargetWithoutChannelsEnsured_ShouldReturnProperStatistics() { + // Fetch new inventories from source project. Convert them to drafts. + final List inventoryEntries = + CTP_SOURCE_CLIENT + .execute( + InventoryEntryQuery.of() + .withExpansionPaths(InventoryEntryExpansionModel::supplyChannel) + .plusExpansionPaths(ExpansionPath.of("custom.type"))) + .toCompletableFuture() + .join() + .getResults(); + + final List newInventories = mapToInventoryEntryDrafts(inventoryEntries); + + // Prepare sync options and perform sync of draft to target project. + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).ensureChannels(false).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(newInventories).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(3, 0, 1, 1); + } + + @Disabled + @Test + void sync_WithBatchProcessing_ShouldCreateAllGivenInventories() { + // Ensure inventory entries amount in target project before sync. + final List oldEntriesBeforeSync = + CTP_TARGET_CLIENT + .execute(InventoryEntryQuery.of()) + .toCompletableFuture() + .join() + .getResults(); + assertThat(oldEntriesBeforeSync).hasSize(2); + + // Create 10 drafts of new inventories. + final List newInventories = + LongStream.range(0, 10) + .mapToObj(l -> InventoryEntryDraftBuilder.of(String.valueOf(l), l).build()) + .collect(toList()); + + // Set batch size of number less that new inventories size. + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).batchSize(3).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + + // Perform sync and ensure its results. + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(newInventories).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(10, 10, 0, 0); + + // Ensure inventory entries amount in target project after sync. + final List oldEntriesAfterSync = + CTP_TARGET_CLIENT + .execute(InventoryEntryQuery.of()) + .toCompletableFuture() + .join() + .getResults(); + assertThat(oldEntriesAfterSync).hasSize(12); + } + + @Test + void sync_ShouldReturnProperStatisticsObject() { + // Fetch new inventories from source project. Convert them to drafts. + final List inventoryEntries = + CTP_SOURCE_CLIENT + .execute( + InventoryEntryQuery.of() + .withExpansionPaths(InventoryEntryExpansionModel::supplyChannel) + .plusExpansionPaths(ExpansionPath.of("custom.type"))) + .toCompletableFuture() + .join() + .getResults(); + + final List newInventories = mapToInventoryEntryDrafts(inventoryEntries); + + // Prepare sync options and perform sync of draft to target project. + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).ensureChannels(true).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(newInventories).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(3, 1, 1, 0); + assertThat(inventorySyncStatistics.getLatestBatchProcessingTimeInMillis()).isGreaterThan(0L); + } + + @Test + void sync_WithCustomErrorCallback_ShouldExecuteCallbackOnError() { + // Fetch new inventories from source project. Convert them to drafts. + final List inventoryEntries = + CTP_SOURCE_CLIENT + .execute( + InventoryEntryQuery.of() + .withExpansionPaths(InventoryEntryExpansionModel::supplyChannel) + .plusExpansionPaths(ExpansionPath.of("custom.type"))) + .toCompletableFuture() + .join() + .getResults(); + + final List newInventories = mapToInventoryEntryDrafts(inventoryEntries); + + // Prepare sync options and perform sync of draft to target project. + final AtomicInteger invocationCounter = new AtomicInteger(0); + QuadConsumer< + SyncException, + Optional, + Optional, + List>> + countingErrorCallback = + (exception, newResource, oldResource, updateActions) -> + invocationCounter.incrementAndGet(); + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) .errorCallback(countingErrorCallback) - .ensureChannels(false).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - final InventorySyncStatistics inventorySyncStatistics = inventorySync.sync(newInventories).toCompletableFuture() - .join(); - assertThat(inventorySyncStatistics).hasValues(3, 0, 1, 1); - assertThat(invocationCounter.get()).isEqualTo(1); - } - - @Test - void sync_WithSyncDifferentBatchesConcurrently_ShouldReturnProperStatistics() { - //Prepare new inventories. - final List newDrafts = IntStream.range(100,160) - .mapToObj(i -> InventoryEntryDraftBuilder.of(String.valueOf(i), QUANTITY_ON_STOCK_1).build()) + .ensureChannels(false) + .build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + final InventorySyncStatistics inventorySyncStatistics = + inventorySync.sync(newInventories).toCompletableFuture().join(); + assertThat(inventorySyncStatistics).hasValues(3, 0, 1, 1); + assertThat(invocationCounter.get()).isEqualTo(1); + } + + @Test + void sync_WithSyncDifferentBatchesConcurrently_ShouldReturnProperStatistics() { + // Prepare new inventories. + final List newDrafts = + IntStream.range(100, 160) + .mapToObj( + i -> InventoryEntryDraftBuilder.of(String.valueOf(i), QUANTITY_ON_STOCK_1).build()) .collect(toList()); - //Split them to batches. - final List firstBatch = newDrafts.subList(0,20); - final List secondBatch = newDrafts.subList(20,40); - final List thirdBatch = newDrafts.subList(40,60); - - //Initialize sync. - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final InventorySync inventorySync = new InventorySync(inventorySyncOptions); - - //Run batch syncing concurrently. - final CompletableFuture firstResult = inventorySync.sync(firstBatch) - .toCompletableFuture(); - final CompletableFuture secondResult = inventorySync.sync(secondBatch) - .toCompletableFuture(); - final CompletableFuture thirdResult = inventorySync.sync(thirdBatch) - .toCompletableFuture(); - - CompletableFuture.allOf(firstResult, secondResult, thirdResult).join(); - - //Ensure instance's statistics. - assertThat(inventorySync.getStatistics()).isNotNull(); - - //TODO check distinct results when ISSUE #23 is resolved - //TODO uncomment assertions below when ISSUE #23 is resolved (otherwise they may fail) - - //assertThat(inventorySync.getStatistics().getProcessed()).isEqualTo(60); - //assertThat(inventorySync.getStatistics().getCreated()).isEqualTo(60); - //assertThat(inventorySync.getStatistics().getUpdated()).isEqualTo(0); - //assertThat(inventorySync.getStatistics().getFailed()).isEqualTo(0); - } - - private void assertValues(@Nonnull final InventoryEntry inventoryEntry, - @Nonnull final Long quantityOnStock, - @Nullable final ZonedDateTime expectedDelivery, - @Nullable final Integer restockableInDays) { - assertThat(inventoryEntry.getQuantityOnStock()).isEqualTo(quantityOnStock); - assertThat(inventoryEntry.getExpectedDelivery()).isEqualTo(expectedDelivery); - assertThat(inventoryEntry.getRestockableInDays()).isEqualTo(restockableInDays); - } + // Split them to batches. + final List firstBatch = newDrafts.subList(0, 20); + final List secondBatch = newDrafts.subList(20, 40); + final List thirdBatch = newDrafts.subList(40, 60); + + // Initialize sync. + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + final InventorySync inventorySync = new InventorySync(inventorySyncOptions); + + // Run batch syncing concurrently. + final CompletableFuture firstResult = + inventorySync.sync(firstBatch).toCompletableFuture(); + final CompletableFuture secondResult = + inventorySync.sync(secondBatch).toCompletableFuture(); + final CompletableFuture thirdResult = + inventorySync.sync(thirdBatch).toCompletableFuture(); + + CompletableFuture.allOf(firstResult, secondResult, thirdResult).join(); + + // Ensure instance's statistics. + assertThat(inventorySync.getStatistics()).isNotNull(); + + // TODO check distinct results when ISSUE #23 is resolved + // TODO uncomment assertions below when ISSUE #23 is resolved (otherwise they may fail) + + // assertThat(inventorySync.getStatistics().getProcessed()).isEqualTo(60); + // assertThat(inventorySync.getStatistics().getCreated()).isEqualTo(60); + // assertThat(inventorySync.getStatistics().getUpdated()).isEqualTo(0); + // assertThat(inventorySync.getStatistics().getFailed()).isEqualTo(0); + } + + private void assertValues( + @Nonnull final InventoryEntry inventoryEntry, + @Nonnull final Long quantityOnStock, + @Nullable final ZonedDateTime expectedDelivery, + @Nullable final Integer restockableInDays) { + assertThat(inventoryEntry.getQuantityOnStock()).isEqualTo(quantityOnStock); + assertThat(inventoryEntry.getExpectedDelivery()).isEqualTo(expectedDelivery); + assertThat(inventoryEntry.getRestockableInDays()).isEqualTo(restockableInDays); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/inventories/InventoryUpdateActionUtilsIT.java b/src/integration-test/java/com/commercetools/sync/integration/inventories/InventoryUpdateActionUtilsIT.java index 2377d5e4e1..c5ee9c6b21 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/inventories/InventoryUpdateActionUtilsIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/inventories/InventoryUpdateActionUtilsIT.java @@ -1,21 +1,5 @@ package com.commercetools.sync.integration.inventories; -import com.commercetools.sync.inventories.InventorySyncOptions; -import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; -import com.commercetools.sync.inventories.helpers.InventoryCustomActionBuilder; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.inventory.InventoryEntry; -import io.sphere.sdk.inventory.InventoryEntryDraft; -import io.sphere.sdk.inventory.commands.InventoryEntryUpdateCommand; -import io.sphere.sdk.types.CustomFieldsDraft; -import io.sphere.sdk.types.CustomFieldsDraftBuilder; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.Optional; - import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildPrimaryResourceCustomUpdateActions; import static com.commercetools.sync.integration.commons.utils.ChannelITUtils.deleteChannelsFromTargetAndSource; import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; @@ -31,64 +15,85 @@ import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.populateTargetProject; import static org.assertj.core.api.Assertions.assertThat; +import com.commercetools.sync.inventories.InventorySyncOptions; +import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; +import com.commercetools.sync.inventories.helpers.InventoryCustomActionBuilder; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.inventory.InventoryEntry; +import io.sphere.sdk.inventory.InventoryEntryDraft; +import io.sphere.sdk.inventory.commands.InventoryEntryUpdateCommand; +import io.sphere.sdk.types.CustomFieldsDraft; +import io.sphere.sdk.types.CustomFieldsDraftBuilder; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class InventoryUpdateActionUtilsIT { - private static final String CUSTOM_FIELD_VALUE = "custom-value-1"; + private static final String CUSTOM_FIELD_VALUE = "custom-value-1"; - /** - * Deletes inventories and supply channels from source and target CTP projects. - * Populates target CTP projects with test data. - */ - @BeforeEach - void setup() { - deleteInventoryEntriesFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - deleteChannelsFromTargetAndSource(); - populateTargetProject(); - } + /** + * Deletes inventories and supply channels from source and target CTP projects. Populates target + * CTP projects with test data. + */ + @BeforeEach + void setup() { + deleteInventoryEntriesFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + deleteChannelsFromTargetAndSource(); + populateTargetProject(); + } - /** - * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} projects that - * were set up in this test class. - */ - @AfterAll - static void tearDown() { - deleteInventoryEntriesFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - deleteChannelsFromTargetAndSource(); - } + /** + * Deletes all the test data from the {@code CTP_SOURCE_CLIENT} and the {@code CTP_SOURCE_CLIENT} + * projects that were set up in this test class. + */ + @AfterAll + static void tearDown() { + deleteInventoryEntriesFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + deleteChannelsFromTargetAndSource(); + } - @Test - void buildCustomUpdateActions_ShouldBuildActionThatSetCustomField() { - //Fetch old inventory and ensure it has custom fields. - final Optional oldInventoryOptional = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); - assertThat(oldInventoryOptional).isNotEmpty(); - final InventoryEntry oldInventoryBeforeSync = oldInventoryOptional.get(); - assertThat(oldInventoryBeforeSync.getCustom()).isNotNull(); - assertThat(oldInventoryBeforeSync.getCustom().getType().getObj()).isNotNull(); - assertThat(oldInventoryBeforeSync.getCustom().getType().getObj().getKey()).isEqualTo(CUSTOM_TYPE); + @Test + void buildCustomUpdateActions_ShouldBuildActionThatSetCustomField() { + // Fetch old inventory and ensure it has custom fields. + final Optional oldInventoryOptional = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); + assertThat(oldInventoryOptional).isNotEmpty(); + final InventoryEntry oldInventoryBeforeSync = oldInventoryOptional.get(); + assertThat(oldInventoryBeforeSync.getCustom()).isNotNull(); + assertThat(oldInventoryBeforeSync.getCustom().getType().getObj()).isNotNull(); + assertThat(oldInventoryBeforeSync.getCustom().getType().getObj().getKey()) + .isEqualTo(CUSTOM_TYPE); - //Prepare draft with updated data. - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraftBuilder - .ofTypeId(oldInventoryBeforeSync.getCustom().getType().getId()) - .addObject(CUSTOM_FIELD_NAME, CUSTOM_FIELD_VALUE).build(); - final InventoryEntryDraft newInventory = - InventoryEntryDraft.of(SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, null) + // Prepare draft with updated data. + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraftBuilder.ofTypeId(oldInventoryBeforeSync.getCustom().getType().getId()) + .addObject(CUSTOM_FIELD_NAME, CUSTOM_FIELD_VALUE) + .build(); + final InventoryEntryDraft newInventory = + InventoryEntryDraft.of( + SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, null) .withCustom(customFieldsDraft); - //Build update actions. - final InventorySyncOptions options = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); - final List> updateActions = - buildPrimaryResourceCustomUpdateActions(oldInventoryBeforeSync, newInventory, - new InventoryCustomActionBuilder(), options); - assertThat(updateActions).isNotEmpty(); + // Build update actions. + final InventorySyncOptions options = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + final List> updateActions = + buildPrimaryResourceCustomUpdateActions( + oldInventoryBeforeSync, newInventory, new InventoryCustomActionBuilder(), options); + assertThat(updateActions).isNotEmpty(); - //Execute update command and ensure returned entry is properly updated. - final InventoryEntry oldEntryAfterSync = CTP_TARGET_CLIENT + // Execute update command and ensure returned entry is properly updated. + final InventoryEntry oldEntryAfterSync = + CTP_TARGET_CLIENT .execute(InventoryEntryUpdateCommand.of(oldInventoryBeforeSync, updateActions)) - .toCompletableFuture().join(); - assertThat(oldEntryAfterSync.getCustom()).isNotNull(); - assertThat(oldEntryAfterSync.getCustom().getFieldAsString(CUSTOM_FIELD_NAME)).isEqualTo(CUSTOM_FIELD_VALUE); - } + .toCompletableFuture() + .join(); + assertThat(oldEntryAfterSync.getCustom()).isNotNull(); + assertThat(oldEntryAfterSync.getCustom().getFieldAsString(CUSTOM_FIELD_NAME)) + .isEqualTo(CUSTOM_FIELD_VALUE); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/inventories/utils/InventoryITUtils.java b/src/integration-test/java/com/commercetools/sync/integration/inventories/utils/InventoryITUtils.java index 648164249e..07860cf2e7 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/inventories/utils/InventoryITUtils.java +++ b/src/integration-test/java/com/commercetools/sync/integration/inventories/utils/InventoryITUtils.java @@ -1,5 +1,10 @@ package com.commercetools.sync.integration.inventories.utils; +import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static java.util.Collections.singletonList; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import io.sphere.sdk.channels.Channel; @@ -25,9 +30,6 @@ import io.sphere.sdk.types.TypeDraft; import io.sphere.sdk.types.TypeDraftBuilder; import io.sphere.sdk.types.commands.TypeCreateCommand; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Collections; @@ -35,175 +37,216 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; - -import static com.commercetools.sync.integration.commons.utils.ITUtils.queryAndExecute; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_SOURCE_CLIENT; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static java.util.Collections.singletonList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class InventoryITUtils { - public static final String SKU_1 = "100000"; - public static final String SKU_2 = "200000"; - - public static final Long QUANTITY_ON_STOCK_1 = 1L; - public static final Long QUANTITY_ON_STOCK_2 = 2L; - - public static final Integer RESTOCKABLE_IN_DAYS_1 = 1; - public static final Integer RESTOCKABLE_IN_DAYS_2 = 2; - - public static final ZonedDateTime EXPECTED_DELIVERY_1 = ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); - public static final ZonedDateTime EXPECTED_DELIVERY_2 = ZonedDateTime.of(2017, 5, 1, 20, 0, 0, 0, ZoneId.of("UTC")); - - public static final String SUPPLY_CHANNEL_KEY_1 = "channel-key_1"; - public static final String SUPPLY_CHANNEL_KEY_2 = "channel-key_2"; - - public static final String CUSTOM_TYPE = "inventory-custom-type-name"; - public static final String CUSTOM_FIELD_NAME = "inventory-custom-field-1"; - - /** - * Deletes all inventory entries from CTP project, represented by provided {@code ctpClient}. - * - * @param ctpClient represents the CTP project the inventory entries will be deleted from. - */ - public static void deleteInventoryEntries(@Nonnull final SphereClient ctpClient) { - queryAndExecute(ctpClient, InventoryEntryQuery.of(), InventoryEntryDeleteCommand::of); - } - - /** - * Deletes all inventory entries from CTP projects defined by {@code CTP_SOURCE_CLIENT} and - * {@code CTP_TARGET_CLIENT}. - */ - public static void deleteInventoryEntriesFromTargetAndSource() { - deleteInventoryEntries(CTP_SOURCE_CLIENT); - deleteInventoryEntries(CTP_TARGET_CLIENT); - } - - /** - * Populate source CTP project. - * Creates supply channel of key SUPPLY_CHANNEL_KEY_1. - * Creates supply channel of key SUPPLY_CHANNEL_KEY_2. - * Creates inventory entry of values: SKU_1, QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1. - * Creates inventory entry of values: SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2 and - * reference to firstly created supply channel. - * Creates inventory entry of values: SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2 and - * reference to secondly created supply channel. - */ - public static void populateSourceProject() { - final ChannelDraft channelDraft1 = ChannelDraft.of(SUPPLY_CHANNEL_KEY_1) - .withRoles(ChannelRole.INVENTORY_SUPPLY); - final ChannelDraft channelDraft2 = ChannelDraft.of(SUPPLY_CHANNEL_KEY_2) - .withRoles(ChannelRole.INVENTORY_SUPPLY); - - final String channelId1 = CTP_SOURCE_CLIENT.execute(ChannelCreateCommand.of(channelDraft1)) - .toCompletableFuture().join().getId(); - final String channelId2 = CTP_SOURCE_CLIENT.execute(ChannelCreateCommand.of(channelDraft2)) - .toCompletableFuture().join().getId(); - - final Reference supplyChannelReference1 = Channel.referenceOfId(channelId1); - final Reference supplyChannelReference2 = Channel.referenceOfId(channelId2); - - createInventoriesCustomType(CTP_SOURCE_CLIENT); - - final InventoryEntryDraft draft1 = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1, null) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE, getMockCustomFieldsJsons())).build(); - final InventoryEntryDraft draft2 = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, supplyChannelReference1) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE, getMockCustomFieldsJsons())).build(); - final InventoryEntryDraft draft3 = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, supplyChannelReference2) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE, getMockCustomFieldsJsons())).build(); - - CTP_SOURCE_CLIENT.execute(InventoryEntryCreateCommand.of(draft1)).toCompletableFuture().join(); - CTP_SOURCE_CLIENT.execute(InventoryEntryCreateCommand.of(draft2)).toCompletableFuture().join(); - CTP_SOURCE_CLIENT.execute(InventoryEntryCreateCommand.of(draft3)).toCompletableFuture().join(); - - } - - /** - * Populate target CTP project. - * Creates supply channel of key SUPPLY_CHANNEL_KEY_1. - * Creates inventory entry of values: SKU_1, QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1. - * Creates inventory entry of values: SKU_1, QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1 and - * reference to supply channel created before. - * Creates inventory custom type of key CUSTOM_TYPE, and String field definition of name CUSTOM_FIELD_NAME. - */ - public static void populateTargetProject() { - final ChannelDraft channelDraft = ChannelDraft.of(SUPPLY_CHANNEL_KEY_1).withRoles(ChannelRole.INVENTORY_SUPPLY); - final String channelId = CTP_TARGET_CLIENT.execute(ChannelCreateCommand.of(channelDraft)) - .toCompletableFuture().join().getId(); - final Reference supplyChannelReference = Channel.referenceOfId(channelId); - - createInventoriesCustomType(CTP_TARGET_CLIENT); - - final InventoryEntryDraft draft1 = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1, null) + public static final String SKU_1 = "100000"; + public static final String SKU_2 = "200000"; + + public static final Long QUANTITY_ON_STOCK_1 = 1L; + public static final Long QUANTITY_ON_STOCK_2 = 2L; + + public static final Integer RESTOCKABLE_IN_DAYS_1 = 1; + public static final Integer RESTOCKABLE_IN_DAYS_2 = 2; + + public static final ZonedDateTime EXPECTED_DELIVERY_1 = + ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); + public static final ZonedDateTime EXPECTED_DELIVERY_2 = + ZonedDateTime.of(2017, 5, 1, 20, 0, 0, 0, ZoneId.of("UTC")); + + public static final String SUPPLY_CHANNEL_KEY_1 = "channel-key_1"; + public static final String SUPPLY_CHANNEL_KEY_2 = "channel-key_2"; + + public static final String CUSTOM_TYPE = "inventory-custom-type-name"; + public static final String CUSTOM_FIELD_NAME = "inventory-custom-field-1"; + + /** + * Deletes all inventory entries from CTP project, represented by provided {@code ctpClient}. + * + * @param ctpClient represents the CTP project the inventory entries will be deleted from. + */ + public static void deleteInventoryEntries(@Nonnull final SphereClient ctpClient) { + queryAndExecute(ctpClient, InventoryEntryQuery.of(), InventoryEntryDeleteCommand::of); + } + + /** + * Deletes all inventory entries from CTP projects defined by {@code CTP_SOURCE_CLIENT} and {@code + * CTP_TARGET_CLIENT}. + */ + public static void deleteInventoryEntriesFromTargetAndSource() { + deleteInventoryEntries(CTP_SOURCE_CLIENT); + deleteInventoryEntries(CTP_TARGET_CLIENT); + } + + /** + * Populate source CTP project. Creates supply channel of key SUPPLY_CHANNEL_KEY_1. Creates supply + * channel of key SUPPLY_CHANNEL_KEY_2. Creates inventory entry of values: SKU_1, + * QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1. Creates inventory entry of + * values: SKU_1, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2 and reference to + * firstly created supply channel. Creates inventory entry of values: SKU_1, QUANTITY_ON_STOCK_2, + * EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2 and reference to secondly created supply channel. + */ + public static void populateSourceProject() { + final ChannelDraft channelDraft1 = + ChannelDraft.of(SUPPLY_CHANNEL_KEY_1).withRoles(ChannelRole.INVENTORY_SUPPLY); + final ChannelDraft channelDraft2 = + ChannelDraft.of(SUPPLY_CHANNEL_KEY_2).withRoles(ChannelRole.INVENTORY_SUPPLY); + + final String channelId1 = + CTP_SOURCE_CLIENT + .execute(ChannelCreateCommand.of(channelDraft1)) + .toCompletableFuture() + .join() + .getId(); + final String channelId2 = + CTP_SOURCE_CLIENT + .execute(ChannelCreateCommand.of(channelDraft2)) + .toCompletableFuture() + .join() + .getId(); + + final Reference supplyChannelReference1 = Channel.referenceOfId(channelId1); + final Reference supplyChannelReference2 = Channel.referenceOfId(channelId2); + + createInventoriesCustomType(CTP_SOURCE_CLIENT); + + final InventoryEntryDraft draft1 = + InventoryEntryDraftBuilder.of( + SKU_1, QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1, null) + .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE, getMockCustomFieldsJsons())) + .build(); + final InventoryEntryDraft draft2 = + InventoryEntryDraftBuilder.of( + SKU_1, + QUANTITY_ON_STOCK_2, + EXPECTED_DELIVERY_2, + RESTOCKABLE_IN_DAYS_2, + supplyChannelReference1) + .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE, getMockCustomFieldsJsons())) + .build(); + final InventoryEntryDraft draft3 = + InventoryEntryDraftBuilder.of( + SKU_1, + QUANTITY_ON_STOCK_2, + EXPECTED_DELIVERY_2, + RESTOCKABLE_IN_DAYS_2, + supplyChannelReference2) + .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE, getMockCustomFieldsJsons())) + .build(); + + CTP_SOURCE_CLIENT.execute(InventoryEntryCreateCommand.of(draft1)).toCompletableFuture().join(); + CTP_SOURCE_CLIENT.execute(InventoryEntryCreateCommand.of(draft2)).toCompletableFuture().join(); + CTP_SOURCE_CLIENT.execute(InventoryEntryCreateCommand.of(draft3)).toCompletableFuture().join(); + } + + /** + * Populate target CTP project. Creates supply channel of key SUPPLY_CHANNEL_KEY_1. Creates + * inventory entry of values: SKU_1, QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, + * RESTOCKABLE_IN_DAYS_1. Creates inventory entry of values: SKU_1, QUANTITY_ON_STOCK_1, + * EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1 and reference to supply channel created before. + * Creates inventory custom type of key CUSTOM_TYPE, and String field definition of name + * CUSTOM_FIELD_NAME. + */ + public static void populateTargetProject() { + final ChannelDraft channelDraft = + ChannelDraft.of(SUPPLY_CHANNEL_KEY_1).withRoles(ChannelRole.INVENTORY_SUPPLY); + final String channelId = + CTP_TARGET_CLIENT + .execute(ChannelCreateCommand.of(channelDraft)) + .toCompletableFuture() + .join() + .getId(); + final Reference supplyChannelReference = Channel.referenceOfId(channelId); + + createInventoriesCustomType(CTP_TARGET_CLIENT); + + final InventoryEntryDraft draft1 = + InventoryEntryDraftBuilder.of( + SKU_1, QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1, null) .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE, getMockCustomFieldsJsons())) .build(); - final InventoryEntryDraft draft2 = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_ON_STOCK_1, EXPECTED_DELIVERY_1, RESTOCKABLE_IN_DAYS_1, supplyChannelReference) + final InventoryEntryDraft draft2 = + InventoryEntryDraftBuilder.of( + SKU_1, + QUANTITY_ON_STOCK_1, + EXPECTED_DELIVERY_1, + RESTOCKABLE_IN_DAYS_1, + supplyChannelReference) .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE, getMockCustomFieldsJsons())) .build(); - CTP_TARGET_CLIENT.execute(InventoryEntryCreateCommand.of(draft1)).toCompletableFuture().join(); - CTP_TARGET_CLIENT.execute(InventoryEntryCreateCommand.of(draft2)).toCompletableFuture().join(); - } - - private static Type createInventoriesCustomType(@Nonnull final SphereClient ctpClient) { - final FieldDefinition fieldDefinition = FieldDefinition - .of(StringFieldType.of(), CUSTOM_FIELD_NAME, LocalizedString.of(Locale.ENGLISH, CUSTOM_FIELD_NAME), false); - final TypeDraft typeDraft = TypeDraftBuilder.of(CUSTOM_TYPE, LocalizedString.of(Locale.ENGLISH, CUSTOM_TYPE), - Collections.singleton(InventoryEntry.resourceTypeId())).fieldDefinitions(singletonList(fieldDefinition)) - .build(); - return ctpClient.execute(TypeCreateCommand.of(typeDraft)).toCompletableFuture().join(); - } - - private static Map getMockCustomFieldsJsons() { - final Map customFieldsJsons = new HashMap<>(); - customFieldsJsons - .put(CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode("customValue")); - return customFieldsJsons; - } - - /** - * Tries to fetch inventory entry of {@code sku} and {@code supplyChannel} using {@code sphereClient}. - * - * @param sphereClient sphere client used to execute requests - * @param sku sku of requested inventory entry - * @param supplyChannel optional reference to supply channel of requested inventory entry - * @return {@link Optional} which may contain inventory entry of {@code sku} and {@code supplyChannel} - */ - public static Optional getInventoryEntryBySkuAndSupplyChannel(@Nonnull final SphereClient - sphereClient, - @Nonnull final String sku, - @Nullable final Reference - supplyChannel) { - InventoryEntryQuery query = InventoryEntryQuery.of() - .withExpansionPaths(ExpansionPath.of("custom.type")) - .plusPredicates(inventoryEntryQueryModel -> - inventoryEntryQueryModel.sku().is(sku)); - query = supplyChannel == null - ? query.plusPredicates(inventoryEntryQueryModel -> inventoryEntryQueryModel.supplyChannel().isNotPresent()) - : query.plusPredicates(inventoryEntryQueryModel -> inventoryEntryQueryModel.supplyChannel() - .is(supplyChannel)); - return sphereClient.execute(query).toCompletableFuture().join().head(); - } - - /** - * Tries to fetch channel of key {@code channelKey} using {@code sphereClient}. - * - * @param sphereClient sphere client used to execute requests - * @param channelKey key of requested channel - * @return {@link Optional} which may contain channel of key {@code channelKey} - */ - public static Optional getChannelByKey(@Nonnull final SphereClient sphereClient, - @Nonnull final String channelKey) { - final ChannelQuery channelQuery = ChannelQueryBuilder.of().plusPredicates(channelQueryModel -> - channelQueryModel.key().is(channelKey)).build(); - return sphereClient.execute(channelQuery).toCompletableFuture().join().head(); - } - - private InventoryITUtils() { - } + CTP_TARGET_CLIENT.execute(InventoryEntryCreateCommand.of(draft1)).toCompletableFuture().join(); + CTP_TARGET_CLIENT.execute(InventoryEntryCreateCommand.of(draft2)).toCompletableFuture().join(); + } + + private static Type createInventoriesCustomType(@Nonnull final SphereClient ctpClient) { + final FieldDefinition fieldDefinition = + FieldDefinition.of( + StringFieldType.of(), + CUSTOM_FIELD_NAME, + LocalizedString.of(Locale.ENGLISH, CUSTOM_FIELD_NAME), + false); + final TypeDraft typeDraft = + TypeDraftBuilder.of( + CUSTOM_TYPE, + LocalizedString.of(Locale.ENGLISH, CUSTOM_TYPE), + Collections.singleton(InventoryEntry.resourceTypeId())) + .fieldDefinitions(singletonList(fieldDefinition)) + .build(); + return ctpClient.execute(TypeCreateCommand.of(typeDraft)).toCompletableFuture().join(); + } + + private static Map getMockCustomFieldsJsons() { + final Map customFieldsJsons = new HashMap<>(); + customFieldsJsons.put(CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode("customValue")); + return customFieldsJsons; + } + + /** + * Tries to fetch inventory entry of {@code sku} and {@code supplyChannel} using {@code + * sphereClient}. + * + * @param sphereClient sphere client used to execute requests + * @param sku sku of requested inventory entry + * @param supplyChannel optional reference to supply channel of requested inventory entry + * @return {@link Optional} which may contain inventory entry of {@code sku} and {@code + * supplyChannel} + */ + public static Optional getInventoryEntryBySkuAndSupplyChannel( + @Nonnull final SphereClient sphereClient, + @Nonnull final String sku, + @Nullable final Reference supplyChannel) { + InventoryEntryQuery query = + InventoryEntryQuery.of() + .withExpansionPaths(ExpansionPath.of("custom.type")) + .plusPredicates(inventoryEntryQueryModel -> inventoryEntryQueryModel.sku().is(sku)); + query = + supplyChannel == null + ? query.plusPredicates( + inventoryEntryQueryModel -> inventoryEntryQueryModel.supplyChannel().isNotPresent()) + : query.plusPredicates( + inventoryEntryQueryModel -> + inventoryEntryQueryModel.supplyChannel().is(supplyChannel)); + return sphereClient.execute(query).toCompletableFuture().join().head(); + } + + /** + * Tries to fetch channel of key {@code channelKey} using {@code sphereClient}. + * + * @param sphereClient sphere client used to execute requests + * @param channelKey key of requested channel + * @return {@link Optional} which may contain channel of key {@code channelKey} + */ + public static Optional getChannelByKey( + @Nonnull final SphereClient sphereClient, @Nonnull final String channelKey) { + final ChannelQuery channelQuery = + ChannelQueryBuilder.of() + .plusPredicates(channelQueryModel -> channelQueryModel.key().is(channelKey)) + .build(); + return sphereClient.execute(channelQuery).toCompletableFuture().join().head(); + } + + private InventoryITUtils() {} } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CartDiscountServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CartDiscountServiceImplIT.java index 4936d644c8..e2c4b70d0f 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CartDiscountServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CartDiscountServiceImplIT.java @@ -1,5 +1,24 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_CART_PREDICATE_2; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_KEY_1; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_KEY_2; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_NAME_2; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_TARGET_2; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_VALUE_2; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.SORT_ORDER_1; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.SORT_ORDER_2; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.deleteCartDiscountsFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.populateTargetProject; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; @@ -17,355 +36,378 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.models.errors.DuplicateFieldError; import io.sphere.sdk.queries.PagedResult; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_CART_PREDICATE_2; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_KEY_1; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_KEY_2; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_NAME_2; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_TARGET_2; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.CART_DISCOUNT_VALUE_2; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.SORT_ORDER_1; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.SORT_ORDER_2; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.deleteCartDiscountsFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.CartDiscountITUtils.populateTargetProject; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CartDiscountServiceImplIT { - private CartDiscountService cartDiscountService; + private CartDiscountService cartDiscountService; - private List errorCallBackMessages; - private List errorCallBackExceptions; + private List errorCallBackMessages; + private List errorCallBackExceptions; - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteCartDiscountsFromTargetAndSource(); - } + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteCartDiscountsFromTargetAndSource(); + } - /** - * Deletes types & cart discounts from the target CTP project, then it populates the project with test data. - */ - @BeforeEach - void setup() { - deleteCartDiscountsFromTargetAndSource(); + /** + * Deletes types & cart discounts from the target CTP project, then it populates the project with + * test data. + */ + @BeforeEach + void setup() { + deleteCartDiscountsFromTargetAndSource(); - populateTargetProject(); + populateTargetProject(); - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception); + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception); }) - .build(); - - cartDiscountService = new CartDiscountServiceImpl(cartDiscountSyncOptions); - } - - @Test - void fetchCartDiscount_WithExistingCartDiscountKey_ShouldFetchCartDiscount() { - final Optional cartDiscountOptional = CTP_TARGET_CLIENT - .execute(CartDiscountQuery.of() - .withPredicates(cartDiscountQueryModel -> - cartDiscountQueryModel.key().is(CART_DISCOUNT_KEY_1))) - .toCompletableFuture().join().head(); - assertThat(cartDiscountOptional).isNotNull(); - - final Optional fetchedCartDiscountOptional = - cartDiscountService.fetchCartDiscount(CART_DISCOUNT_KEY_1).toCompletableFuture().join(); - - assertThat(fetchedCartDiscountOptional).isEqualTo(cartDiscountOptional); - } - - @Test - void fetchMatchingCartDiscountsByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { - final Set cartDiscountKeys = new HashSet<>(); - final Set matchingCartDiscounts = - cartDiscountService.fetchMatchingCartDiscountsByKeys(cartDiscountKeys) - .toCompletableFuture() - .join(); - - assertThat(matchingCartDiscounts).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingCartDiscountsByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { - final Set cartDiscountKeys = new HashSet<>(); - cartDiscountKeys.add("cart_discount_key_1"); - cartDiscountKeys.add("cart_discount_key_2"); - - final Set matchingCartDiscounts = - cartDiscountService.fetchMatchingCartDiscountsByKeys(cartDiscountKeys) - .toCompletableFuture() - .join(); - - assertThat(matchingCartDiscounts).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingCartDiscountsByKeys_WithAnyExistingKeys_ShouldReturnASetOfCartDiscounts() { - final Set cartDiscountKeys = new HashSet<>(); - cartDiscountKeys.add(CART_DISCOUNT_KEY_1); - - final Set matchingCartDiscounts = - cartDiscountService.fetchMatchingCartDiscountsByKeys(cartDiscountKeys) - .toCompletableFuture() - .join(); - - assertThat(matchingCartDiscounts).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingCartDiscountsByKeys_WithBadGateWayExceptionAlways_ShouldFail() { - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(CartDiscountQuery.class))) - .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - - final CartDiscountSyncOptions spyOptions = - CartDiscountSyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception); - }) - .build(); - - final CartDiscountService spyCartDiscountService = new CartDiscountServiceImpl(spyOptions); - - - final Set cartDiscountKeys = new HashSet<>(); - cartDiscountKeys.add(CART_DISCOUNT_KEY_1); - - // test and assert - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyCartDiscountService.fetchMatchingCartDiscountsByKeys(cartDiscountKeys)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } - - @Test - void createCartDiscount_WithValidCartDiscount_ShouldCreateCartDiscount() { - //preparation - final CartDiscountDraft newCartDiscountDraft = - CartDiscountDraftBuilder.of(CART_DISCOUNT_NAME_2, - CART_DISCOUNT_CART_PREDICATE_2, - CART_DISCOUNT_VALUE_2, - CART_DISCOUNT_TARGET_2, - SORT_ORDER_2, - false) - .key(CART_DISCOUNT_KEY_2) - .active(false) - .build(); - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final CartDiscountSyncOptions spyOptions = - CartDiscountSyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception); - }) - .build(); - - final CartDiscountService spyCartDiscountService = new CartDiscountServiceImpl(spyOptions); - - //test - final Optional createdCartDiscount = spyCartDiscountService - .createCartDiscount(newCartDiscountDraft) - .toCompletableFuture().join(); - - final Optional queriedOptional = CTP_TARGET_CLIENT - .execute(CartDiscountQuery.of().withPredicates(cartDiscountQueryModel -> - cartDiscountQueryModel.key().is(CART_DISCOUNT_KEY_2))) - .toCompletableFuture().join().head(); - - assertThat(queriedOptional).hasValueSatisfying(queried -> - assertThat(createdCartDiscount).hasValueSatisfying(created -> { - assertThat(created.getKey()).isEqualTo(queried.getKey()); - assertThat(created.getName()).isEqualTo(queried.getName()); - assertThat(created.getCartPredicate()).isEqualTo(queried.getCartPredicate()); - assertThat(created.getValue()).isEqualTo(queried.getValue()); - assertThat(created.getTarget()).isEqualTo(queried.getTarget()); - assertThat(created.getSortOrder()).isEqualTo(queried.getSortOrder()); - })); - - } - - @Test - void createCartDiscount_WithDuplicateCartDiscountKey_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final CartDiscountDraft newCartDiscountDraft = - CartDiscountDraftBuilder.of(CART_DISCOUNT_NAME_2, - CART_DISCOUNT_CART_PREDICATE_2, - CART_DISCOUNT_VALUE_2, - CART_DISCOUNT_TARGET_2, - SORT_ORDER_2, - false) - .key(CART_DISCOUNT_KEY_1) - .active(false) - .build(); - - final CartDiscountSyncOptions options = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception); - }) - .build(); + .build(); + + cartDiscountService = new CartDiscountServiceImpl(cartDiscountSyncOptions); + } + + @Test + void fetchCartDiscount_WithExistingCartDiscountKey_ShouldFetchCartDiscount() { + final Optional cartDiscountOptional = + CTP_TARGET_CLIENT + .execute( + CartDiscountQuery.of() + .withPredicates( + cartDiscountQueryModel -> + cartDiscountQueryModel.key().is(CART_DISCOUNT_KEY_1))) + .toCompletableFuture() + .join() + .head(); + assertThat(cartDiscountOptional).isNotNull(); - cartDiscountService = new CartDiscountServiceImpl(options); + final Optional fetchedCartDiscountOptional = + cartDiscountService.fetchCartDiscount(CART_DISCOUNT_KEY_1).toCompletableFuture().join(); - // test - final Optional result = cartDiscountService - .createCartDiscount(newCartDiscountDraft) + assertThat(fetchedCartDiscountOptional).isEqualTo(cartDiscountOptional); + } + + @Test + void fetchMatchingCartDiscountsByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { + final Set cartDiscountKeys = new HashSet<>(); + final Set matchingCartDiscounts = + cartDiscountService + .fetchMatchingCartDiscountsByKeys(cartDiscountKeys) + .toCompletableFuture() + .join(); + + assertThat(matchingCartDiscounts).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingCartDiscountsByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { + final Set cartDiscountKeys = new HashSet<>(); + cartDiscountKeys.add("cart_discount_key_1"); + cartDiscountKeys.add("cart_discount_key_2"); + + final Set matchingCartDiscounts = + cartDiscountService + .fetchMatchingCartDiscountsByKeys(cartDiscountKeys) .toCompletableFuture() .join(); - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(msg -> assertThat(msg) - .contains(format("A duplicate value '\"%s\"' exists for field 'key'.", CART_DISCOUNT_KEY_1))); - - ensureErrorCallbackIsDuplicateFieldError(); - } - - private void ensureErrorCallbackIsDuplicateFieldError() { - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> { - assertThat(exception).hasCauseExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) exception.getCause(); - - final List fieldErrors = errorResponseException - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - }); - } - - @Test - void createCartDiscount_WithDuplicateSortOrder_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final CartDiscountDraft newCartDiscountDraft = - CartDiscountDraftBuilder.of(CART_DISCOUNT_NAME_2, - CART_DISCOUNT_CART_PREDICATE_2, - CART_DISCOUNT_VALUE_2, - CART_DISCOUNT_TARGET_2, - SORT_ORDER_1, - false) - .key(CART_DISCOUNT_KEY_2) - .active(false) - .build(); - - final CartDiscountSyncOptions options = CartDiscountSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception); + assertThat(matchingCartDiscounts).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingCartDiscountsByKeys_WithAnyExistingKeys_ShouldReturnASetOfCartDiscounts() { + final Set cartDiscountKeys = new HashSet<>(); + cartDiscountKeys.add(CART_DISCOUNT_KEY_1); + + final Set matchingCartDiscounts = + cartDiscountService + .fetchMatchingCartDiscountsByKeys(cartDiscountKeys) + .toCompletableFuture() + .join(); + + assertThat(matchingCartDiscounts).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingCartDiscountsByKeys_WithBadGateWayExceptionAlways_ShouldFail() { + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(CartDiscountQuery.class))) + .thenReturn(exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + + final CartDiscountSyncOptions spyOptions = + CartDiscountSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception); + }) + .build(); + + final CartDiscountService spyCartDiscountService = new CartDiscountServiceImpl(spyOptions); + + final Set cartDiscountKeys = new HashSet<>(); + cartDiscountKeys.add(CART_DISCOUNT_KEY_1); + + // test and assert + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyCartDiscountService.fetchMatchingCartDiscountsByKeys(cartDiscountKeys)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } + + @Test + void createCartDiscount_WithValidCartDiscount_ShouldCreateCartDiscount() { + // preparation + final CartDiscountDraft newCartDiscountDraft = + CartDiscountDraftBuilder.of( + CART_DISCOUNT_NAME_2, + CART_DISCOUNT_CART_PREDICATE_2, + CART_DISCOUNT_VALUE_2, + CART_DISCOUNT_TARGET_2, + SORT_ORDER_2, + false) + .key(CART_DISCOUNT_KEY_2) + .active(false) + .build(); + + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + + final CartDiscountSyncOptions spyOptions = + CartDiscountSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception); }) - .build(); + .build(); - cartDiscountService = new CartDiscountServiceImpl(options); + final CartDiscountService spyCartDiscountService = new CartDiscountServiceImpl(spyOptions); - // test - final Optional result = cartDiscountService + // test + final Optional createdCartDiscount = + spyCartDiscountService .createCartDiscount(newCartDiscountDraft) .toCompletableFuture() .join(); - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(msg -> assertThat(msg) - .contains(format("A duplicate value '\"%s\"' exists for field 'sortOrder'.", SORT_ORDER_1))); - - ensureErrorCallbackIsDuplicateFieldError(); - } - - @Test - void updateCartDiscount_WithValidChanges_ShouldUpdateCartDiscountCorrectly() { - final CartDiscount cartDiscount = CTP_TARGET_CLIENT - .execute(CartDiscountQuery.of() - .withPredicates(cartDiscountQueryModel -> - cartDiscountQueryModel.key().is(CART_DISCOUNT_KEY_1))) - .toCompletableFuture() - .thenApply(PagedResult::head) - .thenApply(Optional::get) - .join(); - - final ChangeCartPredicate changeCartPredicateUpdateAction = - ChangeCartPredicate.of(CART_DISCOUNT_CART_PREDICATE_2); - - final CartDiscount updatedCartDiscount = cartDiscountService + final Optional queriedOptional = + CTP_TARGET_CLIENT + .execute( + CartDiscountQuery.of() + .withPredicates( + cartDiscountQueryModel -> + cartDiscountQueryModel.key().is(CART_DISCOUNT_KEY_2))) + .toCompletableFuture() + .join() + .head(); + + assertThat(queriedOptional) + .hasValueSatisfying( + queried -> + assertThat(createdCartDiscount) + .hasValueSatisfying( + created -> { + assertThat(created.getKey()).isEqualTo(queried.getKey()); + assertThat(created.getName()).isEqualTo(queried.getName()); + assertThat(created.getCartPredicate()) + .isEqualTo(queried.getCartPredicate()); + assertThat(created.getValue()).isEqualTo(queried.getValue()); + assertThat(created.getTarget()).isEqualTo(queried.getTarget()); + assertThat(created.getSortOrder()).isEqualTo(queried.getSortOrder()); + })); + } + + @Test + void createCartDiscount_WithDuplicateCartDiscountKey_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final CartDiscountDraft newCartDiscountDraft = + CartDiscountDraftBuilder.of( + CART_DISCOUNT_NAME_2, + CART_DISCOUNT_CART_PREDICATE_2, + CART_DISCOUNT_VALUE_2, + CART_DISCOUNT_TARGET_2, + SORT_ORDER_2, + false) + .key(CART_DISCOUNT_KEY_1) + .active(false) + .build(); + + final CartDiscountSyncOptions options = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception); + }) + .build(); + + cartDiscountService = new CartDiscountServiceImpl(options); + + // test + final Optional result = + cartDiscountService.createCartDiscount(newCartDiscountDraft).toCompletableFuture().join(); + + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + msg -> + assertThat(msg) + .contains( + format( + "A duplicate value '\"%s\"' exists for field 'key'.", + CART_DISCOUNT_KEY_1))); + + ensureErrorCallbackIsDuplicateFieldError(); + } + + private void ensureErrorCallbackIsDuplicateFieldError() { + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> { + assertThat(exception).hasCauseExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) exception.getCause(); + + final List fieldErrors = + errorResponseException.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + }); + } + + @Test + void createCartDiscount_WithDuplicateSortOrder_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final CartDiscountDraft newCartDiscountDraft = + CartDiscountDraftBuilder.of( + CART_DISCOUNT_NAME_2, + CART_DISCOUNT_CART_PREDICATE_2, + CART_DISCOUNT_VALUE_2, + CART_DISCOUNT_TARGET_2, + SORT_ORDER_1, + false) + .key(CART_DISCOUNT_KEY_2) + .active(false) + .build(); + + final CartDiscountSyncOptions options = + CartDiscountSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception); + }) + .build(); + + cartDiscountService = new CartDiscountServiceImpl(options); + + // test + final Optional result = + cartDiscountService.createCartDiscount(newCartDiscountDraft).toCompletableFuture().join(); + + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + msg -> + assertThat(msg) + .contains( + format( + "A duplicate value '\"%s\"' exists for field 'sortOrder'.", + SORT_ORDER_1))); + + ensureErrorCallbackIsDuplicateFieldError(); + } + + @Test + void updateCartDiscount_WithValidChanges_ShouldUpdateCartDiscountCorrectly() { + final CartDiscount cartDiscount = + CTP_TARGET_CLIENT + .execute( + CartDiscountQuery.of() + .withPredicates( + cartDiscountQueryModel -> + cartDiscountQueryModel.key().is(CART_DISCOUNT_KEY_1))) + .toCompletableFuture() + .thenApply(PagedResult::head) + .thenApply(Optional::get) + .join(); + + final ChangeCartPredicate changeCartPredicateUpdateAction = + ChangeCartPredicate.of(CART_DISCOUNT_CART_PREDICATE_2); + + final CartDiscount updatedCartDiscount = + cartDiscountService .updateCartDiscount(cartDiscount, singletonList(changeCartPredicateUpdateAction)) .toCompletableFuture() .join(); - assertThat(updatedCartDiscount).satisfies(updated -> { - assertThat(updated.getKey()).isEqualTo(cartDiscount.getKey()); - assertThat(updated.getName()).isEqualTo(cartDiscount.getName()); - assertThat(updated.getValue()).isEqualTo(cartDiscount.getValue()); - assertThat(updated.getTarget()).isEqualTo(cartDiscount.getTarget()); - assertThat(updated.getCartPredicate()).isEqualTo(CART_DISCOUNT_CART_PREDICATE_2.toSphereCartPredicate()); - }); - } - - @Test - void updateCartDiscount_WithInvalidChanges_ShouldCompleteExceptionally() { - final CartDiscount cartDiscount = CTP_TARGET_CLIENT + assertThat(updatedCartDiscount) + .satisfies( + updated -> { + assertThat(updated.getKey()).isEqualTo(cartDiscount.getKey()); + assertThat(updated.getName()).isEqualTo(cartDiscount.getName()); + assertThat(updated.getValue()).isEqualTo(cartDiscount.getValue()); + assertThat(updated.getTarget()).isEqualTo(cartDiscount.getTarget()); + assertThat(updated.getCartPredicate()) + .isEqualTo(CART_DISCOUNT_CART_PREDICATE_2.toSphereCartPredicate()); + }); + } + + @Test + void updateCartDiscount_WithInvalidChanges_ShouldCompleteExceptionally() { + final CartDiscount cartDiscount = + CTP_TARGET_CLIENT .execute(CartDiscountByKeyGet.of(CART_DISCOUNT_KEY_1)) .toCompletableFuture() .join(); - final ChangeName invalidAction = ChangeName.of(null); + final ChangeName invalidAction = ChangeName.of(null); - cartDiscountService - .updateCartDiscount(cartDiscount, singletonList(invalidAction)) - .handle((result, throwable) -> { - assertThat(result).isNull(); - assertThat(throwable).hasMessageContaining("Request body does not contain valid JSON."); - return null; + cartDiscountService + .updateCartDiscount(cartDiscount, singletonList(invalidAction)) + .handle( + (result, throwable) -> { + assertThat(result).isNull(); + assertThat(throwable) + .hasMessageContaining("Request body does not contain valid JSON."); + return null; }) - .toCompletableFuture() - .join(); - } + .toCompletableFuture() + .join(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CategoryServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CategoryServiceImplIT.java index d40f1cfc05..f82446e76c 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CategoryServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CategoryServiceImplIT.java @@ -1,5 +1,19 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCustomFieldsDraft; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; @@ -19,12 +33,6 @@ import io.sphere.sdk.models.errors.DuplicateFieldError; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -34,465 +42,511 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionException; - -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCustomFieldsDraft; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypes; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CategoryServiceImplIT { - private CategoryService categoryService; - private Category oldCategory; - private static final String oldCategoryKey = "oldCategoryKey"; - - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - - /** - * Delete all categories and types from target project. Then create custom types for target CTP project categories. - */ - @BeforeAll - static void setup() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, "anyName", CTP_TARGET_CLIENT); - } - - /** - * Deletes Categories and Types from target CTP projects, then it populates target CTP project with category test - * data. - */ - @BeforeEach - void setupTest() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - deleteAllCategories(CTP_TARGET_CLIENT); - - final CategorySyncOptions categorySyncOptions = - CategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback( - (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback( - (exception, oldResource, newResource) -> warningCallBackMessages - .add(exception.getMessage())) - .build(); - - // Create a mock new category in the target project. - - final CategoryDraft oldCategoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "furniture")) + private CategoryService categoryService; + private Category oldCategory; + private static final String oldCategoryKey = "oldCategoryKey"; + + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + /** + * Delete all categories and types from target project. Then create custom types for target CTP + * project categories. + */ + @BeforeAll + static void setup() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, "anyName", CTP_TARGET_CLIENT); + } + + /** + * Deletes Categories and Types from target CTP projects, then it populates target CTP project + * with category test data. + */ + @BeforeEach + void setupTest() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + deleteAllCategories(CTP_TARGET_CLIENT); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .build(); + + // Create a mock new category in the target project. + + final CategoryDraft oldCategoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), + LocalizedString.of(Locale.ENGLISH, "furniture")) .key(oldCategoryKey) .custom(getCustomFieldsDraft()) .build(); - oldCategory = CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(oldCategoryDraft)) - .toCompletableFuture() - .join(); - - categoryService = new CategoryServiceImpl(categorySyncOptions); - } - - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteAllCategories(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - } - - @Test - void cacheKeysToIds_ShouldCacheCategoryKeysOnlyFirstTime() { - Map cache = categoryService.cacheKeysToIds(Collections.singleton(oldCategoryKey)) - .toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - // Create new category without caching - final String newCategoryKey = "newCategoryKey"; - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "classic furniture"), - LocalizedString.of(Locale.ENGLISH, "classic-furniture", Locale.GERMAN, "klassische-moebel")) + oldCategory = + CTP_TARGET_CLIENT + .execute(CategoryCreateCommand.of(oldCategoryDraft)) + .toCompletableFuture() + .join(); + + categoryService = new CategoryServiceImpl(categorySyncOptions); + } + + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteAllCategories(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + } + + @Test + void cacheKeysToIds_ShouldCacheCategoryKeysOnlyFirstTime() { + Map cache = + categoryService + .cacheKeysToIds(Collections.singleton(oldCategoryKey)) + .toCompletableFuture() + .join(); + assertThat(cache).hasSize(1); + + // Create new category without caching + final String newCategoryKey = "newCategoryKey"; + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "classic furniture"), + LocalizedString.of( + Locale.ENGLISH, "classic-furniture", Locale.GERMAN, "klassische-moebel")) .key(newCategoryKey) .build(); - CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(categoryDraft)).toCompletableFuture().join(); - - cache = categoryService.cacheKeysToIds(Collections.singleton(oldCategoryKey)).toCompletableFuture().join(); - assertThat(cache).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingCategoriesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { - final Set fetchedCategories = categoryService.fetchMatchingCategoriesByKeys(Collections.emptySet()) - .toCompletableFuture().join(); - assertThat(fetchedCategories).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingCategoriesByKeys_WithAllExistingSetOfKeys_ShouldReturnSetOfCategories() { - final Set keys = new HashSet<>(); - keys.add(oldCategoryKey); - final Set fetchedCategories = categoryService.fetchMatchingCategoriesByKeys(keys) - .toCompletableFuture().join(); - assertThat(fetchedCategories).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingCategoriesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(CategoryQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - final CategorySyncOptions spyOptions = - CategorySyncOptionsBuilder.of(spyClient) - .errorCallback( - (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .build(); - final CategoryService spyCategoryService = new CategoryServiceImpl(spyOptions); - - - final Set keys = new HashSet<>(); - keys.add(oldCategoryKey); - - // test and assert - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyCategoryService.fetchMatchingCategoriesByKeys(keys)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } - - @Test - void fetchMatchingCategoriesByKeys_WithSomeExistingSetOfKeys_ShouldReturnSetOfCategories() { - final Set keys = new HashSet<>(); - keys.add(oldCategoryKey); - keys.add("new-key"); - final Set fetchedCategories = categoryService.fetchMatchingCategoriesByKeys(keys) - .toCompletableFuture().join(); - assertThat(fetchedCategories).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchCachedCategoryId_WithNonExistingCategory_ShouldNotFetchACategory() { - final Optional categoryId = categoryService.fetchCachedCategoryId("non-existing-category-key") - .toCompletableFuture() - .join(); - assertThat(categoryId).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchCachedCategoryId_WithExistingCategory_ShouldFetchCategoryAndCache() { - final Optional categoryId = categoryService.fetchCachedCategoryId(oldCategory.getKey()) - .toCompletableFuture() - .join(); - assertThat(categoryId).contains(oldCategory.getId()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchCachedCategoryId_WithBlankKey_ShouldReturnFutureContainingEmptyOptional() { - // test - final Optional result = categoryService.fetchCachedCategoryId("").toCompletableFuture().join(); - - // assertions - assertThat(result).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void createCategory_WithValidCategory_ShouldCreateCategoryAndCacheId() { - // preparation - final String newCategoryKey = "newCategoryKey"; - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "classic furniture"), - LocalizedString.of(Locale.ENGLISH, "classic-furniture", Locale.GERMAN, "klassische-moebel")) + CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(categoryDraft)).toCompletableFuture().join(); + + cache = + categoryService + .cacheKeysToIds(Collections.singleton(oldCategoryKey)) + .toCompletableFuture() + .join(); + assertThat(cache).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingCategoriesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { + final Set fetchedCategories = + categoryService + .fetchMatchingCategoriesByKeys(Collections.emptySet()) + .toCompletableFuture() + .join(); + assertThat(fetchedCategories).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingCategoriesByKeys_WithAllExistingSetOfKeys_ShouldReturnSetOfCategories() { + final Set keys = new HashSet<>(); + keys.add(oldCategoryKey); + final Set fetchedCategories = + categoryService.fetchMatchingCategoriesByKeys(keys).toCompletableFuture().join(); + assertThat(fetchedCategories).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingCategoriesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(CategoryQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + final CategorySyncOptions spyOptions = + CategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); + final CategoryService spyCategoryService = new CategoryServiceImpl(spyOptions); + + final Set keys = new HashSet<>(); + keys.add(oldCategoryKey); + + // test and assert + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyCategoryService.fetchMatchingCategoriesByKeys(keys)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } + + @Test + void fetchMatchingCategoriesByKeys_WithSomeExistingSetOfKeys_ShouldReturnSetOfCategories() { + final Set keys = new HashSet<>(); + keys.add(oldCategoryKey); + keys.add("new-key"); + final Set fetchedCategories = + categoryService.fetchMatchingCategoriesByKeys(keys).toCompletableFuture().join(); + assertThat(fetchedCategories).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchCachedCategoryId_WithNonExistingCategory_ShouldNotFetchACategory() { + final Optional categoryId = + categoryService + .fetchCachedCategoryId("non-existing-category-key") + .toCompletableFuture() + .join(); + assertThat(categoryId).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchCachedCategoryId_WithExistingCategory_ShouldFetchCategoryAndCache() { + final Optional categoryId = + categoryService.fetchCachedCategoryId(oldCategory.getKey()).toCompletableFuture().join(); + assertThat(categoryId).contains(oldCategory.getId()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchCachedCategoryId_WithBlankKey_ShouldReturnFutureContainingEmptyOptional() { + // test + final Optional result = + categoryService.fetchCachedCategoryId("").toCompletableFuture().join(); + + // assertions + assertThat(result).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void createCategory_WithValidCategory_ShouldCreateCategoryAndCacheId() { + // preparation + final String newCategoryKey = "newCategoryKey"; + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "classic furniture"), + LocalizedString.of( + Locale.ENGLISH, "classic-furniture", Locale.GERMAN, "klassische-moebel")) .key(newCategoryKey) .custom(getCustomFieldsDraft()) .build(); - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final CategorySyncOptions spyOptions = CategorySyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final CategorySyncOptions spyOptions = + CategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final CategoryService spyProductService = new CategoryServiceImpl(spyOptions); - - // test - final Optional createdOptional = categoryService - .createCategory(categoryDraft) - .toCompletableFuture().join(); - - - // assertion - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - - - //assert CTP state - final Optional queriedOptional = CTP_TARGET_CLIENT - .execute(CategoryQuery.of() - .withPredicates(categoryQueryModel -> categoryQueryModel.key().is(newCategoryKey))) - .toCompletableFuture().join().head(); - - assertThat(queriedOptional) - .hasValueSatisfying(queried -> assertThat(createdOptional) - .hasValueSatisfying(created -> { - assertThat(queried.getName()).isEqualTo(created.getName()); - assertThat(queried.getSlug()).isEqualTo(created.getSlug()); - assertThat(queried.getCustom()).isNotNull(); - assertThat(queried.getKey()).isEqualTo(newCategoryKey); - })); - - // Assert that the created category is cached - final Optional productId = - spyProductService - .fetchCachedCategoryId(newCategoryKey) - .toCompletableFuture().join(); - assertThat(productId).isPresent(); - verify(spyClient, times(0)).execute(any(ProductTypeQuery.class)); - } - - @Test - void createCategory_WithBlankKey_ShouldNotCreateCategory() { - // preparation - final String newCategoryKey = ""; - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "classic furniture"), - LocalizedString.of(Locale.ENGLISH, "classic-furniture", Locale.GERMAN, "klassische-moebel")) + final CategoryService spyProductService = new CategoryServiceImpl(spyOptions); + + // test + final Optional createdOptional = + categoryService.createCategory(categoryDraft).toCompletableFuture().join(); + + // assertion + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + + // assert CTP state + final Optional queriedOptional = + CTP_TARGET_CLIENT + .execute( + CategoryQuery.of() + .withPredicates( + categoryQueryModel -> categoryQueryModel.key().is(newCategoryKey))) + .toCompletableFuture() + .join() + .head(); + + assertThat(queriedOptional) + .hasValueSatisfying( + queried -> + assertThat(createdOptional) + .hasValueSatisfying( + created -> { + assertThat(queried.getName()).isEqualTo(created.getName()); + assertThat(queried.getSlug()).isEqualTo(created.getSlug()); + assertThat(queried.getCustom()).isNotNull(); + assertThat(queried.getKey()).isEqualTo(newCategoryKey); + })); + + // Assert that the created category is cached + final Optional productId = + spyProductService.fetchCachedCategoryId(newCategoryKey).toCompletableFuture().join(); + assertThat(productId).isPresent(); + verify(spyClient, times(0)).execute(any(ProductTypeQuery.class)); + } + + @Test + void createCategory_WithBlankKey_ShouldNotCreateCategory() { + // preparation + final String newCategoryKey = ""; + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "classic furniture"), + LocalizedString.of( + Locale.ENGLISH, "classic-furniture", Locale.GERMAN, "klassische-moebel")) .key(newCategoryKey) .custom(getCustomFieldsDraft()) .build(); - // test - final Optional createdOptional = categoryService - .createCategory(categoryDraft) - .toCompletableFuture().join(); - - // assertion - assertThat(createdOptional).isEmpty(); - assertThat(errorCallBackMessages) - .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); - } - - @Test - void createCategory_WithDuplicateSlug_ShouldNotCreateCategory() { - // preparation - final String newCategoryKey = "newCat"; - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), + // test + final Optional createdOptional = + categoryService.createCategory(categoryDraft).toCompletableFuture().join(); + + // assertion + assertThat(createdOptional).isEmpty(); + assertThat(errorCallBackMessages) + .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); + } + + @Test + void createCategory_WithDuplicateSlug_ShouldNotCreateCategory() { + // preparation + final String newCategoryKey = "newCat"; + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "furniture")) .key(newCategoryKey) .custom(getCustomFieldsDraft()) .build(); - // test - final Optional createdCategoryOptional = categoryService.createCategory(categoryDraft) - .toCompletableFuture().join(); - // assertion - assertThat(createdCategoryOptional).isEmpty(); - - assertThat(errorCallBackExceptions) - .hasSize(1) - .allSatisfy(exception -> { - assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponse = ((ErrorResponseException)exception); - - final List fieldErrors = errorResponse - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - assertThat(fieldErrors).allSatisfy(error -> { - assertThat(error.getField()).isEqualTo("slug.en"); - assertThat(error.getDuplicateValue()).isEqualTo("furniture"); - }); + // test + final Optional createdCategoryOptional = + categoryService.createCategory(categoryDraft).toCompletableFuture().join(); + // assertion + assertThat(createdCategoryOptional).isEmpty(); + + assertThat(errorCallBackExceptions) + .hasSize(1) + .allSatisfy( + exception -> { + assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponse = ((ErrorResponseException) exception); + + final List fieldErrors = + errorResponse.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + assertThat(fieldErrors) + .allSatisfy( + error -> { + assertThat(error.getField()).isEqualTo("slug.en"); + assertThat(error.getDuplicateValue()).isEqualTo("furniture"); + }); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .allSatisfy(errorMessage -> { - assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); - assertThat(errorMessage).contains("\"field\" : \"slug.en\""); - assertThat(errorMessage).contains("\"duplicateValue\" : \"furniture\""); + assertThat(errorCallBackMessages) + .hasSize(1) + .allSatisfy( + errorMessage -> { + assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); + assertThat(errorMessage).contains("\"field\" : \"slug.en\""); + assertThat(errorMessage).contains("\"duplicateValue\" : \"furniture\""); }); - //assert CTP state - final Optional categoryOptional = CTP_TARGET_CLIENT - .execute(CategoryQuery.of() - .withPredicates(categoryQueryModel -> categoryQueryModel.key().is(newCategoryKey))) - .toCompletableFuture().join().head(); - assertThat(categoryOptional).isEmpty(); - } - - @Test - void updateCategory_WithValidChanges_ShouldUpdateCategoryCorrectly() { - final Optional categoryOptional = CTP_TARGET_CLIENT - .execute(CategoryQuery - .of() - .withPredicates(categoryQueryModel -> categoryQueryModel.key().is(oldCategory.getKey()))) - .toCompletableFuture().join().head(); - - final String newCategoryName = "This is my new name!"; - final ChangeName changeNameUpdateAction = ChangeName - .of(LocalizedString.of(Locale.GERMAN, newCategoryName)); - - final Category updatedCategory = categoryService - .updateCategory(categoryOptional.get(), Collections.singletonList(changeNameUpdateAction)) - .toCompletableFuture().join(); - assertThat(updatedCategory).isNotNull(); - - //assert CTP state - final Optional fetchedCategoryOptional = CTP_TARGET_CLIENT - .execute(CategoryQuery - .of() - .withPredicates(categoryQueryModel -> categoryQueryModel.key().is(oldCategory.getKey()))) - .toCompletableFuture().join().head(); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(fetchedCategoryOptional).isNotEmpty(); - final Category fetchedCategory = fetchedCategoryOptional.get(); - assertThat(fetchedCategory.getName()).isEqualTo(updatedCategory.getName()); - assertThat(fetchedCategory.getSlug()).isEqualTo(updatedCategory.getSlug()); - assertThat(fetchedCategory.getParent()).isEqualTo(updatedCategory.getParent()); - assertThat(fetchedCategory.getCustom()).isEqualTo(updatedCategory.getCustom()); - assertThat(fetchedCategory.getKey()).isEqualTo(updatedCategory.getKey()); - } - - @Test - void updateCategory_WithInvalidChanges_ShouldNotUpdateCategory() { - // Create a mock new category in the target project. - final CategoryDraft newCategoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "furniture"), LocalizedString.of(Locale.ENGLISH, "furniture1")) + // assert CTP state + final Optional categoryOptional = + CTP_TARGET_CLIENT + .execute( + CategoryQuery.of() + .withPredicates( + categoryQueryModel -> categoryQueryModel.key().is(newCategoryKey))) + .toCompletableFuture() + .join() + .head(); + assertThat(categoryOptional).isEmpty(); + } + + @Test + void updateCategory_WithValidChanges_ShouldUpdateCategoryCorrectly() { + final Optional categoryOptional = + CTP_TARGET_CLIENT + .execute( + CategoryQuery.of() + .withPredicates( + categoryQueryModel -> categoryQueryModel.key().is(oldCategory.getKey()))) + .toCompletableFuture() + .join() + .head(); + + final String newCategoryName = "This is my new name!"; + final ChangeName changeNameUpdateAction = + ChangeName.of(LocalizedString.of(Locale.GERMAN, newCategoryName)); + + final Category updatedCategory = + categoryService + .updateCategory( + categoryOptional.get(), Collections.singletonList(changeNameUpdateAction)) + .toCompletableFuture() + .join(); + assertThat(updatedCategory).isNotNull(); + + // assert CTP state + final Optional fetchedCategoryOptional = + CTP_TARGET_CLIENT + .execute( + CategoryQuery.of() + .withPredicates( + categoryQueryModel -> categoryQueryModel.key().is(oldCategory.getKey()))) + .toCompletableFuture() + .join() + .head(); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(fetchedCategoryOptional).isNotEmpty(); + final Category fetchedCategory = fetchedCategoryOptional.get(); + assertThat(fetchedCategory.getName()).isEqualTo(updatedCategory.getName()); + assertThat(fetchedCategory.getSlug()).isEqualTo(updatedCategory.getSlug()); + assertThat(fetchedCategory.getParent()).isEqualTo(updatedCategory.getParent()); + assertThat(fetchedCategory.getCustom()).isEqualTo(updatedCategory.getCustom()); + assertThat(fetchedCategory.getKey()).isEqualTo(updatedCategory.getKey()); + } + + @Test + void updateCategory_WithInvalidChanges_ShouldNotUpdateCategory() { + // Create a mock new category in the target project. + final CategoryDraft newCategoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "furniture"), + LocalizedString.of(Locale.ENGLISH, "furniture1")) .key("newCategory") .custom(getCustomFieldsDraft()) .build(); - final Category newCategory = CTP_TARGET_CLIENT.execute(CategoryCreateCommand.of(newCategoryDraft)) - .toCompletableFuture().join(); - - - final LocalizedString newSlug = LocalizedString.of(Locale.ENGLISH, "furniture"); - final ChangeSlug changeSlugUpdateAction = ChangeSlug.of(newSlug); - - categoryService - .updateCategory(newCategory, Collections.singletonList(changeSlugUpdateAction)) - .exceptionally(exception -> { - assertThat(exception).isExactlyInstanceOf(CompletionException.class); - assertThat(exception.getCause()).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponse = ((ErrorResponseException) exception.getCause()); - - final List fieldErrors = errorResponse - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - assertThat(fieldErrors).allSatisfy(error -> { - assertThat(error.getField()).isEqualTo("slug.en"); - assertThat(error.getDuplicateValue()).isEqualTo("furniture"); - }); - return null; + final Category newCategory = + CTP_TARGET_CLIENT + .execute(CategoryCreateCommand.of(newCategoryDraft)) + .toCompletableFuture() + .join(); + + final LocalizedString newSlug = LocalizedString.of(Locale.ENGLISH, "furniture"); + final ChangeSlug changeSlugUpdateAction = ChangeSlug.of(newSlug); + + categoryService + .updateCategory(newCategory, Collections.singletonList(changeSlugUpdateAction)) + .exceptionally( + exception -> { + assertThat(exception).isExactlyInstanceOf(CompletionException.class); + assertThat(exception.getCause()).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponse = + ((ErrorResponseException) exception.getCause()); + + final List fieldErrors = + errorResponse.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + assertThat(fieldErrors) + .allSatisfy( + error -> { + assertThat(error.getField()).isEqualTo("slug.en"); + assertThat(error.getDuplicateValue()).isEqualTo("furniture"); + }); + return null; }) - .toCompletableFuture().join(); - - - //assert CTP state - final Optional fetchedCategoryOptional = CTP_TARGET_CLIENT - .execute(CategoryQuery - .of() - .withPredicates(categoryQueryModel -> categoryQueryModel.key().is(newCategory.getKey()))) - .toCompletableFuture().join().head(); - - assertThat(fetchedCategoryOptional).isNotEmpty(); - final Category fetchedCategory = fetchedCategoryOptional.get(); - assertThat(fetchedCategory.getSlug()).isNotEqualTo(newSlug); - } - - @Test - void fetchCategory_WithExistingCategoryKey_ShouldFetchCategory() { - final Optional fetchedCategoryOptional = - executeBlocking(categoryService.fetchCategory(oldCategoryKey)); - assertThat(fetchedCategoryOptional).contains(oldCategory); - } - - @Test - void fetchCategory_WithBlankKey_ShouldNotFetchCategory() { - final Optional fetchedCategoryOptional = - executeBlocking(categoryService.fetchCategory(StringUtils.EMPTY)); - assertThat(fetchedCategoryOptional).isEmpty(); - } - - @Test - void fetchCategory_WithNullKey_ShouldNotFetchCategory() { - final Optional fetchedCategoryOptional = - executeBlocking(categoryService.fetchCategory(null)); - assertThat(fetchedCategoryOptional).isEmpty(); - } - - @Test - void fetchCategory_WithBadGateWayExceptionAlways_ShouldFail() { - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(CategoryQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - final CategorySyncOptions spyOptions = - CategorySyncOptionsBuilder.of(spyClient) - .errorCallback( - (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .build(); - final CategoryService spyCategoryService = new CategoryServiceImpl(spyOptions); - - // test and assertion - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyCategoryService.fetchCategory(oldCategoryKey)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } + .toCompletableFuture() + .join(); + + // assert CTP state + final Optional fetchedCategoryOptional = + CTP_TARGET_CLIENT + .execute( + CategoryQuery.of() + .withPredicates( + categoryQueryModel -> categoryQueryModel.key().is(newCategory.getKey()))) + .toCompletableFuture() + .join() + .head(); + + assertThat(fetchedCategoryOptional).isNotEmpty(); + final Category fetchedCategory = fetchedCategoryOptional.get(); + assertThat(fetchedCategory.getSlug()).isNotEqualTo(newSlug); + } + + @Test + void fetchCategory_WithExistingCategoryKey_ShouldFetchCategory() { + final Optional fetchedCategoryOptional = + executeBlocking(categoryService.fetchCategory(oldCategoryKey)); + assertThat(fetchedCategoryOptional).contains(oldCategory); + } + + @Test + void fetchCategory_WithBlankKey_ShouldNotFetchCategory() { + final Optional fetchedCategoryOptional = + executeBlocking(categoryService.fetchCategory(StringUtils.EMPTY)); + assertThat(fetchedCategoryOptional).isEmpty(); + } + + @Test + void fetchCategory_WithNullKey_ShouldNotFetchCategory() { + final Optional fetchedCategoryOptional = + executeBlocking(categoryService.fetchCategory(null)); + assertThat(fetchedCategoryOptional).isEmpty(); + } + + @Test + void fetchCategory_WithBadGateWayExceptionAlways_ShouldFail() { + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(CategoryQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + final CategorySyncOptions spyOptions = + CategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); + final CategoryService spyCategoryService = new CategoryServiceImpl(spyOptions); + + // test and assertion + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyCategoryService.fetchCategory(oldCategoryKey)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/ChannelServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/ChannelServiceImplIT.java index fec5b0d7eb..fd1b35ccb9 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/ChannelServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/ChannelServiceImplIT.java @@ -1,5 +1,13 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.ChannelITUtils.deleteChannelsFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.deleteInventoryEntriesFromTargetAndSource; +import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.populateTargetProject; +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.inventories.InventorySyncOptions; import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; import com.commercetools.sync.services.ChannelService; @@ -10,120 +18,111 @@ import io.sphere.sdk.channels.ChannelRole; import io.sphere.sdk.channels.commands.ChannelCreateCommand; import io.sphere.sdk.channels.queries.ChannelQuery; +import java.util.Optional; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Optional; - -import static com.commercetools.sync.integration.commons.utils.ChannelITUtils.deleteChannelsFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.deleteInventoryEntriesFromTargetAndSource; -import static com.commercetools.sync.integration.inventories.utils.InventoryITUtils.populateTargetProject; -import static java.util.Collections.singleton; -import static org.assertj.core.api.Assertions.assertThat; - class ChannelServiceImplIT { - private ChannelService channelService; - private static final String CHANNEL_KEY = "channel_key"; - - /** - * Deletes inventories and supply channels from source and target CTP projects. - * Populates target CTP project with test data. - */ - @BeforeEach - void setup() { - deleteInventoryEntriesFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - deleteChannelsFromTargetAndSource(); - populateTargetProject(); - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .build(); - channelService = new ChannelServiceImpl(inventorySyncOptions, singleton(ChannelRole.INVENTORY_SUPPLY)); - } - - /** - * Cleans up the target and source test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteInventoryEntriesFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - deleteChannelsFromTargetAndSource(); - } - - @Test - void fetchCachedChannelId_WithNonExistingChannel_ShouldNotFetchAChannel() { - Optional channelId = channelService.fetchCachedChannelId(CHANNEL_KEY) - .toCompletableFuture() - .join(); - assertThat(channelId).isEmpty(); - } - - @Test - void fetchCachedChannelId_WithExistingChannel_ShouldFetchChannelAndCache() { - final ChannelDraft draft = ChannelDraftBuilder.of(CHANNEL_KEY) - .roles(singleton(ChannelRole.INVENTORY_SUPPLY)) - .build(); - CTP_TARGET_CLIENT.execute(ChannelCreateCommand.of(draft)).toCompletableFuture().join(); - assertThat(channelService.fetchCachedChannelId(CHANNEL_KEY).toCompletableFuture().join()).isNotEmpty(); - } - - @Test - void createChannel_ShouldCreateChannel() { - // preparation - final String newChannelKey = "new_channel_key"; - - // test - final Optional result = channelService - .createChannel(newChannelKey) - .toCompletableFuture() - .join(); - - // assertion - assertThat(result).hasValueSatisfying(channel -> { - assertThat(channel.getRoles()).containsExactly(ChannelRole.INVENTORY_SUPPLY); - assertThat(channel.getKey()).isEqualTo(newChannelKey); - }); - - //assert CTP state - final Optional createdChannelOptional = CTP_TARGET_CLIENT + private ChannelService channelService; + private static final String CHANNEL_KEY = "channel_key"; + + /** + * Deletes inventories and supply channels from source and target CTP projects. Populates target + * CTP project with test data. + */ + @BeforeEach + void setup() { + deleteInventoryEntriesFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + deleteChannelsFromTargetAndSource(); + populateTargetProject(); + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + channelService = + new ChannelServiceImpl(inventorySyncOptions, singleton(ChannelRole.INVENTORY_SUPPLY)); + } + + /** Cleans up the target and source test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteInventoryEntriesFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + deleteChannelsFromTargetAndSource(); + } + + @Test + void fetchCachedChannelId_WithNonExistingChannel_ShouldNotFetchAChannel() { + Optional channelId = + channelService.fetchCachedChannelId(CHANNEL_KEY).toCompletableFuture().join(); + assertThat(channelId).isEmpty(); + } + + @Test + void fetchCachedChannelId_WithExistingChannel_ShouldFetchChannelAndCache() { + final ChannelDraft draft = + ChannelDraftBuilder.of(CHANNEL_KEY).roles(singleton(ChannelRole.INVENTORY_SUPPLY)).build(); + CTP_TARGET_CLIENT.execute(ChannelCreateCommand.of(draft)).toCompletableFuture().join(); + assertThat(channelService.fetchCachedChannelId(CHANNEL_KEY).toCompletableFuture().join()) + .isNotEmpty(); + } + + @Test + void createChannel_ShouldCreateChannel() { + // preparation + final String newChannelKey = "new_channel_key"; + + // test + final Optional result = + channelService.createChannel(newChannelKey).toCompletableFuture().join(); + + // assertion + assertThat(result) + .hasValueSatisfying( + channel -> { + assertThat(channel.getRoles()).containsExactly(ChannelRole.INVENTORY_SUPPLY); + assertThat(channel.getKey()).isEqualTo(newChannelKey); + }); + + // assert CTP state + final Optional createdChannelOptional = + CTP_TARGET_CLIENT .execute(ChannelQuery.of().byKey(newChannelKey)) .toCompletableFuture() .join() .head(); - assertThat(createdChannelOptional).isEqualTo(result); - } - - @Test - void createAndCacheChannel_ShouldCreateChannelAndCacheIt() { - // preparation - final String newChannelKey = "new_channel_key"; - - // test - final Optional result = channelService - .createAndCacheChannel(newChannelKey) - .toCompletableFuture() - .join(); - - // assertion - assertThat(result).hasValueSatisfying(channel -> { - assertThat(channel.getRoles()).containsExactly(ChannelRole.INVENTORY_SUPPLY); - assertThat(channel.getKey()).isEqualTo(newChannelKey); - }); - - //assert CTP state - final Optional createdChannelOptional = CTP_TARGET_CLIENT + assertThat(createdChannelOptional).isEqualTo(result); + } + + @Test + void createAndCacheChannel_ShouldCreateChannelAndCacheIt() { + // preparation + final String newChannelKey = "new_channel_key"; + + // test + final Optional result = + channelService.createAndCacheChannel(newChannelKey).toCompletableFuture().join(); + + // assertion + assertThat(result) + .hasValueSatisfying( + channel -> { + assertThat(channel.getRoles()).containsExactly(ChannelRole.INVENTORY_SUPPLY); + assertThat(channel.getKey()).isEqualTo(newChannelKey); + }); + + // assert CTP state + final Optional createdChannelOptional = + CTP_TARGET_CLIENT .execute(ChannelQuery.of().byKey(newChannelKey)) .toCompletableFuture() .join() .head(); - assertThat(createdChannelOptional).isEqualTo(result); + assertThat(createdChannelOptional).isEqualTo(result); - //assert cache state - final Optional newChannelId = - channelService.fetchCachedChannelId(newChannelKey).toCompletableFuture().join(); - assertThat(newChannelId).contains(result.get().getId()); - } + // assert cache state + final Optional newChannelId = + channelService.fetchCachedChannelId(newChannelKey).toCompletableFuture().join(); + assertThat(newChannelId).contains(result.get().getId()); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomObjectServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomObjectServiceImplIT.java index d86e4bbb28..77faa77a35 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomObjectServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomObjectServiceImplIT.java @@ -1,9 +1,19 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.createCustomObject; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteCustomObject; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customobjects.CustomObjectSyncOptions; import com.commercetools.sync.customobjects.CustomObjectSyncOptionsBuilder; import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; - import com.commercetools.sync.services.CustomObjectService; import com.commercetools.sync.services.impl.CustomObjectServiceImpl; import com.fasterxml.jackson.databind.JsonNode; @@ -24,262 +34,299 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteCustomObject; -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.createCustomObject; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - class CustomObjectServiceImplIT { - private CustomObjectService customObjectService; - private static final String OLD_CUSTOM_OBJECT_KEY = "old_custom_object_key"; - private static final String OLD_CUSTOM_OBJECT_CONTAINER = "old_custom_object_container"; - private static final ObjectNode OLD_CUSTOM_OBJECT_VALUE = - JsonNodeFactory.instance.objectNode().put( - "old_custom_object_attribute", "old_custom_object_value"); - - private static final String NEW_CUSTOM_OBJECT_KEY = "new_custom_object_key"; - private static final String NEW_CUSTOM_OBJECT_CONTAINER = "new_custom_object_container"; - private static final ObjectNode NEW_CUSTOM_OBJECT_VALUE = - JsonNodeFactory.instance.objectNode().put( - "new_custom_object_attribute", "new_custom_object_value"); - - private List errorCallBackMessages; - private List errorCallBackExceptions; - - /** - * Deletes customObjects from the target CTP project, then it populates the project with test data. - */ - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - - deleteCustomObject(CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY, OLD_CUSTOM_OBJECT_CONTAINER); - deleteCustomObject(CTP_TARGET_CLIENT, NEW_CUSTOM_OBJECT_KEY, NEW_CUSTOM_OBJECT_CONTAINER); - createCustomObject(CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY, - OLD_CUSTOM_OBJECT_CONTAINER, OLD_CUSTOM_OBJECT_VALUE); - - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + private CustomObjectService customObjectService; + private static final String OLD_CUSTOM_OBJECT_KEY = "old_custom_object_key"; + private static final String OLD_CUSTOM_OBJECT_CONTAINER = "old_custom_object_container"; + private static final ObjectNode OLD_CUSTOM_OBJECT_VALUE = + JsonNodeFactory.instance + .objectNode() + .put("old_custom_object_attribute", "old_custom_object_value"); + + private static final String NEW_CUSTOM_OBJECT_KEY = "new_custom_object_key"; + private static final String NEW_CUSTOM_OBJECT_CONTAINER = "new_custom_object_container"; + private static final ObjectNode NEW_CUSTOM_OBJECT_VALUE = + JsonNodeFactory.instance + .objectNode() + .put("new_custom_object_attribute", "new_custom_object_value"); + + private List errorCallBackMessages; + private List errorCallBackExceptions; + + /** + * Deletes customObjects from the target CTP project, then it populates the project with test + * data. + */ + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + + deleteCustomObject(CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY, OLD_CUSTOM_OBJECT_CONTAINER); + deleteCustomObject(CTP_TARGET_CLIENT, NEW_CUSTOM_OBJECT_KEY, NEW_CUSTOM_OBJECT_CONTAINER); + createCustomObject( + CTP_TARGET_CLIENT, + OLD_CUSTOM_OBJECT_KEY, + OLD_CUSTOM_OBJECT_CONTAINER, + OLD_CUSTOM_OBJECT_VALUE); + + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - customObjectService = new CustomObjectServiceImpl(customObjectSyncOptions); - } - - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteCustomObject(CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY, OLD_CUSTOM_OBJECT_CONTAINER); - deleteCustomObject(CTP_TARGET_CLIENT, NEW_CUSTOM_OBJECT_KEY, NEW_CUSTOM_OBJECT_CONTAINER); - } - - @Test - void fetchCachedCustomObjectId_WithNonExistingCustomObject_ShouldReturnEmptyCustomObject() { - CustomObjectCompositeIdentifier compositeIdentifier = - CustomObjectCompositeIdentifier.of("non-existing-key", "non-existing-container"); - final Optional customObject = customObjectService.fetchCachedCustomObjectId(compositeIdentifier) - .toCompletableFuture() - .join(); - assertThat(customObject).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingCustomObjects_WithNonExistingKeysAndContainers_ShouldReturnEmptySet() { - final Set customObjectCompositeIdentifiers = new HashSet<>(); - customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of( - OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_1")); - customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of( - OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_2")); - - final Set> matchingCustomObjects = customObjectService - .fetchMatchingCustomObjects(customObjectCompositeIdentifiers) - .toCompletableFuture() - .join(); - - assertThat(matchingCustomObjects).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingCustomObjects_WithDifferentExistingCombinationOfKeysAndContainers_ShouldReturnEmptySet() { - - deleteCustomObject(CTP_TARGET_CLIENT, - OLD_CUSTOM_OBJECT_KEY + "_1", - OLD_CUSTOM_OBJECT_CONTAINER + "_1"); - deleteCustomObject(CTP_TARGET_CLIENT, - OLD_CUSTOM_OBJECT_KEY + "_1", - OLD_CUSTOM_OBJECT_CONTAINER + "_2"); - deleteCustomObject(CTP_TARGET_CLIENT, - OLD_CUSTOM_OBJECT_KEY + "_2", - OLD_CUSTOM_OBJECT_CONTAINER + "_1"); - deleteCustomObject(CTP_TARGET_CLIENT, - OLD_CUSTOM_OBJECT_KEY + "_2", - OLD_CUSTOM_OBJECT_CONTAINER + "_2"); - - createCustomObject(CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_1", - OLD_CUSTOM_OBJECT_CONTAINER + "_1", OLD_CUSTOM_OBJECT_VALUE); - createCustomObject(CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_1", - OLD_CUSTOM_OBJECT_CONTAINER + "_2", OLD_CUSTOM_OBJECT_VALUE); - createCustomObject(CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_2", - OLD_CUSTOM_OBJECT_CONTAINER + "_1", OLD_CUSTOM_OBJECT_VALUE); - createCustomObject(CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_2", - OLD_CUSTOM_OBJECT_CONTAINER + "_2", OLD_CUSTOM_OBJECT_VALUE); - - final Set customObjectCompositeIdentifiers = new HashSet<>(); - customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of( - OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_1")); - customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of( - OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_2")); - customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of( - OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_1")); - - - final Set> matchingCustomObjects = customObjectService - .fetchMatchingCustomObjects(customObjectCompositeIdentifiers) - .toCompletableFuture() - .join(); - - assertThat(matchingCustomObjects).size().isEqualTo(3); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - - deleteCustomObject(CTP_TARGET_CLIENT, - OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_1"); - deleteCustomObject(CTP_TARGET_CLIENT, - OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_2"); - deleteCustomObject(CTP_TARGET_CLIENT, - OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_1"); - deleteCustomObject(CTP_TARGET_CLIENT, - OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_2"); - } - - @Test - void fetchMatchingCustomObjectsByCompositeIdentifiers_WithBadGateWayExceptionAlways_ShouldFail() { - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(CustomObjectQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - - final CustomObjectSyncOptions spyOptions = - CustomObjectSyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .build(); - - final CustomObjectService spyCustomObjectService = new CustomObjectServiceImpl(spyOptions); - - final Set customObjectCompositeIdentifiers = new HashSet<>(); - customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of( - OLD_CUSTOM_OBJECT_KEY, OLD_CUSTOM_OBJECT_CONTAINER)); - - // test and assert - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyCustomObjectService - .fetchMatchingCustomObjects(customObjectCompositeIdentifiers)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } - - @Test - void fetchCustomObject_WithNonExistingCustomObjectKeyAndContainer_ShouldReturnEmptyCustomObject() { - final Optional> customObjectOptional = CTP_TARGET_CLIENT - .execute(CustomObjectQuery.ofJsonNode() - .withPredicates(customObjectQueryModel -> - customObjectQueryModel.key().is(OLD_CUSTOM_OBJECT_KEY).and( - customObjectQueryModel.container().is(OLD_CUSTOM_OBJECT_CONTAINER)))) - .toCompletableFuture().join().head(); - assertThat(customObjectOptional).isNotNull(); - - final Optional> fetchedCustomObjectOptional = - executeBlocking(customObjectService.fetchCustomObject(CustomObjectCompositeIdentifier.of( - "non-existing-key", "non-existing-container"))); - assertThat(fetchedCustomObjectOptional).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void upsertCustomObject_WithValidCustomObject_ShouldCreateCustomObjectAndCacheId() { - final CustomObjectDraft newCustomObjectDraft = CustomObjectDraft.ofUnversionedUpsert( - NEW_CUSTOM_OBJECT_CONTAINER, - NEW_CUSTOM_OBJECT_KEY, - NEW_CUSTOM_OBJECT_VALUE); - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final CustomObjectSyncOptions spyOptions = CustomObjectSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); + customObjectService = new CustomObjectServiceImpl(customObjectSyncOptions); + } + + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteCustomObject(CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY, OLD_CUSTOM_OBJECT_CONTAINER); + deleteCustomObject(CTP_TARGET_CLIENT, NEW_CUSTOM_OBJECT_KEY, NEW_CUSTOM_OBJECT_CONTAINER); + } + + @Test + void fetchCachedCustomObjectId_WithNonExistingCustomObject_ShouldReturnEmptyCustomObject() { + CustomObjectCompositeIdentifier compositeIdentifier = + CustomObjectCompositeIdentifier.of("non-existing-key", "non-existing-container"); + final Optional customObject = + customObjectService + .fetchCachedCustomObjectId(compositeIdentifier) + .toCompletableFuture() + .join(); + assertThat(customObject).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingCustomObjects_WithNonExistingKeysAndContainers_ShouldReturnEmptySet() { + final Set customObjectCompositeIdentifiers = new HashSet<>(); + customObjectCompositeIdentifiers.add( + CustomObjectCompositeIdentifier.of( + OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_1")); + customObjectCompositeIdentifiers.add( + CustomObjectCompositeIdentifier.of( + OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_2")); + + final Set> matchingCustomObjects = + customObjectService + .fetchMatchingCustomObjects(customObjectCompositeIdentifiers) + .toCompletableFuture() + .join(); + + assertThat(matchingCustomObjects).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void + fetchMatchingCustomObjects_WithDifferentExistingCombinationOfKeysAndContainers_ShouldReturnEmptySet() { + + deleteCustomObject( + CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_1"); + deleteCustomObject( + CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_2"); + deleteCustomObject( + CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_1"); + deleteCustomObject( + CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_2"); + + createCustomObject( + CTP_TARGET_CLIENT, + OLD_CUSTOM_OBJECT_KEY + "_1", + OLD_CUSTOM_OBJECT_CONTAINER + "_1", + OLD_CUSTOM_OBJECT_VALUE); + createCustomObject( + CTP_TARGET_CLIENT, + OLD_CUSTOM_OBJECT_KEY + "_1", + OLD_CUSTOM_OBJECT_CONTAINER + "_2", + OLD_CUSTOM_OBJECT_VALUE); + createCustomObject( + CTP_TARGET_CLIENT, + OLD_CUSTOM_OBJECT_KEY + "_2", + OLD_CUSTOM_OBJECT_CONTAINER + "_1", + OLD_CUSTOM_OBJECT_VALUE); + createCustomObject( + CTP_TARGET_CLIENT, + OLD_CUSTOM_OBJECT_KEY + "_2", + OLD_CUSTOM_OBJECT_CONTAINER + "_2", + OLD_CUSTOM_OBJECT_VALUE); + + final Set customObjectCompositeIdentifiers = new HashSet<>(); + customObjectCompositeIdentifiers.add( + CustomObjectCompositeIdentifier.of( + OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_1")); + customObjectCompositeIdentifiers.add( + CustomObjectCompositeIdentifier.of( + OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_2")); + customObjectCompositeIdentifiers.add( + CustomObjectCompositeIdentifier.of( + OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_1")); + + final Set> matchingCustomObjects = + customObjectService + .fetchMatchingCustomObjects(customObjectCompositeIdentifiers) + .toCompletableFuture() + .join(); + + assertThat(matchingCustomObjects).size().isEqualTo(3); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + + deleteCustomObject( + CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_1"); + deleteCustomObject( + CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_1", OLD_CUSTOM_OBJECT_CONTAINER + "_2"); + deleteCustomObject( + CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_1"); + deleteCustomObject( + CTP_TARGET_CLIENT, OLD_CUSTOM_OBJECT_KEY + "_2", OLD_CUSTOM_OBJECT_CONTAINER + "_2"); + } + + @Test + void fetchMatchingCustomObjectsByCompositeIdentifiers_WithBadGateWayExceptionAlways_ShouldFail() { + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(CustomObjectQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + + final CustomObjectSyncOptions spyOptions = + CustomObjectSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }) - .build(); - - final CustomObjectService spyCustomObjectService = new CustomObjectServiceImpl(spyOptions); - - - final Optional> createdCustomObject = spyCustomObjectService - .upsertCustomObject(newCustomObjectDraft) - .toCompletableFuture().join(); - - final Optional> queriedOptional = CTP_TARGET_CLIENT - .execute(CustomObjectQuery.ofJsonNode().withPredicates(customObjectQueryModel -> - customObjectQueryModel.container().is(NEW_CUSTOM_OBJECT_CONTAINER).and( - customObjectQueryModel.key().is(NEW_CUSTOM_OBJECT_KEY)))) - .toCompletableFuture().join().head(); - - assertThat(queriedOptional).hasValueSatisfying(queried -> - assertThat(createdCustomObject).hasValueSatisfying(created -> { - assertThat(created.getKey()).isEqualTo(queried.getKey()); - assertThat(created.getContainer()).isEqualTo(queried.getContainer()); - assertThat(created.getId()).isEqualTo(queried.getId()); - assertThat(created.getValue()).isEqualTo(queried.getValue()); - })); - - // Assert that the created customObject is cached - final Optional customObjectId = - spyCustomObjectService.fetchCachedCustomObjectId( - CustomObjectCompositeIdentifier.of(NEW_CUSTOM_OBJECT_KEY, NEW_CUSTOM_OBJECT_CONTAINER)) - .toCompletableFuture().join(); - assertThat(customObjectId).isPresent(); - verify(spyClient, times(0)).execute(any(CustomObjectQuery.class)); - } - - @Test - void upsertCustomObject_WithDuplicateKeyAndContainerInCompositeIdentifier_ShouldUpdateValue() { - //preparation - final CustomObjectDraft newCustomObjectDraft = CustomObjectDraft.ofUnversionedUpsert( - OLD_CUSTOM_OBJECT_CONTAINER, - OLD_CUSTOM_OBJECT_KEY, - NEW_CUSTOM_OBJECT_VALUE); - - final Optional> result = - customObjectService.upsertCustomObject(newCustomObjectDraft).toCompletableFuture().join(); - - // assertion - assertThat(result).isNotEmpty(); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - assertThat(result.get().getValue()).isEqualTo(NEW_CUSTOM_OBJECT_VALUE); - assertThat(result.get().getContainer()).isEqualTo(OLD_CUSTOM_OBJECT_CONTAINER); - assertThat(result.get().getKey()).isEqualTo(OLD_CUSTOM_OBJECT_KEY); - - } + .build(); + + final CustomObjectService spyCustomObjectService = new CustomObjectServiceImpl(spyOptions); + + final Set customObjectCompositeIdentifiers = new HashSet<>(); + customObjectCompositeIdentifiers.add( + CustomObjectCompositeIdentifier.of(OLD_CUSTOM_OBJECT_KEY, OLD_CUSTOM_OBJECT_CONTAINER)); + + // test and assert + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyCustomObjectService.fetchMatchingCustomObjects(customObjectCompositeIdentifiers)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } + + @Test + void + fetchCustomObject_WithNonExistingCustomObjectKeyAndContainer_ShouldReturnEmptyCustomObject() { + final Optional> customObjectOptional = + CTP_TARGET_CLIENT + .execute( + CustomObjectQuery.ofJsonNode() + .withPredicates( + customObjectQueryModel -> + customObjectQueryModel + .key() + .is(OLD_CUSTOM_OBJECT_KEY) + .and( + customObjectQueryModel + .container() + .is(OLD_CUSTOM_OBJECT_CONTAINER)))) + .toCompletableFuture() + .join() + .head(); + assertThat(customObjectOptional).isNotNull(); + + final Optional> fetchedCustomObjectOptional = + executeBlocking( + customObjectService.fetchCustomObject( + CustomObjectCompositeIdentifier.of("non-existing-key", "non-existing-container"))); + assertThat(fetchedCustomObjectOptional).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void upsertCustomObject_WithValidCustomObject_ShouldCreateCustomObjectAndCacheId() { + final CustomObjectDraft newCustomObjectDraft = + CustomObjectDraft.ofUnversionedUpsert( + NEW_CUSTOM_OBJECT_CONTAINER, NEW_CUSTOM_OBJECT_KEY, NEW_CUSTOM_OBJECT_VALUE); + + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final CustomObjectSyncOptions spyOptions = + CustomObjectSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); + + final CustomObjectService spyCustomObjectService = new CustomObjectServiceImpl(spyOptions); + + final Optional> createdCustomObject = + spyCustomObjectService + .upsertCustomObject(newCustomObjectDraft) + .toCompletableFuture() + .join(); + + final Optional> queriedOptional = + CTP_TARGET_CLIENT + .execute( + CustomObjectQuery.ofJsonNode() + .withPredicates( + customObjectQueryModel -> + customObjectQueryModel + .container() + .is(NEW_CUSTOM_OBJECT_CONTAINER) + .and(customObjectQueryModel.key().is(NEW_CUSTOM_OBJECT_KEY)))) + .toCompletableFuture() + .join() + .head(); + + assertThat(queriedOptional) + .hasValueSatisfying( + queried -> + assertThat(createdCustomObject) + .hasValueSatisfying( + created -> { + assertThat(created.getKey()).isEqualTo(queried.getKey()); + assertThat(created.getContainer()).isEqualTo(queried.getContainer()); + assertThat(created.getId()).isEqualTo(queried.getId()); + assertThat(created.getValue()).isEqualTo(queried.getValue()); + })); + + // Assert that the created customObject is cached + final Optional customObjectId = + spyCustomObjectService + .fetchCachedCustomObjectId( + CustomObjectCompositeIdentifier.of( + NEW_CUSTOM_OBJECT_KEY, NEW_CUSTOM_OBJECT_CONTAINER)) + .toCompletableFuture() + .join(); + assertThat(customObjectId).isPresent(); + verify(spyClient, times(0)).execute(any(CustomObjectQuery.class)); + } + + @Test + void upsertCustomObject_WithDuplicateKeyAndContainerInCompositeIdentifier_ShouldUpdateValue() { + // preparation + final CustomObjectDraft newCustomObjectDraft = + CustomObjectDraft.ofUnversionedUpsert( + OLD_CUSTOM_OBJECT_CONTAINER, OLD_CUSTOM_OBJECT_KEY, NEW_CUSTOM_OBJECT_VALUE); + + final Optional> result = + customObjectService.upsertCustomObject(newCustomObjectDraft).toCompletableFuture().join(); + + // assertion + assertThat(result).isNotEmpty(); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + assertThat(result.get().getValue()).isEqualTo(NEW_CUSTOM_OBJECT_VALUE); + assertThat(result.get().getContainer()).isEqualTo(OLD_CUSTOM_OBJECT_CONTAINER); + assertThat(result.get().getKey()).isEqualTo(OLD_CUSTOM_OBJECT_KEY); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomerGroupServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomerGroupServiceImplIT.java index bf866b6ab1..adffd7608c 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomerGroupServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomerGroupServiceImplIT.java @@ -1,81 +1,81 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.createCustomerGroup; +import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.deleteCustomerGroups; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.services.CustomerGroupService; import com.commercetools.sync.services.impl.CustomerGroupServiceImpl; import io.sphere.sdk.customergroups.CustomerGroup; +import java.util.ArrayList; +import java.util.Optional; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.Optional; - -import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.createCustomerGroup; -import static com.commercetools.sync.integration.commons.utils.CustomerGroupITUtils.deleteCustomerGroups; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static org.assertj.core.api.Assertions.assertThat; - class CustomerGroupServiceImplIT { - private CustomerGroupService customerGroupService; - private static final String CUSTOMER_GROUP_KEY = "customerGroup_key"; - private static final String CUSTOMER_GROUP_NAME = "customerGroup_name"; + private CustomerGroupService customerGroupService; + private static final String CUSTOMER_GROUP_KEY = "customerGroup_key"; + private static final String CUSTOMER_GROUP_NAME = "customerGroup_name"; - private CustomerGroup oldCustomerGroup; - private ArrayList warnings; + private CustomerGroup oldCustomerGroup; + private ArrayList warnings; - /** - * Deletes customer group from the target CTP projects, then it populates the project with test data. - */ - @BeforeEach - void setup() { - deleteCustomerGroups(CTP_TARGET_CLIENT); - warnings = new ArrayList<>(); - oldCustomerGroup = createCustomerGroup(CTP_TARGET_CLIENT, CUSTOMER_GROUP_NAME, CUSTOMER_GROUP_KEY); - final ProductSyncOptions productSyncOptions = - ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .warningCallback((exception, oldResource, newResource) -> warnings.add(exception.getMessage())) - .build(); - customerGroupService = new CustomerGroupServiceImpl(productSyncOptions); - } + /** + * Deletes customer group from the target CTP projects, then it populates the project with test + * data. + */ + @BeforeEach + void setup() { + deleteCustomerGroups(CTP_TARGET_CLIENT); + warnings = new ArrayList<>(); + oldCustomerGroup = + createCustomerGroup(CTP_TARGET_CLIENT, CUSTOMER_GROUP_NAME, CUSTOMER_GROUP_KEY); + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .warningCallback( + (exception, oldResource, newResource) -> warnings.add(exception.getMessage())) + .build(); + customerGroupService = new CustomerGroupServiceImpl(productSyncOptions); + } - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteCustomerGroups(CTP_TARGET_CLIENT); - } + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteCustomerGroups(CTP_TARGET_CLIENT); + } - @Test - void fetchCachedCustomerGroupId_WithNonExistingCustomerGroup_ShouldNotFetchACustomerGroup() { - final Optional customerGroupId = customerGroupService + @Test + void fetchCachedCustomerGroupId_WithNonExistingCustomerGroup_ShouldNotFetchACustomerGroup() { + final Optional customerGroupId = + customerGroupService .fetchCachedCustomerGroupId("nonExistingKey") .toCompletableFuture() .join(); - assertThat(customerGroupId).isEmpty(); - } + assertThat(customerGroupId).isEmpty(); + } - @Test - void fetchCachedCustomerGroupId_WithExistingCustomerGroup_ShouldFetchCustomerGroupAndCache() { - final Optional customerGroupId = customerGroupService + @Test + void fetchCachedCustomerGroupId_WithExistingCustomerGroup_ShouldFetchCustomerGroupAndCache() { + final Optional customerGroupId = + customerGroupService .fetchCachedCustomerGroupId(oldCustomerGroup.getKey()) .toCompletableFuture() .join(); - assertThat(customerGroupId).contains(oldCustomerGroup.getId()); - assertThat(warnings).isEmpty(); - } + assertThat(customerGroupId).contains(oldCustomerGroup.getId()); + assertThat(warnings).isEmpty(); + } - @Test - void fetchCachedCustomerGroupId_WithNullKey_ShouldReturnFutureWithEmptyOptional() { - final Optional customerGroupId = customerGroupService - .fetchCachedCustomerGroupId(null) - .toCompletableFuture() - .join(); + @Test + void fetchCachedCustomerGroupId_WithNullKey_ShouldReturnFutureWithEmptyOptional() { + final Optional customerGroupId = + customerGroupService.fetchCachedCustomerGroupId(null).toCompletableFuture().join(); - assertThat(customerGroupId).isEmpty(); - assertThat(warnings).isEmpty(); - } + assertThat(customerGroupId).isEmpty(); + assertThat(warnings).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomerServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomerServiceImplIT.java index 5ee7bb0f13..2b6a97b526 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomerServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/CustomerServiceImplIT.java @@ -1,5 +1,17 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.deleteCustomers; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static java.lang.String.format; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import com.commercetools.sync.customers.CustomerSyncOptions; import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; import com.commercetools.sync.services.CustomerService; @@ -12,225 +24,231 @@ import io.sphere.sdk.customers.commands.updateactions.ChangeEmail; import io.sphere.sdk.customers.queries.CustomerQuery; import io.sphere.sdk.queries.QueryPredicate; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.integration.commons.utils.CustomerITUtils.deleteCustomers; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static java.lang.String.format; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomerServiceImplIT { - private static final String EXISTING_CUSTOMER_KEY = "existing-customer-key"; - private CustomerService customerService; - private Customer customer; - - - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - /** - * Deletes Customers from target CTP projects, then it populates target CTP project with customer test data. - */ - @BeforeEach - void setupTest() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - - deleteCustomers(CTP_TARGET_CLIENT); - - final CustomerSyncOptions customerSyncOptions = - CustomerSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback( - (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback( - (exception, oldResource, newResource) -> warningCallBackMessages - .add(exception.getMessage())) - .build(); - - // Create a mock new customer in the target project. - CustomerDraft customerDraft = CustomerDraftBuilder - .of("mail@mail.com", "password") - .key(EXISTING_CUSTOMER_KEY) - .build(); - customer = CTP_TARGET_CLIENT.execute(CustomerCreateCommand.of(customerDraft)) - .toCompletableFuture().join().getCustomer(); - - customerService = new CustomerServiceImpl(customerSyncOptions); - } - - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteCustomers(CTP_TARGET_CLIENT); - } - - @Test - void fetchCachedCustomerId_WithNonExistingCustomer_ShouldNotFetchACustomerId() { - final Optional customerId = customerService.fetchCachedCustomerId("non-existing-customer-key") - .toCompletableFuture() - .join(); - assertThat(customerId).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchCachedCustomerId_WithExistingNotCachedCustomer_ShouldFetchACustomerId() { - final Optional customerId = customerService.fetchCachedCustomerId(EXISTING_CUSTOMER_KEY) - .toCompletableFuture() - .join(); - assertThat(customerId).isNotEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchCustomerByKey_WithExistingCustomer_ShouldFetchCustomer() { - Optional customer = customerService.fetchCustomerByKey(EXISTING_CUSTOMER_KEY) - .toCompletableFuture() - .join(); - assertThat(customer).isNotEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchCustomerByKey_WithNotExistingCustomer_ShouldReturnEmptyOptional() { - Optional customer = customerService.fetchCustomerByKey("not-existing-customer-key") - .toCompletableFuture() - .join(); - assertThat(customer).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void cacheKeysToIds_WithEmptyKeys_ShouldReturnCurrentCache() { - Map cache = customerService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); - assertThat(cache).hasSize(0); - - cache = customerService.cacheKeysToIds(singleton(EXISTING_CUSTOMER_KEY)).toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - cache = customerService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void cacheKeysToIds_WithCachedKeys_ShouldReturnCacheWithoutAnyRequests() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final CustomerSyncOptions customerSyncOptions = - CustomerSyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, oldResource, newResource) - -> warningCallBackMessages.add(exception.getMessage())) - .build(); - final CustomerServiceImpl spyCustomerService = new CustomerServiceImpl(customerSyncOptions); - - - Map cache = spyCustomerService.cacheKeysToIds(singleton(EXISTING_CUSTOMER_KEY)) - .toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - cache = spyCustomerService.cacheKeysToIds(singleton(EXISTING_CUSTOMER_KEY)) - .toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - verify(spyClient, times(1)).execute(any()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingCustomersByKeys_WithEmptyKeys_ShouldReturnEmptySet() { - Set customers = customerService.fetchMatchingCustomersByKeys(emptySet()) - .toCompletableFuture() - .join(); - - assertThat(customers).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - } - - @Test - void fetchMatchingCustomersByKeys_WithExistingCustomerKeys_ShouldReturnCustomers() { - Set customers = customerService.fetchMatchingCustomersByKeys(singleton(EXISTING_CUSTOMER_KEY)) - .toCompletableFuture() - .join(); - - assertThat(customers).hasSize(1); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - } - - @Test - void createCustomer_WithDuplicationException_ShouldNotCreateCustomer() { - CustomerDraft customerDraft = CustomerDraftBuilder - .of("mail@mail.com", "password") - .key("newKey") + private static final String EXISTING_CUSTOMER_KEY = "existing-customer-key"; + private CustomerService customerService; + private Customer customer; + + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + /** + * Deletes Customers from target CTP projects, then it populates target CTP project with customer + * test data. + */ + @BeforeEach + void setupTest() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + + deleteCustomers(CTP_TARGET_CLIENT); + + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - Optional customerOptional = - customerService.createCustomer(customerDraft).toCompletableFuture().join(); - - assertThat(customerOptional).isEmpty(); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).contains("Failed to create draft with key: 'newKey'. Reason: " - + "detailMessage: There is already an existing customer with the email '\"mail@mail.com\"'."); - assertThat(errorCallBackExceptions).hasSize(1); - } - - @Test - void updateCustomer_WithValidChanges_ShouldUpdateCustomerCorrectly() { - final String newEmail = "newMail@newmail.com"; - final ChangeEmail changeEmail = ChangeEmail - .of(newEmail); - - final Customer updatedCustomer = customerService + // Create a mock new customer in the target project. + CustomerDraft customerDraft = + CustomerDraftBuilder.of("mail@mail.com", "password").key(EXISTING_CUSTOMER_KEY).build(); + customer = + CTP_TARGET_CLIENT + .execute(CustomerCreateCommand.of(customerDraft)) + .toCompletableFuture() + .join() + .getCustomer(); + + customerService = new CustomerServiceImpl(customerSyncOptions); + } + + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteCustomers(CTP_TARGET_CLIENT); + } + + @Test + void fetchCachedCustomerId_WithNonExistingCustomer_ShouldNotFetchACustomerId() { + final Optional customerId = + customerService + .fetchCachedCustomerId("non-existing-customer-key") + .toCompletableFuture() + .join(); + assertThat(customerId).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchCachedCustomerId_WithExistingNotCachedCustomer_ShouldFetchACustomerId() { + final Optional customerId = + customerService.fetchCachedCustomerId(EXISTING_CUSTOMER_KEY).toCompletableFuture().join(); + assertThat(customerId).isNotEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchCustomerByKey_WithExistingCustomer_ShouldFetchCustomer() { + Optional customer = + customerService.fetchCustomerByKey(EXISTING_CUSTOMER_KEY).toCompletableFuture().join(); + assertThat(customer).isNotEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchCustomerByKey_WithNotExistingCustomer_ShouldReturnEmptyOptional() { + Optional customer = + customerService + .fetchCustomerByKey("not-existing-customer-key") + .toCompletableFuture() + .join(); + assertThat(customer).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void cacheKeysToIds_WithEmptyKeys_ShouldReturnCurrentCache() { + Map cache = + customerService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); + assertThat(cache).hasSize(0); + + cache = + customerService + .cacheKeysToIds(singleton(EXISTING_CUSTOMER_KEY)) + .toCompletableFuture() + .join(); + assertThat(cache).hasSize(1); + + cache = customerService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); + assertThat(cache).hasSize(1); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void cacheKeysToIds_WithCachedKeys_ShouldReturnCacheWithoutAnyRequests() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .build(); + final CustomerServiceImpl spyCustomerService = new CustomerServiceImpl(customerSyncOptions); + + Map cache = + spyCustomerService + .cacheKeysToIds(singleton(EXISTING_CUSTOMER_KEY)) + .toCompletableFuture() + .join(); + assertThat(cache).hasSize(1); + + cache = + spyCustomerService + .cacheKeysToIds(singleton(EXISTING_CUSTOMER_KEY)) + .toCompletableFuture() + .join(); + assertThat(cache).hasSize(1); + + verify(spyClient, times(1)).execute(any()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingCustomersByKeys_WithEmptyKeys_ShouldReturnEmptySet() { + Set customers = + customerService.fetchMatchingCustomersByKeys(emptySet()).toCompletableFuture().join(); + + assertThat(customers).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } + + @Test + void fetchMatchingCustomersByKeys_WithExistingCustomerKeys_ShouldReturnCustomers() { + Set customers = + customerService + .fetchMatchingCustomersByKeys(singleton(EXISTING_CUSTOMER_KEY)) + .toCompletableFuture() + .join(); + + assertThat(customers).hasSize(1); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } + + @Test + void createCustomer_WithDuplicationException_ShouldNotCreateCustomer() { + CustomerDraft customerDraft = + CustomerDraftBuilder.of("mail@mail.com", "password").key("newKey").build(); + + Optional customerOptional = + customerService.createCustomer(customerDraft).toCompletableFuture().join(); + + assertThat(customerOptional).isEmpty(); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .contains( + "Failed to create draft with key: 'newKey'. Reason: " + + "detailMessage: There is already an existing customer with the email '\"mail@mail.com\"'."); + assertThat(errorCallBackExceptions).hasSize(1); + } + + @Test + void updateCustomer_WithValidChanges_ShouldUpdateCustomerCorrectly() { + final String newEmail = "newMail@newmail.com"; + final ChangeEmail changeEmail = ChangeEmail.of(newEmail); + + final Customer updatedCustomer = + customerService .updateCustomer(customer, singletonList(changeEmail)) - .toCompletableFuture().join(); - assertThat(updatedCustomer).isNotNull(); - - final Optional queried = CTP_TARGET_CLIENT - .execute(CustomerQuery.of() - .withPredicates(QueryPredicate.of(format("key = \"%s\"", EXISTING_CUSTOMER_KEY)))) - .toCompletableFuture().join().head(); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(queried).isNotEmpty(); - final Customer fetchedCustomer = queried.get(); - assertThat(fetchedCustomer.getEmail()).isEqualTo(updatedCustomer.getEmail()); - assertThat(fetchedCustomer.getPassword()).isEqualTo(updatedCustomer.getPassword()); - - } - + .toCompletableFuture() + .join(); + assertThat(updatedCustomer).isNotNull(); + + final Optional queried = + CTP_TARGET_CLIENT + .execute( + CustomerQuery.of() + .withPredicates( + QueryPredicate.of(format("key = \"%s\"", EXISTING_CUSTOMER_KEY)))) + .toCompletableFuture() + .join() + .head(); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(queried).isNotEmpty(); + final Customer fetchedCustomer = queried.get(); + assertThat(fetchedCustomer.getEmail()).isEqualTo(updatedCustomer.getEmail()); + assertThat(fetchedCustomer.getPassword()).isEqualTo(updatedCustomer.getPassword()); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/InventoryServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/InventoryServiceImplIT.java index 6e60a17cae..bf500be3d6 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/InventoryServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/InventoryServiceImplIT.java @@ -1,24 +1,5 @@ package com.commercetools.sync.integration.services.impl; -import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; -import com.commercetools.sync.services.InventoryService; -import com.commercetools.sync.services.impl.InventoryServiceImpl; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.inventory.InventoryEntry; -import io.sphere.sdk.inventory.InventoryEntryDraft; -import io.sphere.sdk.inventory.InventoryEntryDraftBuilder; -import io.sphere.sdk.inventory.commands.updateactions.ChangeQuantity; -import io.sphere.sdk.inventory.commands.updateactions.SetExpectedDelivery; -import io.sphere.sdk.inventory.commands.updateactions.SetRestockableInDays; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - import static com.commercetools.sync.integration.commons.utils.ChannelITUtils.deleteChannelsFromTargetAndSource; import static com.commercetools.sync.integration.commons.utils.ITUtils.deleteTypesFromTargetAndSource; import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; @@ -37,117 +18,133 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; -class InventoryServiceImplIT { +import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; +import com.commercetools.sync.services.InventoryService; +import com.commercetools.sync.services.impl.InventoryServiceImpl; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.inventory.InventoryEntry; +import io.sphere.sdk.inventory.InventoryEntryDraft; +import io.sphere.sdk.inventory.InventoryEntryDraftBuilder; +import io.sphere.sdk.inventory.commands.updateactions.ChangeQuantity; +import io.sphere.sdk.inventory.commands.updateactions.SetExpectedDelivery; +import io.sphere.sdk.inventory.commands.updateactions.SetRestockableInDays; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; - private InventoryService inventoryService; - - /** - * Deletes inventories and supply channels from source and target CTP projects. - * Populates target CTP project with test data. - */ - @BeforeEach - void setup() { - deleteInventoryEntriesFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - deleteChannelsFromTargetAndSource(); - populateTargetProject(); - inventoryService = new InventoryServiceImpl(InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build()); - } - - /** - * Cleans up the target and source test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteInventoryEntriesFromTargetAndSource(); - deleteTypesFromTargetAndSource(); - deleteChannelsFromTargetAndSource(); - } - - @Test - void fetchInventoryEntriesBySku_ShouldReturnCorrectInventoryEntriesWithoutReferenceExpansion() { - // preparation - final Set skus = singleton(SKU_1); - - // test - final Set result = inventoryService - .fetchInventoryEntriesBySkus(skus) - .toCompletableFuture() - .join(); +class InventoryServiceImplIT { - // assertion - assertThat(result).isNotNull(); - assertThat(result).hasSize(2); - - //assert SKU is correct - result.stream() - .map(InventoryEntry::getSku) - .forEach(sku -> assertThat(sku).isEqualTo(SKU_1)); - - //assert references are not expanded - result.stream() - .filter(inventoryEntry -> inventoryEntry.getSupplyChannel() != null) - .map(InventoryEntry::getSupplyChannel) - .forEach(supplyChannel -> assertThat(supplyChannel.getObj()).isNull()); - } - - @Test - void createInventoryEntry_ShouldCreateCorrectInventoryEntry() { - //prepare draft and create - final InventoryEntryDraft inventoryEntryDraft = InventoryEntryDraftBuilder - .of(SKU_2, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, null) + private InventoryService inventoryService; + + /** + * Deletes inventories and supply channels from source and target CTP projects. Populates target + * CTP project with test data. + */ + @BeforeEach + void setup() { + deleteInventoryEntriesFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + deleteChannelsFromTargetAndSource(); + populateTargetProject(); + inventoryService = + new InventoryServiceImpl(InventorySyncOptionsBuilder.of(CTP_TARGET_CLIENT).build()); + } + + /** Cleans up the target and source test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteInventoryEntriesFromTargetAndSource(); + deleteTypesFromTargetAndSource(); + deleteChannelsFromTargetAndSource(); + } + + @Test + void fetchInventoryEntriesBySku_ShouldReturnCorrectInventoryEntriesWithoutReferenceExpansion() { + // preparation + final Set skus = singleton(SKU_1); + + // test + final Set result = + inventoryService.fetchInventoryEntriesBySkus(skus).toCompletableFuture().join(); + + // assertion + assertThat(result).isNotNull(); + assertThat(result).hasSize(2); + + // assert SKU is correct + result.stream().map(InventoryEntry::getSku).forEach(sku -> assertThat(sku).isEqualTo(SKU_1)); + + // assert references are not expanded + result.stream() + .filter(inventoryEntry -> inventoryEntry.getSupplyChannel() != null) + .map(InventoryEntry::getSupplyChannel) + .forEach(supplyChannel -> assertThat(supplyChannel.getObj()).isNull()); + } + + @Test + void createInventoryEntry_ShouldCreateCorrectInventoryEntry() { + // prepare draft and create + final InventoryEntryDraft inventoryEntryDraft = + InventoryEntryDraftBuilder.of( + SKU_2, QUANTITY_ON_STOCK_2, EXPECTED_DELIVERY_2, RESTOCKABLE_IN_DAYS_2, null) .build(); - final Optional result = inventoryService - .createInventoryEntry(inventoryEntryDraft) + final Optional result = + inventoryService.createInventoryEntry(inventoryEntryDraft).toCompletableFuture().join(); + + // assertions + assertThat(result) + .hasValueSatisfying( + inventoryEntry -> { + assertThat(inventoryEntry.getQuantityOnStock()).isEqualTo(QUANTITY_ON_STOCK_2); + assertThat(inventoryEntry.getRestockableInDays()).isEqualTo(RESTOCKABLE_IN_DAYS_2); + assertThat(inventoryEntry.getExpectedDelivery()).isEqualTo(EXPECTED_DELIVERY_2); + }); + + // assert CTP state + final Optional updatedInventoryEntry = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_2, null); + assertThat(updatedInventoryEntry).isEqualTo(result); + } + + @Test + void updateInventoryEntry_ShouldUpdateInventoryEntry() { + // fetch existing inventory entry and assert its state + final Optional existingEntryOptional = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); + assertThat(existingEntryOptional).isNotEmpty(); + + final InventoryEntry existingEntry = existingEntryOptional.get(); + assertThat(existingEntry.getQuantityOnStock()).isEqualTo(QUANTITY_ON_STOCK_1); + assertThat(existingEntry.getRestockableInDays()).isEqualTo(RESTOCKABLE_IN_DAYS_1); + assertThat(existingEntry.getExpectedDelivery()).isEqualTo(EXPECTED_DELIVERY_1); + + // build update actions and do update + final List> updateActions = + Stream.of( + ChangeQuantity.of(QUANTITY_ON_STOCK_2), + SetExpectedDelivery.of(EXPECTED_DELIVERY_2), + SetRestockableInDays.of(RESTOCKABLE_IN_DAYS_2)) + .collect(toList()); + + final InventoryEntry result = + inventoryService + .updateInventoryEntry(existingEntry, updateActions) .toCompletableFuture() .join(); - - // assertions - assertThat(result).hasValueSatisfying(inventoryEntry -> { - assertThat(inventoryEntry.getQuantityOnStock()).isEqualTo(QUANTITY_ON_STOCK_2); - assertThat(inventoryEntry.getRestockableInDays()).isEqualTo(RESTOCKABLE_IN_DAYS_2); - assertThat(inventoryEntry.getExpectedDelivery()).isEqualTo(EXPECTED_DELIVERY_2); - }); - - - // assert CTP state - final Optional updatedInventoryEntry = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_2, null); - assertThat(updatedInventoryEntry).isEqualTo(result); - } - - @Test - void updateInventoryEntry_ShouldUpdateInventoryEntry() { - //fetch existing inventory entry and assert its state - final Optional existingEntryOptional = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); - assertThat(existingEntryOptional).isNotEmpty(); - - final InventoryEntry existingEntry = existingEntryOptional.get(); - assertThat(existingEntry.getQuantityOnStock()).isEqualTo(QUANTITY_ON_STOCK_1); - assertThat(existingEntry.getRestockableInDays()).isEqualTo(RESTOCKABLE_IN_DAYS_1); - assertThat(existingEntry.getExpectedDelivery()).isEqualTo(EXPECTED_DELIVERY_1); - - //build update actions and do update - final List> updateActions = Stream.of( - ChangeQuantity.of(QUANTITY_ON_STOCK_2), - SetExpectedDelivery.of(EXPECTED_DELIVERY_2), - SetRestockableInDays.of(RESTOCKABLE_IN_DAYS_2) - ).collect(toList()); - - final InventoryEntry result = inventoryService.updateInventoryEntry(existingEntry, updateActions) - .toCompletableFuture() - .join(); - assertThat(result).isNotNull(); - assertThat(result.getQuantityOnStock()).isEqualTo(QUANTITY_ON_STOCK_2); - assertThat(result.getRestockableInDays()).isEqualTo(RESTOCKABLE_IN_DAYS_2); - assertThat(result.getExpectedDelivery()).isEqualTo(EXPECTED_DELIVERY_2); - - //assert CTP state - final Optional updatedInventoryEntry = - getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); - assertThat(updatedInventoryEntry).isNotEmpty(); - assertThat(updatedInventoryEntry.get()).isEqualTo(result); - } + assertThat(result).isNotNull(); + assertThat(result.getQuantityOnStock()).isEqualTo(QUANTITY_ON_STOCK_2); + assertThat(result.getRestockableInDays()).isEqualTo(RESTOCKABLE_IN_DAYS_2); + assertThat(result.getExpectedDelivery()).isEqualTo(EXPECTED_DELIVERY_2); + + // assert CTP state + final Optional updatedInventoryEntry = + getInventoryEntryBySkuAndSupplyChannel(CTP_TARGET_CLIENT, SKU_1, null); + assertThat(updatedInventoryEntry).isNotEmpty(); + assertThat(updatedInventoryEntry.get()).isEqualTo(result); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/ProductServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/ProductServiceImplIT.java index 190d176c1b..638cf1d981 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/ProductServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/ProductServiceImplIT.java @@ -1,5 +1,33 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategories; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCategoryDrafts; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getReferencesWithIds; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; +import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_2_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraft; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; +import static com.commercetools.sync.products.ProductSyncMockUtils.createRandomCategoryOrderHints; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.services.ProductService; @@ -27,12 +55,6 @@ import io.sphere.sdk.producttypes.queries.ProductTypeQuery; import io.sphere.sdk.queries.QueryPredicate; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -45,366 +67,373 @@ import java.util.concurrent.CompletionException; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_KEY; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.OLD_CATEGORY_CUSTOM_TYPE_NAME; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategories; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getCategoryDrafts; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.getReferencesWithIds; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteAllProducts; -import static com.commercetools.sync.integration.commons.utils.ProductITUtils.deleteProductSyncTestData; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_2_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraft; -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; -import static com.commercetools.sync.products.ProductSyncMockUtils.createRandomCategoryOrderHints; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductServiceImplIT { - private ProductService productService; - private static ProductType productType; - private static List> categoryReferencesWithIds; - private Product product; - - - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - - /** - * Delete all product related test data from target project. Then creates custom types for target CTP project - * categories. - */ - @BeforeAll - static void setup() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_CATEGORY_CUSTOM_TYPE_KEY, Locale.ENGLISH, - OLD_CATEGORY_CUSTOM_TYPE_NAME, CTP_TARGET_CLIENT); - final List categories = createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); - categoryReferencesWithIds = getReferencesWithIds(categories); - productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - } - - /** - * Deletes Products and Types from target CTP projects, then it populates target CTP project with product test - * data. - */ - @BeforeEach - void setupTest() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - deleteAllProducts(CTP_TARGET_CLIENT); - - final ProductSyncOptions productSyncOptions = - ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback( - (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback( - (exception, oldResource, newResource) -> warningCallBackMessages - .add(exception.getMessage())) - .build(); - - // Create a mock new product in the target project. - final ProductDraft productDraft = createProductDraft(PRODUCT_KEY_1_RESOURCE_PATH, - productType.toReference(), null, null, categoryReferencesWithIds, + private ProductService productService; + private static ProductType productType; + private static List> categoryReferencesWithIds; + private Product product; + + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + /** + * Delete all product related test data from target project. Then creates custom types for target + * CTP project categories. + */ + @BeforeAll + static void setup() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + createCategoriesCustomType( + OLD_CATEGORY_CUSTOM_TYPE_KEY, + Locale.ENGLISH, + OLD_CATEGORY_CUSTOM_TYPE_NAME, + CTP_TARGET_CLIENT); + final List categories = + createCategories(CTP_TARGET_CLIENT, getCategoryDrafts(null, 2)); + categoryReferencesWithIds = getReferencesWithIds(categories); + productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + } + + /** + * Deletes Products and Types from target CTP projects, then it populates target CTP project with + * product test data. + */ + @BeforeEach + void setupTest() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + deleteAllProducts(CTP_TARGET_CLIENT); + + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .build(); + + // Create a mock new product in the target project. + final ProductDraft productDraft = + createProductDraft( + PRODUCT_KEY_1_RESOURCE_PATH, + productType.toReference(), + null, + null, + categoryReferencesWithIds, createRandomCategoryOrderHints(categoryReferencesWithIds)); - product = CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft)) - .toCompletableFuture().join(); - - productService = new ProductServiceImpl(productSyncOptions); - } - - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteProductSyncTestData(CTP_TARGET_CLIENT); - } - - @Test - void getIdFromCacheOrFetch_WithNotCachedExistingProduct_ShouldFetchProduct() { - final Optional productId = productService.getIdFromCacheOrFetch(product.getKey()) - .toCompletableFuture() - .join(); - assertThat(productId).isNotEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void getIdFromCacheOrFetch_WithNullProductKey_ShouldReturnEmptyOptional() { - final Optional productId = productService.getIdFromCacheOrFetch(null) - .toCompletableFuture() - .join(); - assertThat(productId).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void getIdFromCacheOrFetch_WithCachedExistingProduct_ShouldFetchFromCache() { - final String oldKey = product.getKey(); - final Optional oldProductId = productService.getIdFromCacheOrFetch(oldKey) - .toCompletableFuture() - .join(); - - // Change product key on ctp - final String newKey = "newKey"; - productService.updateProduct(product, Collections.singletonList(SetKey.of(newKey))) - .toCompletableFuture() - .join(); - - // Fetch product from cache - final Optional cachedProductId = productService.getIdFromCacheOrFetch(oldKey) - .toCompletableFuture().join(); - - assertThat(cachedProductId).isNotEmpty(); - assertThat(cachedProductId).isEqualTo(oldProductId); - - // Fetch product from ctp (because of new key not cached) - final Optional productId = productService.getIdFromCacheOrFetch(newKey) - .toCompletableFuture().join(); - - assertThat(productId).isNotEmpty(); - // Both keys point to the same id. - assertThat(productId).isEqualTo(cachedProductId); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void cacheKeysToIds_WithEmptyKeys_ShouldReturnCurrentCache() { - Map cache = productService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); - assertThat(cache).hasSize(0); // Since cache is empty - - cache = productService.cacheKeysToIds(singleton(product.getKey())).toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - cache = productService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); - assertThat(cache).hasSize(1); // Since cache has been fed with a product key - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void cacheKeysToIds_WithAlreadyCachedKeys_ShouldNotMakeRequestsAndReturnCurrentCache() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final ProductSyncOptions productSyncOptions = - ProductSyncOptionsBuilder.of(spyClient) - .errorCallback( - (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback( - (exception, oldResource, newResource) - -> warningCallBackMessages.add(exception.getMessage())) - .build(); - final ProductService spyProductService = new ProductServiceImpl(productSyncOptions); - - final ProductDraft productDraft1 = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - productType.toReference()) + product = + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraft)) + .toCompletableFuture() + .join(); + + productService = new ProductServiceImpl(productSyncOptions); + } + + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteProductSyncTestData(CTP_TARGET_CLIENT); + } + + @Test + void getIdFromCacheOrFetch_WithNotCachedExistingProduct_ShouldFetchProduct() { + final Optional productId = + productService.getIdFromCacheOrFetch(product.getKey()).toCompletableFuture().join(); + assertThat(productId).isNotEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void getIdFromCacheOrFetch_WithNullProductKey_ShouldReturnEmptyOptional() { + final Optional productId = + productService.getIdFromCacheOrFetch(null).toCompletableFuture().join(); + assertThat(productId).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void getIdFromCacheOrFetch_WithCachedExistingProduct_ShouldFetchFromCache() { + final String oldKey = product.getKey(); + final Optional oldProductId = + productService.getIdFromCacheOrFetch(oldKey).toCompletableFuture().join(); + + // Change product key on ctp + final String newKey = "newKey"; + productService + .updateProduct(product, Collections.singletonList(SetKey.of(newKey))) + .toCompletableFuture() + .join(); + + // Fetch product from cache + final Optional cachedProductId = + productService.getIdFromCacheOrFetch(oldKey).toCompletableFuture().join(); + + assertThat(cachedProductId).isNotEmpty(); + assertThat(cachedProductId).isEqualTo(oldProductId); + + // Fetch product from ctp (because of new key not cached) + final Optional productId = + productService.getIdFromCacheOrFetch(newKey).toCompletableFuture().join(); + + assertThat(productId).isNotEmpty(); + // Both keys point to the same id. + assertThat(productId).isEqualTo(cachedProductId); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void cacheKeysToIds_WithEmptyKeys_ShouldReturnCurrentCache() { + Map cache = + productService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); + assertThat(cache).hasSize(0); // Since cache is empty + + cache = productService.cacheKeysToIds(singleton(product.getKey())).toCompletableFuture().join(); + assertThat(cache).hasSize(1); + + cache = productService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); + assertThat(cache).hasSize(1); // Since cache has been fed with a product key + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void cacheKeysToIds_WithAlreadyCachedKeys_ShouldNotMakeRequestsAndReturnCurrentCache() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .build(); + final ProductService spyProductService = new ProductServiceImpl(productSyncOptions); + + final ProductDraft productDraft1 = + createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, productType.toReference()) .categories(emptyList()) .taxCategory(null) .state(null) .categoryOrderHints(null) .build(); - Product product2 = - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft1)).toCompletableFuture().join(); - - Set keys = Arrays.asList(product.getKey(), product2.getKey()).stream().collect(Collectors.toSet()); - Map cache = spyProductService.cacheKeysToIds(keys).toCompletableFuture().join(); - assertThat(cache).hasSize(2); - - // Attempt to cache same (already cached) key. - cache = spyProductService.cacheKeysToIds(singleton(product.getKey())) - .toCompletableFuture().join(); - assertThat(cache).hasSize(2); - assertThat(cache).containsKeys(product.getKey(), product2.getKey()); - - // verify only 1 request was made to fetch id the first time, but not second time since it's already in cache. - verify(spyClient, times(1)).execute(any()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void cacheKeysToIds_WithSomeEmptyKeys_ShouldReturnCorrectCache() { - final Set productKeys = new HashSet<>(); - productKeys.add(product.getKey()); - productKeys.add(null); - productKeys.add(""); - Map cache = productService.cacheKeysToIds(productKeys) - .toCompletableFuture().join(); - assertThat(cache).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingProductsByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { - final Set fetchedProducts = productService.fetchMatchingProductsByKeys(Collections.emptySet()) - .toCompletableFuture().join(); - assertThat(fetchedProducts).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingProductsByKeys_WithAllExistingSetOfKeys_ShouldReturnSetOfProducts() { - final Set fetchedProducts = productService.fetchMatchingProductsByKeys(singleton(product.getKey())) - .toCompletableFuture().join(); - assertThat(fetchedProducts).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingProductsByKeys_WithBadGateWayExceptionAlways_ShouldFail() { - // preparation - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(ProductQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - final ProductSyncOptions spyOptions = ProductSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + Product product2 = + CTP_TARGET_CLIENT + .execute(ProductCreateCommand.of(productDraft1)) + .toCompletableFuture() + .join(); + + Set keys = + Arrays.asList(product.getKey(), product2.getKey()).stream().collect(Collectors.toSet()); + Map cache = spyProductService.cacheKeysToIds(keys).toCompletableFuture().join(); + assertThat(cache).hasSize(2); + + // Attempt to cache same (already cached) key. + cache = + spyProductService.cacheKeysToIds(singleton(product.getKey())).toCompletableFuture().join(); + assertThat(cache).hasSize(2); + assertThat(cache).containsKeys(product.getKey(), product2.getKey()); + + // verify only 1 request was made to fetch id the first time, but not second time since it's + // already in cache. + verify(spyClient, times(1)).execute(any()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void cacheKeysToIds_WithSomeEmptyKeys_ShouldReturnCorrectCache() { + final Set productKeys = new HashSet<>(); + productKeys.add(product.getKey()); + productKeys.add(null); + productKeys.add(""); + Map cache = + productService.cacheKeysToIds(productKeys).toCompletableFuture().join(); + assertThat(cache).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingProductsByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { + final Set fetchedProducts = + productService + .fetchMatchingProductsByKeys(Collections.emptySet()) + .toCompletableFuture() + .join(); + assertThat(fetchedProducts).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingProductsByKeys_WithAllExistingSetOfKeys_ShouldReturnSetOfProducts() { + final Set fetchedProducts = + productService + .fetchMatchingProductsByKeys(singleton(product.getKey())) + .toCompletableFuture() + .join(); + assertThat(fetchedProducts).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingProductsByKeys_WithBadGateWayExceptionAlways_ShouldFail() { + // preparation + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(ProductQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + final ProductSyncOptions spyOptions = + ProductSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final ProductService spyProductService = new ProductServiceImpl(spyOptions); - - - final Set keys = new HashSet<>(); - keys.add(product.getKey()); - - // test and assert - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyProductService.fetchMatchingProductsByKeys(keys)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } - - @Test - void fetchMatchingProductsByKeys_WithSomeExistingSetOfKeys_ShouldReturnSetOfProducts() { - final Set keys = new HashSet<>(); - keys.add(product.getKey()); - keys.add("new-key"); - final Set fetchedProducts = productService.fetchMatchingProductsByKeys(keys) - .toCompletableFuture().join(); - assertThat(fetchedProducts).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingProductsByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedProductsIds() { - final String oldKey = product.getKey(); - final Set fetchedProducts = productService.fetchMatchingProductsByKeys(singleton(oldKey)) - .toCompletableFuture().join(); - assertThat(fetchedProducts).hasSize(1); - - // Change product oldKey on ctp - final String newKey = "newKey"; - productService.updateProduct(product, Collections.singletonList(SetKey.of(newKey))) - .toCompletableFuture() - .join(); - - // Fetch cached id by old key - final Optional cachedProductId = productService.getIdFromCacheOrFetch(oldKey) - .toCompletableFuture().join(); - - assertThat(cachedProductId).isNotEmpty(); - assertThat(cachedProductId).contains(product.getId()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void createProduct_WithValidProduct_ShouldCreateProductAndCacheId() { - // preparation - final ProductDraft productDraft1 = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - productType.toReference()) + final ProductService spyProductService = new ProductServiceImpl(spyOptions); + + final Set keys = new HashSet<>(); + keys.add(product.getKey()); + + // test and assert + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyProductService.fetchMatchingProductsByKeys(keys)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } + + @Test + void fetchMatchingProductsByKeys_WithSomeExistingSetOfKeys_ShouldReturnSetOfProducts() { + final Set keys = new HashSet<>(); + keys.add(product.getKey()); + keys.add("new-key"); + final Set fetchedProducts = + productService.fetchMatchingProductsByKeys(keys).toCompletableFuture().join(); + assertThat(fetchedProducts).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingProductsByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedProductsIds() { + final String oldKey = product.getKey(); + final Set fetchedProducts = + productService.fetchMatchingProductsByKeys(singleton(oldKey)).toCompletableFuture().join(); + assertThat(fetchedProducts).hasSize(1); + + // Change product oldKey on ctp + final String newKey = "newKey"; + productService + .updateProduct(product, Collections.singletonList(SetKey.of(newKey))) + .toCompletableFuture() + .join(); + + // Fetch cached id by old key + final Optional cachedProductId = + productService.getIdFromCacheOrFetch(oldKey).toCompletableFuture().join(); + + assertThat(cachedProductId).isNotEmpty(); + assertThat(cachedProductId).contains(product.getId()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void createProduct_WithValidProduct_ShouldCreateProductAndCacheId() { + // preparation + final ProductDraft productDraft1 = + createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, productType.toReference()) .taxCategory(null) .state(null) .categories(emptyList()) .categoryOrderHints(null) .build(); - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final ProductSyncOptions spyOptions = ProductSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final ProductSyncOptions spyOptions = + ProductSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final ProductService spyProductService = new ProductServiceImpl(spyOptions); - - // test - final Optional createdProductOptional = spyProductService - .createProduct(productDraft1) - .toCompletableFuture().join(); - - // assertion - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - - //assert CTP state - final Optional queriedOptional = CTP_TARGET_CLIENT - .execute(ProductQuery.of() - .withPredicates(QueryPredicate.of(format("key = \"%s\"", productDraft1.getKey())))) - .toCompletableFuture().join().head(); - - assertThat(queriedOptional) - .hasValueSatisfying(queried -> assertThat(createdProductOptional) - .hasValueSatisfying(created -> { - assertThat(queried.getKey()).isEqualTo(created.getKey()); - assertThat(queried.getMasterData().getCurrent().getName()) - .isEqualTo(created.getMasterData().getCurrent().getName()); - assertThat(queried.getMasterData().getCurrent().getSlug()) - .isEqualTo(created.getMasterData().getCurrent().getSlug()); - })); - - // Assert that the created product is cached - final Optional productId = - spyProductService.getIdFromCacheOrFetch(productDraft1.getKey()).toCompletableFuture().join(); - assertThat(productId).isPresent(); - verify(spyClient, times(0)).execute(any(ProductTypeQuery.class)); - } - - @Test - void createProduct_WithBlankKey_ShouldNotCreateProduct() { - // preparation - final String newKey = ""; - final ProductDraft productDraft1 = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - productType.toReference()) + final ProductService spyProductService = new ProductServiceImpl(spyOptions); + + // test + final Optional createdProductOptional = + spyProductService.createProduct(productDraft1).toCompletableFuture().join(); + + // assertion + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + + // assert CTP state + final Optional queriedOptional = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of() + .withPredicates( + QueryPredicate.of(format("key = \"%s\"", productDraft1.getKey())))) + .toCompletableFuture() + .join() + .head(); + + assertThat(queriedOptional) + .hasValueSatisfying( + queried -> + assertThat(createdProductOptional) + .hasValueSatisfying( + created -> { + assertThat(queried.getKey()).isEqualTo(created.getKey()); + assertThat(queried.getMasterData().getCurrent().getName()) + .isEqualTo(created.getMasterData().getCurrent().getName()); + assertThat(queried.getMasterData().getCurrent().getSlug()) + .isEqualTo(created.getMasterData().getCurrent().getSlug()); + })); + + // Assert that the created product is cached + final Optional productId = + spyProductService + .getIdFromCacheOrFetch(productDraft1.getKey()) + .toCompletableFuture() + .join(); + assertThat(productId).isPresent(); + verify(spyClient, times(0)).execute(any(ProductTypeQuery.class)); + } + + @Test + void createProduct_WithBlankKey_ShouldNotCreateProduct() { + // preparation + final String newKey = ""; + final ProductDraft productDraft1 = + createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, productType.toReference()) .key(newKey) .taxCategory(null) .state(null) @@ -413,23 +442,22 @@ void createProduct_WithBlankKey_ShouldNotCreateProduct() { .masterVariant(ProductVariantDraftBuilder.of().build()) .build(); - // test - final Optional createdProductOptional = productService - .createProduct(productDraft1) - .toCompletableFuture().join(); - - // assertion - assertThat(createdProductOptional).isEmpty(); - assertThat(errorCallBackMessages) - .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); - } - - @Test - void createProduct_WithDuplicateSlug_ShouldNotCreateProduct() { - // Create product with same slug as existing product - final String newKey = "newKey"; - final ProductDraft productDraft1 = createProductDraftBuilder(PRODUCT_KEY_1_RESOURCE_PATH, - productType.toReference()) + // test + final Optional createdProductOptional = + productService.createProduct(productDraft1).toCompletableFuture().join(); + + // assertion + assertThat(createdProductOptional).isEmpty(); + assertThat(errorCallBackMessages) + .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); + } + + @Test + void createProduct_WithDuplicateSlug_ShouldNotCreateProduct() { + // Create product with same slug as existing product + final String newKey = "newKey"; + final ProductDraft productDraft1 = + createProductDraftBuilder(PRODUCT_KEY_1_RESOURCE_PATH, productType.toReference()) .key(newKey) .taxCategory(null) .state(null) @@ -438,271 +466,296 @@ void createProduct_WithDuplicateSlug_ShouldNotCreateProduct() { .masterVariant(ProductVariantDraftBuilder.of().build()) .build(); - final Optional createdProductOptional = productService - .createProduct(productDraft1) - .toCompletableFuture().join(); - - assertThat(createdProductOptional).isEmpty(); - final String duplicatedSlug = "english-slug"; - assertThat(errorCallBackExceptions) - .hasSize(1) - .allSatisfy(exception -> { - assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponse = ((ErrorResponseException)exception); - - final List fieldErrors = errorResponse - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - assertThat(fieldErrors).allSatisfy(error -> { - assertThat(error.getField()).isEqualTo("slug.en"); - assertThat(error.getDuplicateValue()).isEqualTo(duplicatedSlug); - }); + final Optional createdProductOptional = + productService.createProduct(productDraft1).toCompletableFuture().join(); + + assertThat(createdProductOptional).isEmpty(); + final String duplicatedSlug = "english-slug"; + assertThat(errorCallBackExceptions) + .hasSize(1) + .allSatisfy( + exception -> { + assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponse = ((ErrorResponseException) exception); + + final List fieldErrors = + errorResponse.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + assertThat(fieldErrors) + .allSatisfy( + error -> { + assertThat(error.getField()).isEqualTo("slug.en"); + assertThat(error.getDuplicateValue()).isEqualTo(duplicatedSlug); + }); }); - assertThat(errorCallBackMessages) - .hasSize(1) - .allSatisfy(errorMessage -> { - assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); - assertThat(errorMessage).contains("\"field\" : \"slug.en\""); - assertThat(errorMessage).contains("\"duplicateValue\" : \"" + duplicatedSlug + "\""); + assertThat(errorCallBackMessages) + .hasSize(1) + .allSatisfy( + errorMessage -> { + assertThat(errorMessage).contains("\"code\" : \"DuplicateField\""); + assertThat(errorMessage).contains("\"field\" : \"slug.en\""); + assertThat(errorMessage).contains("\"duplicateValue\" : \"" + duplicatedSlug + "\""); }); - - //assert CTP state - final Optional productOptional = CTP_TARGET_CLIENT - .execute(ProductQuery.of() - .withPredicates(QueryPredicate.of(format("key = \"%s\"", newKey)))) - .toCompletableFuture().join().head(); - assertThat(productOptional).isEmpty(); - } - - @Test - @SuppressWarnings("ConstantConditions") - void updateProduct_WithValidChanges_ShouldUpdateProductCorrectly() { - final String newProductName = "This is my new name!"; - final ChangeName changeNameUpdateAction = ChangeName - .of(LocalizedString.of(Locale.GERMAN, newProductName)); - - final Product updatedProduct = productService + // assert CTP state + final Optional productOptional = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of().withPredicates(QueryPredicate.of(format("key = \"%s\"", newKey)))) + .toCompletableFuture() + .join() + .head(); + assertThat(productOptional).isEmpty(); + } + + @Test + @SuppressWarnings("ConstantConditions") + void updateProduct_WithValidChanges_ShouldUpdateProductCorrectly() { + final String newProductName = "This is my new name!"; + final ChangeName changeNameUpdateAction = + ChangeName.of(LocalizedString.of(Locale.GERMAN, newProductName)); + + final Product updatedProduct = + productService .updateProduct(product, Collections.singletonList(changeNameUpdateAction)) - .toCompletableFuture().join(); - assertThat(updatedProduct).isNotNull(); - - //assert CTP state - final Optional fetchedProductOptional = CTP_TARGET_CLIENT - .execute(ProductQuery.of() - .withPredicates(QueryPredicate.of(format("key = \"%s\"", product.getKey())))) - .toCompletableFuture().join().head(); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(fetchedProductOptional).isNotEmpty(); - final Product fetchedProduct = fetchedProductOptional.get(); - assertThat(fetchedProduct.getMasterData().getCurrent().getName()) - .isEqualTo(updatedProduct.getMasterData().getCurrent().getName()); - assertThat(fetchedProduct.getMasterData().getCurrent().getSlug()) - .isEqualTo(updatedProduct.getMasterData().getCurrent().getSlug()); - assertThat(fetchedProduct.getKey()).isEqualTo(updatedProduct.getKey()); - } - - @Test - @SuppressWarnings("ConstantConditions") - void updateProduct_WithInvalidChanges_ShouldNotUpdateProduct() { - final ProductDraft productDraft1 = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - productType.toReference()) + .toCompletableFuture() + .join(); + assertThat(updatedProduct).isNotNull(); + + // assert CTP state + final Optional fetchedProductOptional = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of() + .withPredicates(QueryPredicate.of(format("key = \"%s\"", product.getKey())))) + .toCompletableFuture() + .join() + .head(); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(fetchedProductOptional).isNotEmpty(); + final Product fetchedProduct = fetchedProductOptional.get(); + assertThat(fetchedProduct.getMasterData().getCurrent().getName()) + .isEqualTo(updatedProduct.getMasterData().getCurrent().getName()); + assertThat(fetchedProduct.getMasterData().getCurrent().getSlug()) + .isEqualTo(updatedProduct.getMasterData().getCurrent().getSlug()); + assertThat(fetchedProduct.getKey()).isEqualTo(updatedProduct.getKey()); + } + + @Test + @SuppressWarnings("ConstantConditions") + void updateProduct_WithInvalidChanges_ShouldNotUpdateProduct() { + final ProductDraft productDraft1 = + createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, productType.toReference()) .categories(emptyList()) .taxCategory(null) .state(null) .categoryOrderHints(null) .build(); - CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft1)).toCompletableFuture().join(); - + CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft1)).toCompletableFuture().join(); + + final ChangeSlug changeSlugUpdateAction = ChangeSlug.of(productDraft1.getSlug()); + + productService + .updateProduct(product, Collections.singletonList(changeSlugUpdateAction)) + .exceptionally( + exception -> { + assertThat(exception).isNotNull(); + + assertThat(exception).isExactlyInstanceOf(CompletionException.class); + assertThat(exception.getCause()).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponse = + ((ErrorResponseException) exception.getCause()); + + final List fieldErrors = + errorResponse.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + assertThat(fieldErrors) + .allSatisfy( + error -> { + assertThat(error.getField()).isEqualTo("slug.en"); + assertThat(error.getDuplicateValue()) + .isEqualTo(productDraft1.getSlug().get(Locale.ENGLISH)); + }); + return null; + }) + .toCompletableFuture() + .join(); + + // assert CTP state + final Optional fetchedProductOptional = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of() + .withPredicates(QueryPredicate.of(format("key = \"%s\"", product.getKey())))) + .toCompletableFuture() + .join() + .head(); + + assertThat(fetchedProductOptional).isNotEmpty(); + final Product fetchedProduct = fetchedProductOptional.get(); + assertThat(fetchedProduct.getMasterData().getCurrent().getSlug()) + .isNotEqualTo(productDraft1.getSlug()); + } + + @Test + @SuppressWarnings("ConstantConditions") + void updateProduct_WithMoreThan500Actions_ShouldNotFail() { + // Update the product 501 times with a different name every time. + final int numberOfUpdateActions = 501; + final List> updateActions = + IntStream.range(1, numberOfUpdateActions + 1) + .mapToObj(i -> ChangeName.of(LocalizedString.of(Locale.GERMAN, format("name:%s", i)))) + .collect(Collectors.toList()); + + final Product updatedProduct = + productService.updateProduct(product, updateActions).toCompletableFuture().join(); + assertThat(updatedProduct).isNotNull(); + + // assert CTP state + final Optional fetchedProductOptional = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of() + .withPredicates(QueryPredicate.of(format("key = \"%s\"", product.getKey())))) + .toCompletableFuture() + .join() + .head(); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(fetchedProductOptional).isNotEmpty(); + final Product fetchedProduct = fetchedProductOptional.get(); + + // Test that the fetched product has the name of the last update action that was applied. + assertThat(fetchedProduct.getMasterData().getStaged().getName()) + .isEqualTo(LocalizedString.of(Locale.GERMAN, format("name:%s", numberOfUpdateActions))); + } + + @Test + @SuppressWarnings("ConstantConditions") + void updateProduct_WithMoreThan500ImageAdditions_ShouldHaveAllNewImages() { + final Integer productMasterVariantId = + product.getMasterData().getStaged().getMasterVariant().getId(); + + // Update the product by adding 600 images in separate update actions + final int numberOfImages = 600; + final List addedImages = new ArrayList<>(); + final List> updateActions = + IntStream.range(1, numberOfImages + 1) + .mapToObj( + i -> { + final Image newExternalImage = + Image.of(format("image#%s", i), ImageDimensions.of(10, 10)); + addedImages.add(newExternalImage); // keep track of added images. + return AddExternalImage.of(newExternalImage, productMasterVariantId); + }) + .collect(Collectors.toList()); + + final Product updatedProduct = + productService.updateProduct(product, updateActions).toCompletableFuture().join(); + assertThat(updatedProduct).isNotNull(); + + // assert CTP state + final Optional fetchedProductOptional = + CTP_TARGET_CLIENT + .execute( + ProductQuery.of() + .withPredicates(QueryPredicate.of(format("key = \"%s\"", product.getKey())))) + .toCompletableFuture() + .join() + .head(); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(fetchedProductOptional).isNotEmpty(); + + final Product fetchedProduct = fetchedProductOptional.get(); + // Test that the fetched product has exactly the 600 images added before. + final List currentMasterVariantImages = + fetchedProduct.getMasterData().getStaged().getMasterVariant().getImages(); + assertThat(currentMasterVariantImages).containsAll(addedImages); + } + + @Test + void fetchProduct_WithExistingKey_ShouldReturnProduct() { + final Optional fetchedProductOptional = + productService.fetchProduct(product.getKey()).toCompletableFuture().join(); + assertThat(fetchedProductOptional).isNotEmpty(); + assertThat(fetchedProductOptional).contains(product); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchProduct_WithNonExistingKey_ShouldNotReturnProduct() { + final Optional fetchedProductOptional = + productService.fetchProduct("someNonExistingKey").toCompletableFuture().join(); + assertThat(fetchedProductOptional).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchProduct_WithNullKey_ShouldNotReturnProduct() { + final Optional fetchedProductOptional = + productService.fetchProduct(null).toCompletableFuture().join(); + assertThat(fetchedProductOptional).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchProduct_WithBlankKey_ShouldNotReturnProduct() { + final Optional fetchedProductOptional = + productService.fetchProduct(StringUtils.EMPTY).toCompletableFuture().join(); + assertThat(fetchedProductOptional).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchProduct_WithBadGatewayException_ShouldFail() { + // preparation + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(ProductQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + final ProductSyncOptions spyOptions = + ProductSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, oldResource, newResource) -> + warningCallBackMessages.add(exception.getMessage())) + .build(); + final ProductService spyProductService = new ProductServiceImpl(spyOptions); - final ChangeSlug changeSlugUpdateAction = ChangeSlug.of(productDraft1.getSlug()); + final String productKey = product.getKey(); - productService - .updateProduct(product, Collections.singletonList(changeSlugUpdateAction)) - .exceptionally(exception -> { - assertThat(exception).isNotNull(); - - assertThat(exception).isExactlyInstanceOf(CompletionException.class); - assertThat(exception.getCause()).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponse = ((ErrorResponseException) exception.getCause()); - - final List fieldErrors = errorResponse - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - assertThat(fieldErrors).allSatisfy(error -> { - assertThat(error.getField()).isEqualTo("slug.en"); - assertThat(error.getDuplicateValue()).isEqualTo(productDraft1.getSlug().get(Locale.ENGLISH)); - }); - return null; - }) - .toCompletableFuture().join(); - - - //assert CTP state - final Optional fetchedProductOptional = CTP_TARGET_CLIENT - .execute(ProductQuery.of() - .withPredicates(QueryPredicate.of(format("key = \"%s\"", product.getKey())))) - .toCompletableFuture().join().head(); - - assertThat(fetchedProductOptional).isNotEmpty(); - final Product fetchedProduct = fetchedProductOptional.get(); - assertThat(fetchedProduct.getMasterData().getCurrent().getSlug()).isNotEqualTo(productDraft1.getSlug()); - } - - @Test - @SuppressWarnings("ConstantConditions") - void updateProduct_WithMoreThan500Actions_ShouldNotFail() { - // Update the product 501 times with a different name every time. - final int numberOfUpdateActions = 501; - final List> updateActions = - IntStream.range(1, numberOfUpdateActions + 1) - .mapToObj(i -> ChangeName.of(LocalizedString.of(Locale.GERMAN, format("name:%s", i)))) - .collect(Collectors.toList()); - - - final Product updatedProduct = productService.updateProduct(product, updateActions) - .toCompletableFuture().join(); - assertThat(updatedProduct).isNotNull(); - - //assert CTP state - final Optional fetchedProductOptional = CTP_TARGET_CLIENT - .execute(ProductQuery.of() - .withPredicates(QueryPredicate.of(format("key = \"%s\"", product.getKey())))) - .toCompletableFuture().join().head(); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(fetchedProductOptional).isNotEmpty(); - final Product fetchedProduct = fetchedProductOptional.get(); - - // Test that the fetched product has the name of the last update action that was applied. - assertThat(fetchedProduct.getMasterData().getStaged().getName()) - .isEqualTo(LocalizedString.of(Locale.GERMAN, format("name:%s", numberOfUpdateActions))); - } - - @Test - @SuppressWarnings("ConstantConditions") - void updateProduct_WithMoreThan500ImageAdditions_ShouldHaveAllNewImages() { - final Integer productMasterVariantId = product.getMasterData().getStaged().getMasterVariant().getId(); - - // Update the product by adding 600 images in separate update actions - final int numberOfImages = 600; - final List addedImages = new ArrayList<>(); - final List> updateActions = - IntStream.range(1, numberOfImages + 1) - .mapToObj(i -> { - final Image newExternalImage = Image.of(format("image#%s", i), ImageDimensions.of(10, 10)); - addedImages.add(newExternalImage); // keep track of added images. - return AddExternalImage.of(newExternalImage, productMasterVariantId); - }) - .collect(Collectors.toList()); - - final Product updatedProduct = productService.updateProduct(product, updateActions) - .toCompletableFuture().join(); - assertThat(updatedProduct).isNotNull(); - - //assert CTP state - final Optional fetchedProductOptional = CTP_TARGET_CLIENT - .execute(ProductQuery.of() - .withPredicates(QueryPredicate.of(format("key = \"%s\"", product.getKey())))) - .toCompletableFuture().join().head(); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(fetchedProductOptional).isNotEmpty(); - - final Product fetchedProduct = fetchedProductOptional.get(); - // Test that the fetched product has exactly the 600 images added before. - final List currentMasterVariantImages = fetchedProduct.getMasterData().getStaged() - .getMasterVariant().getImages(); - assertThat(currentMasterVariantImages).containsAll(addedImages); - } - - @Test - void fetchProduct_WithExistingKey_ShouldReturnProduct() { - final Optional fetchedProductOptional = productService.fetchProduct(product.getKey()) - .toCompletableFuture() - .join(); - assertThat(fetchedProductOptional).isNotEmpty(); - assertThat(fetchedProductOptional).contains(product); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchProduct_WithNonExistingKey_ShouldNotReturnProduct() { - final Optional fetchedProductOptional = productService.fetchProduct("someNonExistingKey") - .toCompletableFuture() - .join(); - assertThat(fetchedProductOptional).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchProduct_WithNullKey_ShouldNotReturnProduct() { - final Optional fetchedProductOptional = productService.fetchProduct(null) - .toCompletableFuture() - .join(); - assertThat(fetchedProductOptional).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchProduct_WithBlankKey_ShouldNotReturnProduct() { - final Optional fetchedProductOptional = productService.fetchProduct(StringUtils.EMPTY) - .toCompletableFuture() - .join(); - assertThat(fetchedProductOptional).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchProduct_WithBadGatewayException_ShouldFail() { - // preparation - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(ProductQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - final ProductSyncOptions spyOptions = - ProductSyncOptionsBuilder.of(spyClient) - .errorCallback( - (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback( - (exception, oldResource, newResource) - -> warningCallBackMessages.add(exception.getMessage())) - .build(); - final ProductService spyProductService = new ProductServiceImpl(spyOptions); - - - final String productKey = product.getKey(); - - // test and assertion - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyProductService.fetchProduct(productKey)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } + // test and assertion + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyProductService.fetchProduct(productKey)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/ProductTypeServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/ProductTypeServiceImplIT.java index 1445cf0a0c..50eb2f384e 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/ProductTypeServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/ProductTypeServiceImplIT.java @@ -1,5 +1,22 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_1; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_DESCRIPTION_1; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_KEY_1; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_NAME_1; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.AttributeMetaData; import com.commercetools.sync.producttypes.ProductTypeSyncOptions; import com.commercetools.sync.producttypes.ProductTypeSyncOptionsBuilder; @@ -15,10 +32,6 @@ import io.sphere.sdk.producttypes.commands.updateactions.SetKey; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -27,378 +40,420 @@ import java.util.Map; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.ATTRIBUTE_DEFINITION_DRAFT_1; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_DESCRIPTION_1; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_KEY_1; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.PRODUCT_TYPE_NAME_1; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.deleteProductTypes; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductTypeServiceImplIT { - private ProductTypeService productTypeService; - private static final String OLD_PRODUCT_TYPE_KEY = "old_product_type_key"; - private static final String OLD_PRODUCT_TYPE_NAME = "old_product_type_name"; - private static final Locale OLD_PRODUCT_TYPE_LOCALE = Locale.ENGLISH; - - private List errorCallBackMessages; - private List errorCallBackExceptions; - - /** - * Deletes product types from the target CTP project, then it populates the project with test data. - */ - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - - deleteProductTypes(CTP_TARGET_CLIENT); - createProductType(OLD_PRODUCT_TYPE_KEY, OLD_PRODUCT_TYPE_LOCALE, OLD_PRODUCT_TYPE_NAME, CTP_TARGET_CLIENT); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .build(); - productTypeService = new ProductTypeServiceImpl(productTypeSyncOptions); - } - - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteProductTypes(CTP_TARGET_CLIENT); - } - - @Test - void fetchCachedProductTypeId_WithNonExistingProductType_ShouldNotFetchAProductType() { - final Optional productTypeId = productTypeService.fetchCachedProductTypeId("non-existing-type-key") - .toCompletableFuture() - .join(); - assertThat(productTypeId).isEmpty(); - } - - @Test - void fetchCachedProductTypeId_WithExistingProductType_ShouldFetchProductTypeAndCache() { - final Optional productTypeId = productTypeService.fetchCachedProductTypeId(OLD_PRODUCT_TYPE_KEY) - .toCompletableFuture() - .join(); - assertThat(productTypeId).isNotEmpty(); - } - - @Test - void fetchCachedProductAttributeMetaDataMap_WithMetadataCache_ShouldReturnAnyAttributeMetadata() { - final Optional productTypeId = productTypeService.fetchCachedProductTypeId(OLD_PRODUCT_TYPE_KEY) - .toCompletableFuture() - .join(); + private ProductTypeService productTypeService; + private static final String OLD_PRODUCT_TYPE_KEY = "old_product_type_key"; + private static final String OLD_PRODUCT_TYPE_NAME = "old_product_type_name"; + private static final Locale OLD_PRODUCT_TYPE_LOCALE = Locale.ENGLISH; + + private List errorCallBackMessages; + private List errorCallBackExceptions; + + /** + * Deletes product types from the target CTP project, then it populates the project with test + * data. + */ + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + + deleteProductTypes(CTP_TARGET_CLIENT); + createProductType( + OLD_PRODUCT_TYPE_KEY, OLD_PRODUCT_TYPE_LOCALE, OLD_PRODUCT_TYPE_NAME, CTP_TARGET_CLIENT); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT).build(); + productTypeService = new ProductTypeServiceImpl(productTypeSyncOptions); + } + + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteProductTypes(CTP_TARGET_CLIENT); + } + + @Test + void fetchCachedProductTypeId_WithNonExistingProductType_ShouldNotFetchAProductType() { + final Optional productTypeId = + productTypeService + .fetchCachedProductTypeId("non-existing-type-key") + .toCompletableFuture() + .join(); + assertThat(productTypeId).isEmpty(); + } + + @Test + void fetchCachedProductTypeId_WithExistingProductType_ShouldFetchProductTypeAndCache() { + final Optional productTypeId = + productTypeService + .fetchCachedProductTypeId(OLD_PRODUCT_TYPE_KEY) + .toCompletableFuture() + .join(); + assertThat(productTypeId).isNotEmpty(); + } + + @Test + void fetchCachedProductAttributeMetaDataMap_WithMetadataCache_ShouldReturnAnyAttributeMetadata() { + final Optional productTypeId = + productTypeService + .fetchCachedProductTypeId(OLD_PRODUCT_TYPE_KEY) + .toCompletableFuture() + .join(); - assertThat(productTypeId).isNotEmpty(); + assertThat(productTypeId).isNotEmpty(); - Optional> fetchCachedProductAttributeMetaDataMap - = productTypeService.fetchCachedProductAttributeMetaDataMap(productTypeId.get()) - .toCompletableFuture() - .join(); + Optional> fetchCachedProductAttributeMetaDataMap = + productTypeService + .fetchCachedProductAttributeMetaDataMap(productTypeId.get()) + .toCompletableFuture() + .join(); - assertThat(fetchCachedProductAttributeMetaDataMap).isNotEmpty(); - } + assertThat(fetchCachedProductAttributeMetaDataMap).isNotEmpty(); + } - @Test - void fetchCachedProductAttributeMetaDataMap_WithoutMetadataCache_ShouldReturnAnyAttributeMetadata() { - final Optional productTypeOptional = CTP_TARGET_CLIENT + @Test + void + fetchCachedProductAttributeMetaDataMap_WithoutMetadataCache_ShouldReturnAnyAttributeMetadata() { + final Optional productTypeOptional = + CTP_TARGET_CLIENT .execute(ProductTypeQuery.of().byKey(OLD_PRODUCT_TYPE_KEY)) .toCompletableFuture() - .join().head(); - - assertThat(productTypeOptional).isNotEmpty(); + .join() + .head(); - if (productTypeOptional.isPresent()) { - Optional> fetchCachedProductAttributeMetaDataMap - = productTypeService.fetchCachedProductAttributeMetaDataMap(productTypeOptional.get().getId()) - .toCompletableFuture() - .join(); + assertThat(productTypeOptional).isNotEmpty(); - assertThat(fetchCachedProductAttributeMetaDataMap).isNotEmpty(); - } - } - - @Test - void fetchMatchingProductTypesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { - final Set typeKeys = new HashSet<>(); - final Set matchingProductTypes = productTypeService.fetchMatchingProductTypesByKeys(typeKeys) - .toCompletableFuture() - .join(); - - assertThat(matchingProductTypes).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingProductTypesByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { - final Set typeKeys = new HashSet<>(); - typeKeys.add("type_key_1"); - typeKeys.add("type_key_2"); - - final Set matchingProductTypes = productTypeService.fetchMatchingProductTypesByKeys(typeKeys) - .toCompletableFuture() - .join(); + if (productTypeOptional.isPresent()) { + Optional> fetchCachedProductAttributeMetaDataMap = + productTypeService + .fetchCachedProductAttributeMetaDataMap(productTypeOptional.get().getId()) + .toCompletableFuture() + .join(); - assertThat(matchingProductTypes).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); + assertThat(fetchCachedProductAttributeMetaDataMap).isNotEmpty(); } + } + + @Test + void fetchMatchingProductTypesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { + final Set typeKeys = new HashSet<>(); + final Set matchingProductTypes = + productTypeService.fetchMatchingProductTypesByKeys(typeKeys).toCompletableFuture().join(); + + assertThat(matchingProductTypes).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingProductTypesByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { + final Set typeKeys = new HashSet<>(); + typeKeys.add("type_key_1"); + typeKeys.add("type_key_2"); + + final Set matchingProductTypes = + productTypeService.fetchMatchingProductTypesByKeys(typeKeys).toCompletableFuture().join(); + + assertThat(matchingProductTypes).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingProductTypesByKeys_WithAnyExistingKeys_ShouldReturnASetOfProductTypes() { + final Set typeKeys = new HashSet<>(); + typeKeys.add(OLD_PRODUCT_TYPE_KEY); + + final Set matchingProductTypes = + productTypeService.fetchMatchingProductTypesByKeys(typeKeys).toCompletableFuture().join(); + + assertThat(matchingProductTypes).isNotEmpty(); + assertThat(matchingProductTypes).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingProductTypesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(ProductTypeQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + final ProductTypeSyncOptions spyOptions = + ProductTypeSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); - @Test - void fetchMatchingProductTypesByKeys_WithAnyExistingKeys_ShouldReturnASetOfProductTypes() { - final Set typeKeys = new HashSet<>(); - typeKeys.add(OLD_PRODUCT_TYPE_KEY); - - final Set matchingProductTypes = productTypeService.fetchMatchingProductTypesByKeys(typeKeys) - .toCompletableFuture() - .join(); + final ProductTypeService spyProductTypeService = new ProductTypeServiceImpl(spyOptions); - assertThat(matchingProductTypes).isNotEmpty(); - assertThat(matchingProductTypes).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingProductTypesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(ProductTypeQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - final ProductTypeSyncOptions spyOptions = - ProductTypeSyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .build(); - - final ProductTypeService spyProductTypeService = new ProductTypeServiceImpl(spyOptions); - - - final Set keys = new HashSet<>(); - keys.add(OLD_PRODUCT_TYPE_KEY); - - // test and assert - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyProductTypeService.fetchMatchingProductTypesByKeys(keys)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } + final Set keys = new HashSet<>(); + keys.add(OLD_PRODUCT_TYPE_KEY); - @Test - void fetchMatchingProductTypesByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedProductTypeIds() { - final Set fetchedProductTypes = productTypeService.fetchMatchingProductTypesByKeys( - singleton(OLD_PRODUCT_TYPE_KEY)) - .toCompletableFuture().join(); - assertThat(fetchedProductTypes).hasSize(1); - - final Optional productTypeOptional = CTP_TARGET_CLIENT - .execute(ProductTypeQuery.of() - .withPredicates(productTypeQueryModel -> productTypeQueryModel.key().is( - OLD_PRODUCT_TYPE_KEY))) - .toCompletableFuture().join().head(); - assertThat(productTypeOptional).isNotNull(); - - // Change product oldKey on ctp - final String newKey = "newKey"; - productTypeService.updateProductType(productTypeOptional.get(), Collections.singletonList(SetKey.of(newKey))) - .toCompletableFuture() - .join(); - - // Fetch cached id by old key - final Optional cachedProductTypeId = productTypeService.fetchCachedProductTypeId(OLD_PRODUCT_TYPE_KEY) - .toCompletableFuture().join(); - - assertThat(cachedProductTypeId).isNotEmpty(); - assertThat(cachedProductTypeId).contains(productTypeOptional.get().getId()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } + // test and assert + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyProductTypeService.fetchMatchingProductTypesByKeys(keys)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } - @Test - void createProductType_WithValidProductType_ShouldCreateProductTypeAndCacheId() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + @Test + void fetchMatchingProductTypesByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedProductTypeIds() { + final Set fetchedProductTypes = + productTypeService + .fetchMatchingProductTypesByKeys(singleton(OLD_PRODUCT_TYPE_KEY)) + .toCompletableFuture() + .join(); + assertThat(fetchedProductTypes).hasSize(1); + + final Optional productTypeOptional = + CTP_TARGET_CLIENT + .execute( + ProductTypeQuery.of() + .withPredicates( + productTypeQueryModel -> + productTypeQueryModel.key().is(OLD_PRODUCT_TYPE_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(productTypeOptional).isNotNull(); + + // Change product oldKey on ctp + final String newKey = "newKey"; + productTypeService + .updateProductType(productTypeOptional.get(), Collections.singletonList(SetKey.of(newKey))) + .toCompletableFuture() + .join(); + + // Fetch cached id by old key + final Optional cachedProductTypeId = + productTypeService + .fetchCachedProductTypeId(OLD_PRODUCT_TYPE_KEY) + .toCompletableFuture() + .join(); + + assertThat(cachedProductTypeId).isNotEmpty(); + assertThat(cachedProductTypeId).contains(productTypeOptional.get().getId()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void createProductType_WithValidProductType_ShouldCreateProductTypeAndCacheId() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( PRODUCT_TYPE_KEY_1, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, - singletonList(ATTRIBUTE_DEFINITION_DRAFT_1) - ); - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final ProductTypeSyncOptions spyOptions = ProductTypeSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); + + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final ProductTypeSyncOptions spyOptions = + ProductTypeSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final ProductTypeService spyProductTypeService = new ProductTypeServiceImpl(spyOptions); - - // test - final Optional createdOptional = spyProductTypeService - .createProductType(newProductTypeDraft) - .toCompletableFuture().join(); - // assertion - final Optional queriedOptional = CTP_TARGET_CLIENT - .execute(ProductTypeQuery.of().withPredicates(productTypeQueryModel -> - productTypeQueryModel.key().is(PRODUCT_TYPE_KEY_1))) - .toCompletableFuture().join().head(); - - assertThat(queriedOptional) - .hasValueSatisfying(queried -> assertThat(createdOptional) - .hasValueSatisfying(created -> { - assertThat(created.getKey()).isEqualTo(queried.getKey()); - assertThat(created.getDescription()).isEqualTo(queried.getDescription()); - assertThat(created.getName()).isEqualTo(queried.getName()); - assertThat(created.getAttributes()).isEqualTo(queried.getAttributes()); - })); - - // Assert that the created product type is cached - final Optional productTypeId = - spyProductTypeService.fetchCachedProductTypeId(PRODUCT_TYPE_KEY_1).toCompletableFuture().join(); - assertThat(productTypeId).isPresent(); - verify(spyClient, times(0)).execute(any(ProductTypeQuery.class)); - } - - @Test - void createProductType_WithInvalidProductType_ShouldHaveEmptyOptionalAsAResult() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + final ProductTypeService spyProductTypeService = new ProductTypeServiceImpl(spyOptions); + + // test + final Optional createdOptional = + spyProductTypeService.createProductType(newProductTypeDraft).toCompletableFuture().join(); + // assertion + final Optional queriedOptional = + CTP_TARGET_CLIENT + .execute( + ProductTypeQuery.of() + .withPredicates( + productTypeQueryModel -> + productTypeQueryModel.key().is(PRODUCT_TYPE_KEY_1))) + .toCompletableFuture() + .join() + .head(); + + assertThat(queriedOptional) + .hasValueSatisfying( + queried -> + assertThat(createdOptional) + .hasValueSatisfying( + created -> { + assertThat(created.getKey()).isEqualTo(queried.getKey()); + assertThat(created.getDescription()).isEqualTo(queried.getDescription()); + assertThat(created.getName()).isEqualTo(queried.getName()); + assertThat(created.getAttributes()).isEqualTo(queried.getAttributes()); + })); + + // Assert that the created product type is cached + final Optional productTypeId = + spyProductTypeService + .fetchCachedProductTypeId(PRODUCT_TYPE_KEY_1) + .toCompletableFuture() + .join(); + assertThat(productTypeId).isPresent(); + verify(spyClient, times(0)).execute(any(ProductTypeQuery.class)); + } + + @Test + void createProductType_WithInvalidProductType_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( "", PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, - singletonList(ATTRIBUTE_DEFINITION_DRAFT_1) - ); - - final ProductTypeSyncOptions options = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); + + final ProductTypeSyncOptions options = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final ProductTypeService service = new ProductTypeServiceImpl(options); + final ProductTypeService service = new ProductTypeServiceImpl(options); - // test - final Optional result = - service.createProductType(newProductTypeDraft).toCompletableFuture().join(); + // test + final Optional result = + service.createProductType(newProductTypeDraft).toCompletableFuture().join(); - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); - } + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); + } - @Test - void createProductType_WithDuplicateKey_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( + @Test + void createProductType_WithDuplicateKey_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( OLD_PRODUCT_TYPE_KEY, PRODUCT_TYPE_NAME_1, PRODUCT_TYPE_DESCRIPTION_1, - singletonList(ATTRIBUTE_DEFINITION_DRAFT_1) - ); - - final ProductTypeSyncOptions options = ProductTypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + singletonList(ATTRIBUTE_DEFINITION_DRAFT_1)); + + final ProductTypeSyncOptions options = + ProductTypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final ProductTypeService service = new ProductTypeServiceImpl(options); - - // test - final Optional result = - service.createProductType(newProductTypeDraft).toCompletableFuture().join(); - - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("A duplicate value")); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> { - assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) exception; - - final List fieldErrors = errorResponseException - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); + final ProductTypeService service = new ProductTypeServiceImpl(options); + + // test + final Optional result = + service.createProductType(newProductTypeDraft).toCompletableFuture().join(); + + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("A duplicate value")); + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> { + assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) exception; + + final List fieldErrors = + errorResponseException.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); }); - } + } + + @Test + void updateProductType_WithValidChanges_ShouldUpdateProductTypeCorrectly() { + final Optional productTypeOptional = + CTP_TARGET_CLIENT + .execute( + ProductTypeQuery.of() + .withPredicates( + productTypeQueryModel -> + productTypeQueryModel.key().is(OLD_PRODUCT_TYPE_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(productTypeOptional).isNotNull(); - @Test - void updateProductType_WithValidChanges_ShouldUpdateProductTypeCorrectly() { - final Optional productTypeOptional = CTP_TARGET_CLIENT - .execute(ProductTypeQuery.of() - .withPredicates(productTypeQueryModel -> productTypeQueryModel.key().is( - OLD_PRODUCT_TYPE_KEY))) - .toCompletableFuture().join().head(); - assertThat(productTypeOptional).isNotNull(); - - final ChangeName changeNameUpdateAction = ChangeName.of("new_product_type_name"); - - final ProductType updatedProductType = - productTypeService.updateProductType(productTypeOptional.get(), singletonList(changeNameUpdateAction)) - .toCompletableFuture().join(); - assertThat(updatedProductType).isNotNull(); - - final Optional updatedProductTypeOptional = CTP_TARGET_CLIENT - .execute(ProductTypeQuery.of() - .withPredicates(productTypeQueryModel -> productTypeQueryModel.key().is( - OLD_PRODUCT_TYPE_KEY))) - .toCompletableFuture().join().head(); - - assertThat(productTypeOptional).isNotEmpty(); - final ProductType fetchedProductType = updatedProductTypeOptional.get(); - assertThat(fetchedProductType.getKey()).isEqualTo(updatedProductType.getKey()); - assertThat(fetchedProductType.getDescription()).isEqualTo(updatedProductType.getDescription()); - assertThat(fetchedProductType.getName()).isEqualTo(updatedProductType.getName()); - assertThat(fetchedProductType.getAttributes()).isEqualTo(updatedProductType.getAttributes()); - } + final ChangeName changeNameUpdateAction = ChangeName.of("new_product_type_name"); - @Test - void updateProductType_WithInvalidChanges_ShouldCompleteExceptionally() { - final Optional typeOptional = CTP_TARGET_CLIENT - .execute(ProductTypeQuery - .of() - .withPredicates(productTypeQueryModel -> productTypeQueryModel.key().is(OLD_PRODUCT_TYPE_KEY))) - .toCompletableFuture().join().head(); - assertThat(typeOptional).isNotNull(); - - final ChangeName changeNameUpdateAction = ChangeName.of(null); - productTypeService.updateProductType(typeOptional.get(), singletonList(changeNameUpdateAction)) - .exceptionally(exception -> { - assertThat(exception).isNotNull(); - assertThat(exception.getMessage()).contains("Request body does not contain valid JSON."); - return null; - }) - .toCompletableFuture().join(); - } -} \ No newline at end of file + final ProductType updatedProductType = + productTypeService + .updateProductType(productTypeOptional.get(), singletonList(changeNameUpdateAction)) + .toCompletableFuture() + .join(); + assertThat(updatedProductType).isNotNull(); + + final Optional updatedProductTypeOptional = + CTP_TARGET_CLIENT + .execute( + ProductTypeQuery.of() + .withPredicates( + productTypeQueryModel -> + productTypeQueryModel.key().is(OLD_PRODUCT_TYPE_KEY))) + .toCompletableFuture() + .join() + .head(); + + assertThat(productTypeOptional).isNotEmpty(); + final ProductType fetchedProductType = updatedProductTypeOptional.get(); + assertThat(fetchedProductType.getKey()).isEqualTo(updatedProductType.getKey()); + assertThat(fetchedProductType.getDescription()).isEqualTo(updatedProductType.getDescription()); + assertThat(fetchedProductType.getName()).isEqualTo(updatedProductType.getName()); + assertThat(fetchedProductType.getAttributes()).isEqualTo(updatedProductType.getAttributes()); + } + + @Test + void updateProductType_WithInvalidChanges_ShouldCompleteExceptionally() { + final Optional typeOptional = + CTP_TARGET_CLIENT + .execute( + ProductTypeQuery.of() + .withPredicates( + productTypeQueryModel -> + productTypeQueryModel.key().is(OLD_PRODUCT_TYPE_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(typeOptional).isNotNull(); + + final ChangeName changeNameUpdateAction = ChangeName.of(null); + productTypeService + .updateProductType(typeOptional.get(), singletonList(changeNameUpdateAction)) + .exceptionally( + exception -> { + assertThat(exception).isNotNull(); + assertThat(exception.getMessage()) + .contains("Request body does not contain valid JSON."); + return null; + }) + .toCompletableFuture() + .join(); + } +} diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/ShoppingListServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/ShoppingListServiceImplIT.java index c3965f6a6b..cb300f8867 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/ShoppingListServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/ShoppingListServiceImplIT.java @@ -1,5 +1,21 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.createShoppingList; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.deleteShoppingListTestData; +import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.deleteShoppingLists; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import com.commercetools.sync.services.ShoppingListService; import com.commercetools.sync.services.impl.ShoppingListServiceImpl; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; @@ -22,253 +38,274 @@ import io.sphere.sdk.shoppinglists.TextLineItemDraftBuilder; import io.sphere.sdk.shoppinglists.commands.updateactions.ChangeName; import io.sphere.sdk.shoppinglists.queries.ShoppingListQuery; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.integration.commons.utils.ProductTypeITUtils.createProductType; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.createShoppingList; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.deleteShoppingListTestData; -import static com.commercetools.sync.integration.commons.utils.ShoppingListITUtils.deleteShoppingLists; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_TYPE_RESOURCE_PATH; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ShoppingListServiceImplIT { - private ShoppingListService shoppingListService; - - private ShoppingList shoppingList; - private List errorCallBackMessages; - private List errorCallBackExceptions; - - /** - * Deletes shopping list and products from the target CTP projects, then it populates the project with test data. - */ - @BeforeEach - void setup() { - deleteShoppingListTestData(CTP_TARGET_CLIENT); - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - shoppingList = createShoppingList(CTP_TARGET_CLIENT, "name", "key"); - final ShoppingListSyncOptions options = - ShoppingListSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception); - }) - .build(); - shoppingListService = new ShoppingListServiceImpl(options); - } - - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteShoppingLists(CTP_TARGET_CLIENT); - } - - @Test - void fetchShoppingList_WithNonExistingShoppingList_ShouldReturnEmptyOptional() { - final Optional shoppingList = - shoppingListService.fetchShoppingList("not-existing-key").toCompletableFuture().join(); - - assertThat(shoppingList).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - - } - - @Test - void fetchShoppingList_WithExistingShoppingList_ShouldFetchShoppingList() { - final Optional shoppingList = - shoppingListService.fetchShoppingList(this.shoppingList.getKey()).toCompletableFuture().join(); - - assertThat(shoppingList).isNotEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingShoppingListsByKeys_WithNotExistingKeys_ShouldReturnEmptySet() { - final Set shoppingListKeys = new HashSet<>(); - shoppingListKeys.add("not_existing_key_1"); - shoppingListKeys.add("not_existing_key_2"); - Set shoppingLists = - shoppingListService.fetchMatchingShoppingListsByKeys(shoppingListKeys).toCompletableFuture().join(); - - assertThat(shoppingLists).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - } - - @Test - void fetchMatchingShoppingListsByKeys_WithExistingShoppingListsKeys_ShouldReturnShoppingLists() { - ShoppingList otherShoppingList = createShoppingList(CTP_TARGET_CLIENT, "other_name", "other_key"); - final Set shoppingListKeys = new HashSet<>(); - shoppingListKeys.add(shoppingList.getKey()); - shoppingListKeys.add(otherShoppingList.getKey()); - Set shoppingLists = - shoppingListService.fetchMatchingShoppingListsByKeys(shoppingListKeys) - .toCompletableFuture() - .join(); - - - assertThat(shoppingLists).hasSize(2); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - } - - @Test - void cacheKeysToIds_WithEmptyKeys_ShouldReturnCurrentCache() { - Map cache = shoppingListService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); - assertThat(cache).hasSize(0); - - cache = shoppingListService.cacheKeysToIds(singleton(shoppingList.getKey())).toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - cache = shoppingListService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void cacheKeysToIds_WithCachedKeys_ShouldReturnCachedKeysWithoutRequest() { - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .build(); - final ShoppingListService shoppingListService = new ShoppingListServiceImpl(shoppingListSyncOptions); - - - Map cache = shoppingListService.cacheKeysToIds(singleton(shoppingList.getKey())) - .toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - cache = shoppingListService.cacheKeysToIds(singleton(shoppingList.getKey())) - .toCompletableFuture().join(); - assertThat(cache).hasSize(1); - - verify(spyClient, times(1)).execute(any()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void createShoppingList_WithValidShoppingList_ShouldCreateShoppingList() { - //preparation - ProductType productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); - final ProductDraft productDraft = ProductDraftBuilder - .of(ResourceIdentifier.ofKey(productType.getKey()), LocalizedString.ofEnglish("newProduct"), + private ShoppingListService shoppingListService; + + private ShoppingList shoppingList; + private List errorCallBackMessages; + private List errorCallBackExceptions; + + /** + * Deletes shopping list and products from the target CTP projects, then it populates the project + * with test data. + */ + @BeforeEach + void setup() { + deleteShoppingListTestData(CTP_TARGET_CLIENT); + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + shoppingList = createShoppingList(CTP_TARGET_CLIENT, "name", "key"); + final ShoppingListSyncOptions options = + ShoppingListSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception); + }) + .build(); + shoppingListService = new ShoppingListServiceImpl(options); + } + + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteShoppingLists(CTP_TARGET_CLIENT); + } + + @Test + void fetchShoppingList_WithNonExistingShoppingList_ShouldReturnEmptyOptional() { + final Optional shoppingList = + shoppingListService.fetchShoppingList("not-existing-key").toCompletableFuture().join(); + + assertThat(shoppingList).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchShoppingList_WithExistingShoppingList_ShouldFetchShoppingList() { + final Optional shoppingList = + shoppingListService + .fetchShoppingList(this.shoppingList.getKey()) + .toCompletableFuture() + .join(); + + assertThat(shoppingList).isNotEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingShoppingListsByKeys_WithNotExistingKeys_ShouldReturnEmptySet() { + final Set shoppingListKeys = new HashSet<>(); + shoppingListKeys.add("not_existing_key_1"); + shoppingListKeys.add("not_existing_key_2"); + Set shoppingLists = + shoppingListService + .fetchMatchingShoppingListsByKeys(shoppingListKeys) + .toCompletableFuture() + .join(); + + assertThat(shoppingLists).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } + + @Test + void fetchMatchingShoppingListsByKeys_WithExistingShoppingListsKeys_ShouldReturnShoppingLists() { + ShoppingList otherShoppingList = + createShoppingList(CTP_TARGET_CLIENT, "other_name", "other_key"); + final Set shoppingListKeys = new HashSet<>(); + shoppingListKeys.add(shoppingList.getKey()); + shoppingListKeys.add(otherShoppingList.getKey()); + Set shoppingLists = + shoppingListService + .fetchMatchingShoppingListsByKeys(shoppingListKeys) + .toCompletableFuture() + .join(); + + assertThat(shoppingLists).hasSize(2); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } + + @Test + void cacheKeysToIds_WithEmptyKeys_ShouldReturnCurrentCache() { + Map cache = + shoppingListService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); + assertThat(cache).hasSize(0); + + cache = + shoppingListService + .cacheKeysToIds(singleton(shoppingList.getKey())) + .toCompletableFuture() + .join(); + assertThat(cache).hasSize(1); + + cache = shoppingListService.cacheKeysToIds(emptySet()).toCompletableFuture().join(); + assertThat(cache).hasSize(1); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void cacheKeysToIds_WithCachedKeys_ShouldReturnCachedKeysWithoutRequest() { + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); + final ShoppingListService shoppingListService = + new ShoppingListServiceImpl(shoppingListSyncOptions); + + Map cache = + shoppingListService + .cacheKeysToIds(singleton(shoppingList.getKey())) + .toCompletableFuture() + .join(); + assertThat(cache).hasSize(1); + + cache = + shoppingListService + .cacheKeysToIds(singleton(shoppingList.getKey())) + .toCompletableFuture() + .join(); + assertThat(cache).hasSize(1); + + verify(spyClient, times(1)).execute(any()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void createShoppingList_WithValidShoppingList_ShouldCreateShoppingList() { + // preparation + ProductType productType = createProductType(PRODUCT_TYPE_RESOURCE_PATH, CTP_TARGET_CLIENT); + final ProductDraft productDraft = + ProductDraftBuilder.of( + ResourceIdentifier.ofKey(productType.getKey()), + LocalizedString.ofEnglish("newProduct"), LocalizedString.ofEnglish("foo"), ProductVariantDraftBuilder.of().key("foo-new").sku("sku-new").build()) .key("newProduct") .build(); - executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); - LineItemDraft lineItemDraft = LineItemDraftBuilder.ofSku("sku-new", Long.valueOf(1)).build(); - TextLineItemDraft textLineItemDraft = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("text"), 1L).build(); - final ShoppingListDraftDsl newShoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("new_name")) - .key("new_key") - .plusLineItems(lineItemDraft) - .plusTextLineItems(textLineItemDraft) - .build(); - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - - final ShoppingListSyncOptions options = - ShoppingListSyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception); - }) - .build(); - ShoppingListService spyShoppingListService = new ShoppingListServiceImpl(options); - - //test - final Optional createdShoppingList = - spyShoppingListService.createShoppingList(newShoppingListDraft).toCompletableFuture().join(); - - final Optional queriedOptional = CTP_TARGET_CLIENT - .execute(ShoppingListQuery.of().withPredicates(shoppingListQueryModel -> - shoppingListQueryModel.key().is("new_key"))) - .toCompletableFuture().join().head(); - - assertThat(queriedOptional).hasValueSatisfying(queried -> - assertThat(createdShoppingList).hasValueSatisfying(created -> { - assertThat(created.getKey()).isEqualTo(queried.getKey()); - assertThat(created.getName()).isEqualTo(queried.getName()); - assertThat(created.getLineItems()).hasSize(1); - assertThat(created.getTextLineItems()).hasSize(1); - })); - } - - @Test - void createShoppingList_WithNotExistingSkuInLineItem_ShouldNotCreateShoppingList() { - // preparation - LineItemDraft lineItemDraft = LineItemDraftBuilder.ofSku("unknownSku", Long.valueOf(1)).build(); - final ShoppingListDraft newShoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("new_name")) - .key("new_key") - .plusLineItems(lineItemDraft) - .build(); - - final Optional createdShoppingListOptional = shoppingListService - .createShoppingList(newShoppingListDraft) - .toCompletableFuture().join(); - - assertThat(createdShoppingListOptional).isEmpty(); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackMessages) - .hasSize(1); - assertThat(errorCallBackMessages.get(0)).contains("Failed to create draft with key: 'new_key'. Reason: " - + "detailMessage: No published product with an sku 'unknownSku' exists."); - } - - @Test - void updateCustomer_WithValidChanges_ShouldUpdateCustomerCorrectly() { - final ChangeName updatedName = ChangeName.of(LocalizedString.ofEnglish("updated_name")); - - final ShoppingList updatedShoppingList = - shoppingListService.updateShoppingList(shoppingList, singletonList(updatedName)) - .toCompletableFuture().join(); - assertThat(updatedShoppingList).isNotNull(); - - final Optional queried = CTP_TARGET_CLIENT - .execute(ShoppingListQuery.of().withPredicates(shoppingListQueryModel -> - shoppingListQueryModel.key().is(shoppingList.getKey()))) - .toCompletableFuture().join().head(); + executeBlocking(CTP_TARGET_CLIENT.execute(ProductCreateCommand.of(productDraft))); + LineItemDraft lineItemDraft = LineItemDraftBuilder.ofSku("sku-new", Long.valueOf(1)).build(); + TextLineItemDraft textLineItemDraft = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("text"), 1L).build(); + final ShoppingListDraftDsl newShoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("new_name")) + .key("new_key") + .plusLineItems(lineItemDraft) + .plusTextLineItems(textLineItemDraft) + .build(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(queried).isNotEmpty(); - final ShoppingList fetchedShoppingList = queried.get(); - assertThat(fetchedShoppingList.getKey()).isEqualTo(updatedShoppingList.getKey()); - assertThat(fetchedShoppingList.getName()).isEqualTo(updatedShoppingList.getName()); + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - } + final ShoppingListSyncOptions options = + ShoppingListSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception); + }) + .build(); + ShoppingListService spyShoppingListService = new ShoppingListServiceImpl(options); + // test + final Optional createdShoppingList = + spyShoppingListService + .createShoppingList(newShoppingListDraft) + .toCompletableFuture() + .join(); + + final Optional queriedOptional = + CTP_TARGET_CLIENT + .execute( + ShoppingListQuery.of() + .withPredicates( + shoppingListQueryModel -> shoppingListQueryModel.key().is("new_key"))) + .toCompletableFuture() + .join() + .head(); + + assertThat(queriedOptional) + .hasValueSatisfying( + queried -> + assertThat(createdShoppingList) + .hasValueSatisfying( + created -> { + assertThat(created.getKey()).isEqualTo(queried.getKey()); + assertThat(created.getName()).isEqualTo(queried.getName()); + assertThat(created.getLineItems()).hasSize(1); + assertThat(created.getTextLineItems()).hasSize(1); + })); + } + + @Test + void createShoppingList_WithNotExistingSkuInLineItem_ShouldNotCreateShoppingList() { + // preparation + LineItemDraft lineItemDraft = LineItemDraftBuilder.ofSku("unknownSku", Long.valueOf(1)).build(); + final ShoppingListDraft newShoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("new_name")) + .key("new_key") + .plusLineItems(lineItemDraft) + .build(); + final Optional createdShoppingListOptional = + shoppingListService.createShoppingList(newShoppingListDraft).toCompletableFuture().join(); + + assertThat(createdShoppingListOptional).isEmpty(); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .contains( + "Failed to create draft with key: 'new_key'. Reason: " + + "detailMessage: No published product with an sku 'unknownSku' exists."); + } + + @Test + void updateCustomer_WithValidChanges_ShouldUpdateCustomerCorrectly() { + final ChangeName updatedName = ChangeName.of(LocalizedString.ofEnglish("updated_name")); + + final ShoppingList updatedShoppingList = + shoppingListService + .updateShoppingList(shoppingList, singletonList(updatedName)) + .toCompletableFuture() + .join(); + assertThat(updatedShoppingList).isNotNull(); + + final Optional queried = + CTP_TARGET_CLIENT + .execute( + ShoppingListQuery.of() + .withPredicates( + shoppingListQueryModel -> + shoppingListQueryModel.key().is(shoppingList.getKey()))) + .toCompletableFuture() + .join() + .head(); + + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(queried).isNotEmpty(); + final ShoppingList fetchedShoppingList = queried.get(); + assertThat(fetchedShoppingList.getKey()).isEqualTo(updatedShoppingList.getKey()); + assertThat(fetchedShoppingList.getName()).isEqualTo(updatedShoppingList.getName()); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/StateServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/StateServiceImplIT.java index 33f41faf45..23a0756bb0 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/StateServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/StateServiceImplIT.java @@ -1,5 +1,24 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.STATE_DESCRIPTION_1; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.STATE_KEY_1; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.STATE_NAME_1; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.clearTransitions; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.createState; +import static com.commercetools.sync.integration.commons.utils.StateITUtils.deleteStates; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.services.StateService; import com.commercetools.sync.services.impl.StateServiceImpl; import com.commercetools.sync.states.StateSyncOptions; @@ -18,444 +37,454 @@ import io.sphere.sdk.states.commands.updateactions.SetName; import io.sphere.sdk.states.queries.StateQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.STATE_DESCRIPTION_1; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.STATE_KEY_1; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.STATE_NAME_1; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.clearTransitions; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.createState; -import static com.commercetools.sync.integration.commons.utils.StateITUtils.deleteStates; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class StateServiceImplIT { - private static final StateType STATE_TYPE = StateType.PRODUCT_STATE; - private static final StateType TRANSITION_STATE_TYPE = StateType.REVIEW_STATE; - private static final String OLD_STATE_KEY = "old_state_key"; - - private List errorCallBackMessages; - private List errorCallBackExceptions; - - private State oldState; - private StateService stateService; - private ArrayList warnings; - - /** - * Deletes states from the target CTP projects, then it populates the project with test data. - */ - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - - deleteStates(CTP_TARGET_CLIENT, STATE_TYPE); - deleteStates(CTP_TARGET_CLIENT, TRANSITION_STATE_TYPE); - warnings = new ArrayList<>(); - oldState = createState(CTP_TARGET_CLIENT, STATE_TYPE); - - final StateSyncOptions StateSyncOptions = StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT) - .warningCallback((exception, oldResource, newResource) -> warnings.add(exception.getMessage())) + private static final StateType STATE_TYPE = StateType.PRODUCT_STATE; + private static final StateType TRANSITION_STATE_TYPE = StateType.REVIEW_STATE; + private static final String OLD_STATE_KEY = "old_state_key"; + + private List errorCallBackMessages; + private List errorCallBackExceptions; + + private State oldState; + private StateService stateService; + private ArrayList warnings; + + /** Deletes states from the target CTP projects, then it populates the project with test data. */ + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + + deleteStates(CTP_TARGET_CLIENT, STATE_TYPE); + deleteStates(CTP_TARGET_CLIENT, TRANSITION_STATE_TYPE); + warnings = new ArrayList<>(); + oldState = createState(CTP_TARGET_CLIENT, STATE_TYPE); + + final StateSyncOptions StateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .warningCallback( + (exception, oldResource, newResource) -> warnings.add(exception.getMessage())) + .build(); + stateService = new StateServiceImpl(StateSyncOptions); + } + + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteStates(CTP_TARGET_CLIENT, STATE_TYPE); + deleteStates(CTP_TARGET_CLIENT, TRANSITION_STATE_TYPE); + } + + @Test + void fetchCachedStateId_WithNonExistingState_ShouldNotFetchAState() { + final Optional stateId = + stateService.fetchCachedStateId("non-existing-state-key").toCompletableFuture().join(); + assertThat(stateId).isEmpty(); + assertThat(warnings).isEmpty(); + } + + @Test + void fetchCachedStateId_WithExistingState_ShouldFetchStateAndCache() { + final Optional stateId = + stateService.fetchCachedStateId(oldState.getKey()).toCompletableFuture().join(); + assertThat(stateId).isNotEmpty(); + assertThat(warnings).isEmpty(); + } + + @Test + void fetchCachedStateId_WithNullKey_ShouldReturnFutureWithEmptyOptional() { + final Optional stateId = + stateService.fetchCachedStateId(null).toCompletableFuture().join(); + + assertThat(stateId).isEmpty(); + assertThat(warnings).isEmpty(); + } + + @Test + void fetchMatchingStatesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { + final Set stateKeys = new HashSet<>(); + final Set matchingStates = + stateService.fetchMatchingStatesByKeys(stateKeys).toCompletableFuture().join(); + + assertThat(matchingStates).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingStatesByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { + final Set stateKeys = new HashSet<>(); + stateKeys.add("state_key_1"); + stateKeys.add("state_key_2"); + + final Set matchingStates = + stateService.fetchMatchingStatesByKeys(stateKeys).toCompletableFuture().join(); + + assertThat(matchingStates).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingStatesByKeys_WithAnyExistingKeys_ShouldReturnASetOfStates() { + final String key = "state_key_X"; + final State transitionState = + createState(CTP_TARGET_CLIENT, "state_transition_key_1", TRANSITION_STATE_TYPE, null); + final State fetchState = + createState(CTP_TARGET_CLIENT, key, TRANSITION_STATE_TYPE, transitionState); + + final Set stateKeys = new HashSet<>(); + stateKeys.add(key); + + final Set matchingStates = + stateService.fetchMatchingStatesByKeys(stateKeys).toCompletableFuture().join(); + + assertThat(matchingStates).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + + State matchedState = matchingStates.iterator().next(); + assertThat(matchedState.getId()).isEqualTo(fetchState.getId()); + assertThat(matchedState.getTransitions()).hasSize(1); + Reference transition = matchedState.getTransitions().iterator().next(); + assertThat(transition.getId()).isEqualTo(transitionState.getId()); + assertThat(transition.getObj()).isNull(); + + clearTransitions(CTP_TARGET_CLIENT, fetchState); + deleteStates(CTP_TARGET_CLIENT, TRANSITION_STATE_TYPE); + } + + @Test + void fetchMatchingStatesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(StateQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + + final StateSyncOptions spyOptions = + StateSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - stateService = new StateServiceImpl(StateSyncOptions); - } - - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteStates(CTP_TARGET_CLIENT, STATE_TYPE); - deleteStates(CTP_TARGET_CLIENT, TRANSITION_STATE_TYPE); - } - - @Test - void fetchCachedStateId_WithNonExistingState_ShouldNotFetchAState() { - final Optional stateId = stateService.fetchCachedStateId("non-existing-state-key") - .toCompletableFuture() - .join(); - assertThat(stateId).isEmpty(); - assertThat(warnings).isEmpty(); - } - - @Test - void fetchCachedStateId_WithExistingState_ShouldFetchStateAndCache() { - final Optional stateId = stateService.fetchCachedStateId(oldState.getKey()) - .toCompletableFuture() - .join(); - assertThat(stateId).isNotEmpty(); - assertThat(warnings).isEmpty(); - } - - @Test - void fetchCachedStateId_WithNullKey_ShouldReturnFutureWithEmptyOptional() { - final Optional stateId = stateService.fetchCachedStateId(null) - .toCompletableFuture() - .join(); - - assertThat(stateId).isEmpty(); - assertThat(warnings).isEmpty(); - } - - @Test - void fetchMatchingStatesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { - final Set stateKeys = new HashSet<>(); - final Set matchingStates = stateService.fetchMatchingStatesByKeys(stateKeys) - .toCompletableFuture() - .join(); - - assertThat(matchingStates).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingStatesByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { - final Set stateKeys = new HashSet<>(); - stateKeys.add("state_key_1"); - stateKeys.add("state_key_2"); - - final Set matchingStates = stateService.fetchMatchingStatesByKeys(stateKeys) - .toCompletableFuture() - .join(); - assertThat(matchingStates).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } + final StateService spyStateService = new StateServiceImpl(spyOptions); - @Test - void fetchMatchingStatesByKeys_WithAnyExistingKeys_ShouldReturnASetOfStates() { - final String key = "state_key_X"; - final State transitionState = createState(CTP_TARGET_CLIENT, "state_transition_key_1", - TRANSITION_STATE_TYPE, null); - final State fetchState = createState(CTP_TARGET_CLIENT, key, TRANSITION_STATE_TYPE, transitionState); + final Set keys = new HashSet<>(); + keys.add(OLD_STATE_KEY); - final Set stateKeys = new HashSet<>(); - stateKeys.add(key); + // test and assert + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyStateService.fetchMatchingStatesByKeys(keys)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } - final Set matchingStates = stateService.fetchMatchingStatesByKeys(stateKeys) + @Test + void fetchMatchingStatesByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedStateIds() { + final Set fetchedStates = + stateService + .fetchMatchingStatesByKeys(singleton(OLD_STATE_KEY)) .toCompletableFuture() .join(); + assertThat(fetchedStates).hasSize(1); - assertThat(matchingStates).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - - State matchedState = matchingStates.iterator().next(); - assertThat(matchedState.getId()).isEqualTo(fetchState.getId()); - assertThat(matchedState.getTransitions()).hasSize(1); - Reference transition = matchedState.getTransitions().iterator().next(); - assertThat(transition.getId()).isEqualTo(transitionState.getId()); - assertThat(transition.getObj()).isNull(); - - clearTransitions(CTP_TARGET_CLIENT, fetchState); - deleteStates(CTP_TARGET_CLIENT, TRANSITION_STATE_TYPE); - } - - @Test - void fetchMatchingStatesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(StateQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - - final StateSyncOptions spyOptions = - StateSyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .build(); - - final StateService spyStateService = new StateServiceImpl(spyOptions); - - - final Set keys = new HashSet<>(); - keys.add(OLD_STATE_KEY); - - // test and assert - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyStateService.fetchMatchingStatesByKeys(keys)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } - - @Test - void fetchMatchingStatesByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedStateIds() { - final Set fetchedStates = stateService.fetchMatchingStatesByKeys(singleton(OLD_STATE_KEY)) - .toCompletableFuture().join(); - assertThat(fetchedStates).hasSize(1); - - final Optional stateOptional = CTP_TARGET_CLIENT - .execute(StateQuery.of().withPredicates(queryModel -> queryModel.key().is(OLD_STATE_KEY))) + final Optional stateOptional = + CTP_TARGET_CLIENT + .execute( + StateQuery.of().withPredicates(queryModel -> queryModel.key().is(OLD_STATE_KEY))) .toCompletableFuture() .join() .head(); - assertThat(stateOptional).isNotNull(); - - // Change state old_state_key on ctp - final String newKey = "new_state_key"; - stateService.updateState(stateOptional.get(), Collections.singletonList(ChangeKey.of(newKey))) - .toCompletableFuture() - .join(); - - // Fetch cached id by old key - final Optional cachedStateId = stateService.fetchCachedStateId(OLD_STATE_KEY) - .toCompletableFuture() - .join(); - - assertThat(cachedStateId).isNotEmpty(); - assertThat(cachedStateId).contains(stateOptional.get().getId()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingStatesByKeysWithTransitions_WithAnyExistingKeys_ShouldReturnASetOfStates() { - final String key = "state_key_Y"; - final State transitionState = createState(CTP_TARGET_CLIENT, "state_transition_key_2", - TRANSITION_STATE_TYPE, null); - final State fetchState = createState(CTP_TARGET_CLIENT, key, TRANSITION_STATE_TYPE, transitionState); - - final Set stateKeys = new HashSet<>(); - stateKeys.add(key); - - final Set matchingStates = stateService.fetchMatchingStatesByKeysWithTransitions(stateKeys) + assertThat(stateOptional).isNotNull(); + + // Change state old_state_key on ctp + final String newKey = "new_state_key"; + stateService + .updateState(stateOptional.get(), Collections.singletonList(ChangeKey.of(newKey))) + .toCompletableFuture() + .join(); + + // Fetch cached id by old key + final Optional cachedStateId = + stateService.fetchCachedStateId(OLD_STATE_KEY).toCompletableFuture().join(); + + assertThat(cachedStateId).isNotEmpty(); + assertThat(cachedStateId).contains(stateOptional.get().getId()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingStatesByKeysWithTransitions_WithAnyExistingKeys_ShouldReturnASetOfStates() { + final String key = "state_key_Y"; + final State transitionState = + createState(CTP_TARGET_CLIENT, "state_transition_key_2", TRANSITION_STATE_TYPE, null); + final State fetchState = + createState(CTP_TARGET_CLIENT, key, TRANSITION_STATE_TYPE, transitionState); + + final Set stateKeys = new HashSet<>(); + stateKeys.add(key); + + final Set matchingStates = + stateService + .fetchMatchingStatesByKeysWithTransitions(stateKeys) .toCompletableFuture() .join(); - assertThat(matchingStates).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - - State matchedState = matchingStates.iterator().next(); - assertThat(matchedState.getId()).isEqualTo(fetchState.getId()); - assertThat(matchedState.getTransitions()).hasSize(1); - Reference transition = matchedState.getTransitions().iterator().next(); - assertThat(transition.getId()).isEqualTo(transitionState.getId()); - assertThat(transition.getObj()).isNotNull(); - assertThat(transition.getObj()).isEqualTo(transitionState); - - clearTransitions(CTP_TARGET_CLIENT, fetchState); - deleteStates(CTP_TARGET_CLIENT, TRANSITION_STATE_TYPE); - } - - @Test - void createState_WithValidState_ShouldCreateStateAndCacheId() { - final StateDraft newStateDraft = StateDraftBuilder.of( - STATE_KEY_1, - STATE_TYPE) + assertThat(matchingStates).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + + State matchedState = matchingStates.iterator().next(); + assertThat(matchedState.getId()).isEqualTo(fetchState.getId()); + assertThat(matchedState.getTransitions()).hasSize(1); + Reference transition = matchedState.getTransitions().iterator().next(); + assertThat(transition.getId()).isEqualTo(transitionState.getId()); + assertThat(transition.getObj()).isNotNull(); + assertThat(transition.getObj()).isEqualTo(transitionState); + + clearTransitions(CTP_TARGET_CLIENT, fetchState); + deleteStates(CTP_TARGET_CLIENT, TRANSITION_STATE_TYPE); + } + + @Test + void createState_WithValidState_ShouldCreateStateAndCacheId() { + final StateDraft newStateDraft = + StateDraftBuilder.of(STATE_KEY_1, STATE_TYPE) .name(STATE_NAME_1) .description(STATE_DESCRIPTION_1) .build(); - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final StateSyncOptions spyOptions = StateSyncOptionsBuilder - .of(spyClient) - .errorCallback( (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final StateSyncOptions spyOptions = + StateSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final StateService spyStateService = new StateServiceImpl(spyOptions); - - //test - final Optional createdState = spyStateService - .createState(newStateDraft) - .toCompletableFuture().join(); - - final Optional queriedOptional = CTP_TARGET_CLIENT - .execute(StateQuery.of().withPredicates(stateQueryModel -> - stateQueryModel.key().is(STATE_KEY_1))) - .toCompletableFuture().join().head(); - - assertThat(queriedOptional).hasValueSatisfying(queried -> - assertThat(createdState).hasValueSatisfying(created -> { - assertThat(created.getKey()).isEqualTo(queried.getKey()); - assertThat(created.getDescription()).isEqualTo(queried.getDescription()); - assertThat(created.getName()).isEqualTo(queried.getName()); - })); - - // Assert that the created state is cached - final Optional stateId = - spyStateService.fetchCachedStateId(STATE_KEY_1).toCompletableFuture().join(); - assertThat(stateId).isPresent(); - verify(spyClient, times(0)).execute(any(StateQuery.class)); - } - - @Test - void createState_WithInvalidState_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final StateDraft newStateDraft = StateDraftBuilder.of( - "", - STATE_TYPE) + final StateService spyStateService = new StateServiceImpl(spyOptions); + + // test + final Optional createdState = + spyStateService.createState(newStateDraft).toCompletableFuture().join(); + + final Optional queriedOptional = + CTP_TARGET_CLIENT + .execute( + StateQuery.of() + .withPredicates(stateQueryModel -> stateQueryModel.key().is(STATE_KEY_1))) + .toCompletableFuture() + .join() + .head(); + + assertThat(queriedOptional) + .hasValueSatisfying( + queried -> + assertThat(createdState) + .hasValueSatisfying( + created -> { + assertThat(created.getKey()).isEqualTo(queried.getKey()); + assertThat(created.getDescription()).isEqualTo(queried.getDescription()); + assertThat(created.getName()).isEqualTo(queried.getName()); + })); + + // Assert that the created state is cached + final Optional stateId = + spyStateService.fetchCachedStateId(STATE_KEY_1).toCompletableFuture().join(); + assertThat(stateId).isPresent(); + verify(spyClient, times(0)).execute(any(StateQuery.class)); + } + + @Test + void createState_WithInvalidState_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final StateDraft newStateDraft = + StateDraftBuilder.of("", STATE_TYPE) .name(STATE_NAME_1) .description(STATE_DESCRIPTION_1) .build(); - final StateSyncOptions options = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback( (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final StateSyncOptions options = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final StateService stateService = new StateServiceImpl(options); - - // test - final Optional result = - stateService.createState(newStateDraft).toCompletableFuture().join(); + final StateService stateService = new StateServiceImpl(options); - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); + // test + final Optional result = + stateService.createState(newStateDraft).toCompletableFuture().join(); - } + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); + } - @Test - void createState_WithDuplicateKey_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final StateDraft newStateDraft = StateDraftBuilder.of( - OLD_STATE_KEY, - STATE_TYPE) + @Test + void createState_WithDuplicateKey_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final StateDraft newStateDraft = + StateDraftBuilder.of(OLD_STATE_KEY, STATE_TYPE) .name(STATE_NAME_1) .description(STATE_DESCRIPTION_1) .build(); - final StateSyncOptions options = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback( (exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final StateSyncOptions options = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final StateService stateService = new StateServiceImpl(options); - - // test - final Optional result = - stateService.createState(newStateDraft).toCompletableFuture().join(); - - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("A duplicate value")); - - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> { - assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) exception; - - final List fieldErrors = errorResponseException - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); + final StateService stateService = new StateServiceImpl(options); + + // test + final Optional result = + stateService.createState(newStateDraft).toCompletableFuture().join(); + + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("A duplicate value")); + + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> { + assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) exception; + + final List fieldErrors = + errorResponseException.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); }); - } - - @Test - void updateState_WithValidChanges_ShouldUpdateStateCorrectly() { - final Optional stateOptional = CTP_TARGET_CLIENT - .execute(StateQuery.of() - .withPredicates(stateQueryModel -> stateQueryModel.key().is(OLD_STATE_KEY))) - .toCompletableFuture().join().head(); - assertThat(stateOptional).isNotNull(); - - final SetName setNameUpdateAction = SetName.of(ofEnglish("new_state_name")); - - final State updatedState = stateService.updateState(stateOptional.get(), singletonList(setNameUpdateAction)) - .toCompletableFuture().join(); - assertThat(updatedState).isNotNull(); - - final Optional updatedStateOptional = CTP_TARGET_CLIENT - .execute(StateQuery.of() - .withPredicates(stateQueryModel -> stateQueryModel.key().is(OLD_STATE_KEY))) - .toCompletableFuture().join().head(); - - assertThat(stateOptional).isNotEmpty(); - final State fetchedState = updatedStateOptional.get(); - assertThat(fetchedState.getKey()).isEqualTo(updatedState.getKey()); - assertThat(fetchedState.getDescription()).isEqualTo(updatedState.getDescription()); - assertThat(fetchedState.getName()).isEqualTo(updatedState.getName()); - } - - @Test - void updateState_WithInvalidChanges_ShouldCompleteExceptionally() { - final Optional stateOptional = CTP_TARGET_CLIENT - .execute(StateQuery.of() - .withPredicates(stateQueryModel -> stateQueryModel.key().is(OLD_STATE_KEY))) - .toCompletableFuture().join().head(); - assertThat(stateOptional).isNotNull(); - - final ChangeType changeTypeUpdateAction = ChangeType.of(null); - stateService.updateState(stateOptional.get(), singletonList(changeTypeUpdateAction)) - .exceptionally(exception -> { - assertThat(exception).isNotNull(); - assertThat(exception.getMessage()).contains("Request body does not contain valid JSON."); - return null; - }) - .toCompletableFuture().join(); - } - - @Test - void fetchState_WithExistingStateKey_ShouldFetchState() { - final Optional stateOptional = CTP_TARGET_CLIENT - .execute(StateQuery.of() - .withPredicates(stateQueryModel -> stateQueryModel.key().is(OLD_STATE_KEY))) - .toCompletableFuture().join().head(); - assertThat(stateOptional).isNotNull(); - - final Optional fetchedStateOptional = - executeBlocking(stateService.fetchState(OLD_STATE_KEY)); - assertThat(fetchedStateOptional).isEqualTo(stateOptional); - } - - @Test - void fetchState_WithBlankKey_ShouldNotFetchState() { - final Optional fetchedStateOptional = - executeBlocking(stateService.fetchState(StringUtils.EMPTY)); - assertThat(fetchedStateOptional).isEmpty(); - } - - @Test - void fetchState_WithNullKey_ShouldNotFetchState() { - final Optional fetchedStateOptional = - executeBlocking(stateService.fetchState(null)); - assertThat(fetchedStateOptional).isEmpty(); - } + } + + @Test + void updateState_WithValidChanges_ShouldUpdateStateCorrectly() { + final Optional stateOptional = + CTP_TARGET_CLIENT + .execute( + StateQuery.of() + .withPredicates(stateQueryModel -> stateQueryModel.key().is(OLD_STATE_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(stateOptional).isNotNull(); + + final SetName setNameUpdateAction = SetName.of(ofEnglish("new_state_name")); + final State updatedState = + stateService + .updateState(stateOptional.get(), singletonList(setNameUpdateAction)) + .toCompletableFuture() + .join(); + assertThat(updatedState).isNotNull(); + + final Optional updatedStateOptional = + CTP_TARGET_CLIENT + .execute( + StateQuery.of() + .withPredicates(stateQueryModel -> stateQueryModel.key().is(OLD_STATE_KEY))) + .toCompletableFuture() + .join() + .head(); + + assertThat(stateOptional).isNotEmpty(); + final State fetchedState = updatedStateOptional.get(); + assertThat(fetchedState.getKey()).isEqualTo(updatedState.getKey()); + assertThat(fetchedState.getDescription()).isEqualTo(updatedState.getDescription()); + assertThat(fetchedState.getName()).isEqualTo(updatedState.getName()); + } + + @Test + void updateState_WithInvalidChanges_ShouldCompleteExceptionally() { + final Optional stateOptional = + CTP_TARGET_CLIENT + .execute( + StateQuery.of() + .withPredicates(stateQueryModel -> stateQueryModel.key().is(OLD_STATE_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(stateOptional).isNotNull(); + + final ChangeType changeTypeUpdateAction = ChangeType.of(null); + stateService + .updateState(stateOptional.get(), singletonList(changeTypeUpdateAction)) + .exceptionally( + exception -> { + assertThat(exception).isNotNull(); + assertThat(exception.getMessage()) + .contains("Request body does not contain valid JSON."); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void fetchState_WithExistingStateKey_ShouldFetchState() { + final Optional stateOptional = + CTP_TARGET_CLIENT + .execute( + StateQuery.of() + .withPredicates(stateQueryModel -> stateQueryModel.key().is(OLD_STATE_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(stateOptional).isNotNull(); + + final Optional fetchedStateOptional = + executeBlocking(stateService.fetchState(OLD_STATE_KEY)); + assertThat(fetchedStateOptional).isEqualTo(stateOptional); + } + + @Test + void fetchState_WithBlankKey_ShouldNotFetchState() { + final Optional fetchedStateOptional = + executeBlocking(stateService.fetchState(StringUtils.EMPTY)); + assertThat(fetchedStateOptional).isEmpty(); + } + + @Test + void fetchState_WithNullKey_ShouldNotFetchState() { + final Optional fetchedStateOptional = executeBlocking(stateService.fetchState(null)); + assertThat(fetchedStateOptional).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/TaxCategoryServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/TaxCategoryServiceImplIT.java index 888d9517fe..8c254ca8be 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/TaxCategoryServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/TaxCategoryServiceImplIT.java @@ -1,5 +1,24 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.TAXCATEGORY_DESCRIPTION_1; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.TAXCATEGORY_KEY; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.TAXCATEGORY_KEY_1; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.TAXCATEGORY_NAME_1; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.createTaxCategory; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.createTaxRateDraft; +import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.deleteTaxCategories; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.services.TaxCategoryService; import com.commercetools.sync.services.impl.TaxCategoryServiceImpl; import com.commercetools.sync.taxcategories.TaxCategorySyncOptions; @@ -15,410 +34,433 @@ import io.sphere.sdk.taxcategories.commands.updateactions.SetKey; import io.sphere.sdk.taxcategories.queries.TaxCategoryQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.TAXCATEGORY_DESCRIPTION_1; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.TAXCATEGORY_KEY; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.TAXCATEGORY_KEY_1; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.TAXCATEGORY_NAME_1; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.createTaxCategory; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.createTaxRateDraft; -import static com.commercetools.sync.integration.commons.utils.TaxCategoryITUtils.deleteTaxCategories; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class TaxCategoryServiceImplIT { - private TaxCategoryService taxCategoryService; - private TaxCategory oldTaxCategory; - private ArrayList warnings; - - private List errorCallBackMessages; - private List errorCallBackExceptions; - - /** - * Deletes tax categories from the target CTP projects, then it populates target CTP project with test data. - */ - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - - deleteTaxCategories(CTP_TARGET_CLIENT); - warnings = new ArrayList<>(); - oldTaxCategory = createTaxCategory(CTP_TARGET_CLIENT); - - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + private TaxCategoryService taxCategoryService; + private TaxCategory oldTaxCategory; + private ArrayList warnings; + + private List errorCallBackMessages; + private List errorCallBackExceptions; + + /** + * Deletes tax categories from the target CTP projects, then it populates target CTP project with + * test data. + */ + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + + deleteTaxCategories(CTP_TARGET_CLIENT); + warnings = new ArrayList<>(); + oldTaxCategory = createTaxCategory(CTP_TARGET_CLIENT); + + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - taxCategoryService = new TaxCategoryServiceImpl(taxCategorySyncOptions); - - } - - /** - * Cleans up the target and source test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteTaxCategories(CTP_TARGET_CLIENT); - } - - @Test - void fetchCachedTaxCategoryId_WithNonExistingTaxCategory_ShouldNotFetchATaxCategory() { - final Optional taxCategoryId = taxCategoryService + taxCategoryService = new TaxCategoryServiceImpl(taxCategorySyncOptions); + } + + /** Cleans up the target and source test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteTaxCategories(CTP_TARGET_CLIENT); + } + + @Test + void fetchCachedTaxCategoryId_WithNonExistingTaxCategory_ShouldNotFetchATaxCategory() { + final Optional taxCategoryId = + taxCategoryService .fetchCachedTaxCategoryId("non-existing-key") .toCompletableFuture() .join(); - assertThat(taxCategoryId).isEmpty(); - assertThat(warnings).isEmpty(); - } + assertThat(taxCategoryId).isEmpty(); + assertThat(warnings).isEmpty(); + } - @Test - void fetchCachedTaxCategoryId_WithExistingTaxCategory_ShouldFetchTaxCategoryAndCache() { - final Optional taxCategoryId = taxCategoryService + @Test + void fetchCachedTaxCategoryId_WithExistingTaxCategory_ShouldFetchTaxCategoryAndCache() { + final Optional taxCategoryId = + taxCategoryService .fetchCachedTaxCategoryId(oldTaxCategory.getKey()) .toCompletableFuture() .join(); - assertThat(taxCategoryId).isNotEmpty(); - assertThat(warnings).isEmpty(); - } - - @Test - void fetchCachedTaxCategoryId_WithNullKey_ShouldReturnFutureWithEmptyOptional() { - final Optional taxCategoryId = taxCategoryService - .fetchCachedTaxCategoryId(null) - .toCompletableFuture() - .join(); - - assertThat(taxCategoryId).isEmpty(); - assertThat(warnings).isEmpty(); - } - - @Test - void fetchMatchingTaxCategoriesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { - final Set taxCategoryKeys = new HashSet<>(); - final Set matchingTaxCategories = taxCategoryService + assertThat(taxCategoryId).isNotEmpty(); + assertThat(warnings).isEmpty(); + } + + @Test + void fetchCachedTaxCategoryId_WithNullKey_ShouldReturnFutureWithEmptyOptional() { + final Optional taxCategoryId = + taxCategoryService.fetchCachedTaxCategoryId(null).toCompletableFuture().join(); + + assertThat(taxCategoryId).isEmpty(); + assertThat(warnings).isEmpty(); + } + + @Test + void fetchMatchingTaxCategoriesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { + final Set taxCategoryKeys = new HashSet<>(); + final Set matchingTaxCategories = + taxCategoryService .fetchMatchingTaxCategoriesByKeys(taxCategoryKeys) .toCompletableFuture() .join(); - assertThat(matchingTaxCategories).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } + assertThat(matchingTaxCategories).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } - @Test - void fetchMatchingTaxCategoriesByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { - final Set taxCategoryKeys = new HashSet<>(); - taxCategoryKeys.add("taxCategory_key_1"); - taxCategoryKeys.add("taxCategory_key_2"); + @Test + void fetchMatchingTaxCategoriesByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { + final Set taxCategoryKeys = new HashSet<>(); + taxCategoryKeys.add("taxCategory_key_1"); + taxCategoryKeys.add("taxCategory_key_2"); - final Set matchingTaxCategories = taxCategoryService + final Set matchingTaxCategories = + taxCategoryService .fetchMatchingTaxCategoriesByKeys(taxCategoryKeys) .toCompletableFuture() .join(); - assertThat(matchingTaxCategories).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } + assertThat(matchingTaxCategories).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } - @Test - void fetchMatchingTaxCategoriesByKeys_WithAnyExistingKeys_ShouldReturnASetOfTaxCategories() { - final Set taxCategoryKeys = new HashSet<>(); - taxCategoryKeys.add(TAXCATEGORY_KEY); + @Test + void fetchMatchingTaxCategoriesByKeys_WithAnyExistingKeys_ShouldReturnASetOfTaxCategories() { + final Set taxCategoryKeys = new HashSet<>(); + taxCategoryKeys.add(TAXCATEGORY_KEY); - final Set matchingTaxCategories = taxCategoryService + final Set matchingTaxCategories = + taxCategoryService .fetchMatchingTaxCategoriesByKeys(taxCategoryKeys) .toCompletableFuture() .join(); - assertThat(matchingTaxCategories).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingTaxCategoriesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(TaxCategoryQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - - final TaxCategorySyncOptions spyOptions = - TaxCategorySyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); + assertThat(matchingTaxCategories).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingTaxCategoriesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(TaxCategoryQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + + final TaxCategorySyncOptions spyOptions = + TaxCategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }) - .build(); - - final TaxCategoryService spyTaxCategoryService = new TaxCategoryServiceImpl(spyOptions); + .build(); + final TaxCategoryService spyTaxCategoryService = new TaxCategoryServiceImpl(spyOptions); - final Set keys = new HashSet<>(); - keys.add(TAXCATEGORY_KEY); + final Set keys = new HashSet<>(); + keys.add(TAXCATEGORY_KEY); - // test and assert - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyTaxCategoryService.fetchMatchingTaxCategoriesByKeys(keys)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } + // test and assert + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyTaxCategoryService.fetchMatchingTaxCategoriesByKeys(keys)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } - @Test - void fetchMatchingTaxCategoriesByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedTaxCategoryIds() { - final Set fetchedTaxCategories = taxCategoryService + @Test + void + fetchMatchingTaxCategoriesByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedTaxCategoryIds() { + final Set fetchedTaxCategories = + taxCategoryService .fetchMatchingTaxCategoriesByKeys(singleton(TAXCATEGORY_KEY)) .toCompletableFuture() .join(); - assertThat(fetchedTaxCategories).hasSize(1); + assertThat(fetchedTaxCategories).hasSize(1); - final Optional taxCategoryOptional = CTP_TARGET_CLIENT - .execute(TaxCategoryQuery.of().withPredicates(queryModel -> queryModel.key().is(TAXCATEGORY_KEY))) + final Optional taxCategoryOptional = + CTP_TARGET_CLIENT + .execute( + TaxCategoryQuery.of() + .withPredicates(queryModel -> queryModel.key().is(TAXCATEGORY_KEY))) .toCompletableFuture() .join() .head(); - assertThat(taxCategoryOptional).isNotNull(); - - // Change taxCategory old_taxCategory_key on ctp - final String newKey = "new_taxCategory_key"; - taxCategoryService.updateTaxCategory(taxCategoryOptional.get(), Collections.singletonList(SetKey.of(newKey))) - .toCompletableFuture() - .join(); - - // Fetch cached id by old key - final Optional cachedTaxCategoryId = taxCategoryService.fetchCachedTaxCategoryId(TAXCATEGORY_KEY) - .toCompletableFuture() - .join(); - - assertThat(cachedTaxCategoryId).isNotEmpty(); - assertThat(cachedTaxCategoryId).contains(taxCategoryOptional.get().getId()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void createTaxCategory_WithValidTaxCategory_ShouldCreateTaxCategoryAndCacheId() { - final TaxCategoryDraft newTaxCategoryDraft = TaxCategoryDraftBuilder.of( - TAXCATEGORY_NAME_1, - singletonList(createTaxRateDraft()), - TAXCATEGORY_DESCRIPTION_1) - .key(TAXCATEGORY_KEY_1) - .build(); - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final TaxCategorySyncOptions spyOptions = TaxCategorySyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + assertThat(taxCategoryOptional).isNotNull(); + + // Change taxCategory old_taxCategory_key on ctp + final String newKey = "new_taxCategory_key"; + taxCategoryService + .updateTaxCategory(taxCategoryOptional.get(), Collections.singletonList(SetKey.of(newKey))) + .toCompletableFuture() + .join(); + + // Fetch cached id by old key + final Optional cachedTaxCategoryId = + taxCategoryService.fetchCachedTaxCategoryId(TAXCATEGORY_KEY).toCompletableFuture().join(); + + assertThat(cachedTaxCategoryId).isNotEmpty(); + assertThat(cachedTaxCategoryId).contains(taxCategoryOptional.get().getId()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void createTaxCategory_WithValidTaxCategory_ShouldCreateTaxCategoryAndCacheId() { + final TaxCategoryDraft newTaxCategoryDraft = + TaxCategoryDraftBuilder.of( + TAXCATEGORY_NAME_1, singletonList(createTaxRateDraft()), TAXCATEGORY_DESCRIPTION_1) + .key(TAXCATEGORY_KEY_1) .build(); - final TaxCategoryService spyTaxCategoryService = new TaxCategoryServiceImpl(spyOptions); - - //test - final Optional createdTaxCategory = spyTaxCategoryService - .createTaxCategory(newTaxCategoryDraft) - .toCompletableFuture().join(); - - final Optional queriedOptional = CTP_TARGET_CLIENT - .execute(TaxCategoryQuery.of().withPredicates(taxCategoryQueryModel -> - taxCategoryQueryModel.key().is(TAXCATEGORY_KEY_1))) - .toCompletableFuture().join().head(); - - assertThat(queriedOptional).hasValueSatisfying(queried -> - assertThat(createdTaxCategory).hasValueSatisfying(created -> { - assertThat(created.getKey()).isEqualTo(queried.getKey()); - assertThat(created.getDescription()).isEqualTo(queried.getDescription()); - assertThat(created.getName()).isEqualTo(queried.getName()); - })); - - // Assert that the created taxCategory is cached - final Optional taxCategoryId = - spyTaxCategoryService.fetchCachedTaxCategoryId(TAXCATEGORY_KEY_1).toCompletableFuture().join(); - assertThat(taxCategoryId).isPresent(); - verify(spyClient, times(0)).execute(any(TaxCategoryQuery.class)); - } - - @Test - void createTaxCategory_WithInvalidTaxCategory_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final TaxCategoryDraft newTaxCategoryDraft = TaxCategoryDraftBuilder.of( - TAXCATEGORY_NAME_1, - singletonList(createTaxRateDraft()), - TAXCATEGORY_DESCRIPTION_1) - .key("") - .build(); - - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final TaxCategorySyncOptions spyOptions = + TaxCategorySyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final TaxCategoryService taxCategoryService = new TaxCategoryServiceImpl(options); + final TaxCategoryService spyTaxCategoryService = new TaxCategoryServiceImpl(spyOptions); + + // test + final Optional createdTaxCategory = + spyTaxCategoryService.createTaxCategory(newTaxCategoryDraft).toCompletableFuture().join(); + + final Optional queriedOptional = + CTP_TARGET_CLIENT + .execute( + TaxCategoryQuery.of() + .withPredicates( + taxCategoryQueryModel -> taxCategoryQueryModel.key().is(TAXCATEGORY_KEY_1))) + .toCompletableFuture() + .join() + .head(); + + assertThat(queriedOptional) + .hasValueSatisfying( + queried -> + assertThat(createdTaxCategory) + .hasValueSatisfying( + created -> { + assertThat(created.getKey()).isEqualTo(queried.getKey()); + assertThat(created.getDescription()).isEqualTo(queried.getDescription()); + assertThat(created.getName()).isEqualTo(queried.getName()); + })); + + // Assert that the created taxCategory is cached + final Optional taxCategoryId = + spyTaxCategoryService + .fetchCachedTaxCategoryId(TAXCATEGORY_KEY_1) + .toCompletableFuture() + .join(); + assertThat(taxCategoryId).isPresent(); + verify(spyClient, times(0)).execute(any(TaxCategoryQuery.class)); + } + + @Test + void createTaxCategory_WithInvalidTaxCategory_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final TaxCategoryDraft newTaxCategoryDraft = + TaxCategoryDraftBuilder.of( + TAXCATEGORY_NAME_1, singletonList(createTaxRateDraft()), TAXCATEGORY_DESCRIPTION_1) + .key("") + .build(); - // test - final Optional result = - taxCategoryService.createTaxCategory(newTaxCategoryDraft).toCompletableFuture().join(); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); - } + final TaxCategoryService taxCategoryService = new TaxCategoryServiceImpl(options); - @Test - void createTaxCategory_WithDuplicateKey_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final TaxCategoryDraft newTaxCategoryDraft = TaxCategoryDraftBuilder - .of(TAXCATEGORY_NAME_1, singletonList(createTaxRateDraft()), TAXCATEGORY_DESCRIPTION_1) + // test + final Optional result = + taxCategoryService.createTaxCategory(newTaxCategoryDraft).toCompletableFuture().join(); + + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); + } + + @Test + void createTaxCategory_WithDuplicateKey_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final TaxCategoryDraft newTaxCategoryDraft = + TaxCategoryDraftBuilder.of( + TAXCATEGORY_NAME_1, singletonList(createTaxRateDraft()), TAXCATEGORY_DESCRIPTION_1) .key(TAXCATEGORY_KEY) .build(); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final TaxCategoryService taxCategoryService = new TaxCategoryServiceImpl(options); - - // test - final Optional result = - taxCategoryService.createTaxCategory(newTaxCategoryDraft).toCompletableFuture().join(); - - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("A duplicate value")); - - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> { - assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) exception; - - final List fieldErrors = errorResponseException - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - }); - } - - @Test - void updateTaxCategory_WithValidChanges_ShouldUpdateTaxCategoryCorrectly() { - final Optional taxCategoryOptional = CTP_TARGET_CLIENT - .execute(TaxCategoryQuery - .of() - .withPredicates(taxCategoryQueryModel -> taxCategoryQueryModel.key().is(TAXCATEGORY_KEY))) - .toCompletableFuture().join().head(); - assertThat(taxCategoryOptional).isNotNull(); - - final ChangeName changeNameUpdateAction = ChangeName.of("new_taxCategory_name"); - - final TaxCategory updatedTaxCategory = taxCategoryService.updateTaxCategory(taxCategoryOptional.get(), - singletonList(changeNameUpdateAction)) - .toCompletableFuture().join(); - assertThat(updatedTaxCategory).isNotNull(); - - final Optional updatedTaxCategoryOptional = CTP_TARGET_CLIENT - .execute(TaxCategoryQuery - .of() - .withPredicates(taxCategoryQueryModel -> taxCategoryQueryModel.key().is(TAXCATEGORY_KEY))) - .toCompletableFuture().join().head(); - - assertThat(taxCategoryOptional).isNotEmpty(); - final TaxCategory fetchedTaxCategory = updatedTaxCategoryOptional.get(); - assertThat(fetchedTaxCategory.getKey()).isEqualTo(updatedTaxCategory.getKey()); - assertThat(fetchedTaxCategory.getDescription()).isEqualTo(updatedTaxCategory.getDescription()); - assertThat(fetchedTaxCategory.getName()).isEqualTo(updatedTaxCategory.getName()); - } - - @Test - void updateTaxCategory_WithInvalidChanges_ShouldCompleteExceptionally() { - final Optional taxCategoryOptional = CTP_TARGET_CLIENT - .execute(TaxCategoryQuery - .of() - .withPredicates(taxCategoryQueryModel -> taxCategoryQueryModel.key().is(TAXCATEGORY_KEY))) - .toCompletableFuture().join().head(); - assertThat(taxCategoryOptional).isNotNull(); - - final ChangeName changeNameUpdateAction = ChangeName.of(null); - taxCategoryService.updateTaxCategory(taxCategoryOptional.get(), singletonList(changeNameUpdateAction)) - .exceptionally(exception -> { - assertThat(exception).isNotNull(); - assertThat(exception.getMessage()).contains("Request body does not contain valid JSON."); - return null; + final TaxCategoryService taxCategoryService = new TaxCategoryServiceImpl(options); + + // test + final Optional result = + taxCategoryService.createTaxCategory(newTaxCategoryDraft).toCompletableFuture().join(); + + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("A duplicate value")); + + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> { + assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) exception; + + final List fieldErrors = + errorResponseException.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); }) - .toCompletableFuture().join(); - } - - @Test - void fetchTaxCategory_WithExistingTaxCategoryKey_ShouldFetchTaxCategory() { - final Optional taxCategoryOptional = CTP_TARGET_CLIENT - .execute(TaxCategoryQuery - .of() - .withPredicates(taxCategoryQueryModel -> taxCategoryQueryModel.key().is(TAXCATEGORY_KEY))) - .toCompletableFuture().join().head(); - assertThat(taxCategoryOptional).isNotNull(); - - final Optional fetchedTaxCategoryOptional = - executeBlocking(taxCategoryService.fetchTaxCategory(TAXCATEGORY_KEY)); - assertThat(fetchedTaxCategoryOptional).isEqualTo(taxCategoryOptional); - } - - @Test - void fetchTaxCategory_WithBlankKey_ShouldNotFetchTaxCategory() { - final Optional fetchedTaxCategoryOptional = - executeBlocking(taxCategoryService.fetchTaxCategory(StringUtils.EMPTY)); - assertThat(fetchedTaxCategoryOptional).isEmpty(); - } - - @Test - void fetchTaxCategory_WithNullKey_ShouldNotFetchTaxCategory() { - final Optional fetchedTaxCategoryOptional = - executeBlocking(taxCategoryService.fetchTaxCategory(null)); - assertThat(fetchedTaxCategoryOptional).isEmpty(); - } + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + }); + } + + @Test + void updateTaxCategory_WithValidChanges_ShouldUpdateTaxCategoryCorrectly() { + final Optional taxCategoryOptional = + CTP_TARGET_CLIENT + .execute( + TaxCategoryQuery.of() + .withPredicates( + taxCategoryQueryModel -> taxCategoryQueryModel.key().is(TAXCATEGORY_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(taxCategoryOptional).isNotNull(); + final ChangeName changeNameUpdateAction = ChangeName.of("new_taxCategory_name"); + + final TaxCategory updatedTaxCategory = + taxCategoryService + .updateTaxCategory(taxCategoryOptional.get(), singletonList(changeNameUpdateAction)) + .toCompletableFuture() + .join(); + assertThat(updatedTaxCategory).isNotNull(); + + final Optional updatedTaxCategoryOptional = + CTP_TARGET_CLIENT + .execute( + TaxCategoryQuery.of() + .withPredicates( + taxCategoryQueryModel -> taxCategoryQueryModel.key().is(TAXCATEGORY_KEY))) + .toCompletableFuture() + .join() + .head(); + + assertThat(taxCategoryOptional).isNotEmpty(); + final TaxCategory fetchedTaxCategory = updatedTaxCategoryOptional.get(); + assertThat(fetchedTaxCategory.getKey()).isEqualTo(updatedTaxCategory.getKey()); + assertThat(fetchedTaxCategory.getDescription()).isEqualTo(updatedTaxCategory.getDescription()); + assertThat(fetchedTaxCategory.getName()).isEqualTo(updatedTaxCategory.getName()); + } + + @Test + void updateTaxCategory_WithInvalidChanges_ShouldCompleteExceptionally() { + final Optional taxCategoryOptional = + CTP_TARGET_CLIENT + .execute( + TaxCategoryQuery.of() + .withPredicates( + taxCategoryQueryModel -> taxCategoryQueryModel.key().is(TAXCATEGORY_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(taxCategoryOptional).isNotNull(); + + final ChangeName changeNameUpdateAction = ChangeName.of(null); + taxCategoryService + .updateTaxCategory(taxCategoryOptional.get(), singletonList(changeNameUpdateAction)) + .exceptionally( + exception -> { + assertThat(exception).isNotNull(); + assertThat(exception.getMessage()) + .contains("Request body does not contain valid JSON."); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void fetchTaxCategory_WithExistingTaxCategoryKey_ShouldFetchTaxCategory() { + final Optional taxCategoryOptional = + CTP_TARGET_CLIENT + .execute( + TaxCategoryQuery.of() + .withPredicates( + taxCategoryQueryModel -> taxCategoryQueryModel.key().is(TAXCATEGORY_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(taxCategoryOptional).isNotNull(); + + final Optional fetchedTaxCategoryOptional = + executeBlocking(taxCategoryService.fetchTaxCategory(TAXCATEGORY_KEY)); + assertThat(fetchedTaxCategoryOptional).isEqualTo(taxCategoryOptional); + } + + @Test + void fetchTaxCategory_WithBlankKey_ShouldNotFetchTaxCategory() { + final Optional fetchedTaxCategoryOptional = + executeBlocking(taxCategoryService.fetchTaxCategory(StringUtils.EMPTY)); + assertThat(fetchedTaxCategoryOptional).isEmpty(); + } + + @Test + void fetchTaxCategory_WithNullKey_ShouldNotFetchTaxCategory() { + final Optional fetchedTaxCategoryOptional = + executeBlocking(taxCategoryService.fetchTaxCategory(null)); + assertThat(fetchedTaxCategoryOptional).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/TypeServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/TypeServiceImplIT.java index 0d7b858dcb..e9a3e2a4c3 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/TypeServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/TypeServiceImplIT.java @@ -1,5 +1,24 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; +import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_DESCRIPTION_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_KEY_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_NAME_1; +import static com.commercetools.sync.integration.commons.utils.TypeITUtils.deleteTypes; +import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.commercetools.sync.services.TypeService; import com.commercetools.sync.services.impl.TypeServiceImpl; @@ -17,11 +36,6 @@ import io.sphere.sdk.types.commands.updateactions.ChangeName; import io.sphere.sdk.types.queries.TypeQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -29,385 +43,388 @@ import java.util.Locale; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.createCategoriesCustomType; -import static com.commercetools.sync.integration.commons.utils.CategoryITUtils.deleteAllCategories; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.FIELD_DEFINITION_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_DESCRIPTION_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_KEY_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.TYPE_NAME_1; -import static com.commercetools.sync.integration.commons.utils.TypeITUtils.deleteTypes; -import static com.commercetools.tests.utils.CompletionStageUtil.executeBlocking; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class TypeServiceImplIT { - private TypeService typeService; - private static final String OLD_TYPE_KEY = "old_type_key"; - private static final String OLD_TYPE_NAME = "old_type_name"; - private static final Locale OLD_TYPE_LOCALE = Locale.ENGLISH; - - private List errorCallBackMessages; - private List errorCallBackExceptions; - - /** - * Deletes types from the target CTP project, then it populates the project with test data. - */ - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - - deleteAllCategories(CTP_TARGET_CLIENT); - deleteTypes(CTP_TARGET_CLIENT); - createCategoriesCustomType(OLD_TYPE_KEY, OLD_TYPE_LOCALE, OLD_TYPE_NAME, CTP_TARGET_CLIENT); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + private TypeService typeService; + private static final String OLD_TYPE_KEY = "old_type_key"; + private static final String OLD_TYPE_NAME = "old_type_name"; + private static final Locale OLD_TYPE_LOCALE = Locale.ENGLISH; + + private List errorCallBackMessages; + private List errorCallBackExceptions; + + /** Deletes types from the target CTP project, then it populates the project with test data. */ + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + + deleteAllCategories(CTP_TARGET_CLIENT); + deleteTypes(CTP_TARGET_CLIENT); + createCategoriesCustomType(OLD_TYPE_KEY, OLD_TYPE_LOCALE, OLD_TYPE_NAME, CTP_TARGET_CLIENT); + + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - typeService = new TypeServiceImpl(typeSyncOptions); - } - - /** - * Cleans up the target test data that were built in this test class. - */ - @AfterAll - static void tearDown() { - deleteTypes(CTP_TARGET_CLIENT); - } - - @Test - void fetchCachedTypeId_WithNonExistingType_ShouldNotFetchAType() { - final Optional typeId = typeService.fetchCachedTypeId("non-existing-type-key") - .toCompletableFuture() - .join(); - assertThat(typeId).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchCachedTypeId_WithExistingType_ShouldFetchTypeAndCache() { - final Optional typeId = typeService.fetchCachedTypeId(OLD_TYPE_KEY) - .toCompletableFuture() - .join(); - assertThat(typeId).isNotEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingTypesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { - final Set typeKeys = new HashSet<>(); - final Set matchingTypes = typeService.fetchMatchingTypesByKeys(typeKeys) - .toCompletableFuture() - .join(); - - assertThat(matchingTypes).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingTypesByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { - final Set typeKeys = new HashSet<>(); - typeKeys.add("type_key_1"); - typeKeys.add("type_key_2"); - - final Set matchingTypes = typeService.fetchMatchingTypesByKeys(typeKeys) - .toCompletableFuture() - .join(); - - assertThat(matchingTypes).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingTypesByKeys_WithAnyExistingKeys_ShouldReturnASetOfTypes() { - final Set typeKeys = new HashSet<>(); - typeKeys.add(OLD_TYPE_KEY); - - final Set matchingTypes = typeService.fetchMatchingTypesByKeys(typeKeys) - .toCompletableFuture() - .join(); - - assertThat(matchingTypes).hasSize(1); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void fetchMatchingTypesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { - // Mock sphere client to return BadGatewayException on any request. - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - when(spyClient.execute(any(TypeQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) - .thenCallRealMethod(); - - final TypeSyncOptions spyOptions = - TypeSyncOptionsBuilder.of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .build(); - - final TypeService spyTypeService = new TypeServiceImpl(spyOptions); - - - final Set keys = new HashSet<>(); - keys.add(OLD_TYPE_KEY); - - // test and assert - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(spyTypeService.fetchMatchingTypesByKeys(keys)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - } - - @Test - void fetchMatchingTypesByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedTypeIds() { - final Set fetchedTypes = typeService.fetchMatchingTypesByKeys(singleton(OLD_TYPE_KEY)) - .toCompletableFuture().join(); - assertThat(fetchedTypes).hasSize(1); - - final Optional typeOptional = CTP_TARGET_CLIENT - .execute(TypeQuery.of().withPredicates(queryModel -> queryModel.key().is(OLD_TYPE_KEY))) - .toCompletableFuture() - .join() - .head(); - - assertThat(typeOptional).isNotNull(); - - // Change type old_type_key on ctp - final String newKey = "new_type_key"; - typeService.updateType(typeOptional.get(), Collections.singletonList(ChangeKey.of(newKey))) - .toCompletableFuture() - .join(); - - // Fetch cached id by old key - final Optional cachedTypeId = typeService.fetchCachedTypeId(OLD_TYPE_KEY) - .toCompletableFuture() - .join(); - - assertThat(cachedTypeId).isNotEmpty(); - assertThat(cachedTypeId).contains(typeOptional.get().getId()); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void createType_WithValidType_ShouldCreateTypeAndCacheId() { - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - TYPE_KEY_1, - TYPE_NAME_1, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) - .build(); - - final SphereClient spyClient = spy(CTP_TARGET_CLIENT); - final TypeSyncOptions spyOptions = TypeSyncOptionsBuilder - .of(spyClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + typeService = new TypeServiceImpl(typeSyncOptions); + } + + /** Cleans up the target test data that were built in this test class. */ + @AfterAll + static void tearDown() { + deleteTypes(CTP_TARGET_CLIENT); + } + + @Test + void fetchCachedTypeId_WithNonExistingType_ShouldNotFetchAType() { + final Optional typeId = + typeService.fetchCachedTypeId("non-existing-type-key").toCompletableFuture().join(); + assertThat(typeId).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchCachedTypeId_WithExistingType_ShouldFetchTypeAndCache() { + final Optional typeId = + typeService.fetchCachedTypeId(OLD_TYPE_KEY).toCompletableFuture().join(); + assertThat(typeId).isNotEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingTypesByKeys_WithEmptySetOfKeys_ShouldReturnEmptySet() { + final Set typeKeys = new HashSet<>(); + final Set matchingTypes = + typeService.fetchMatchingTypesByKeys(typeKeys).toCompletableFuture().join(); + + assertThat(matchingTypes).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingTypesByKeys_WithNonExistingKeys_ShouldReturnEmptySet() { + final Set typeKeys = new HashSet<>(); + typeKeys.add("type_key_1"); + typeKeys.add("type_key_2"); + + final Set matchingTypes = + typeService.fetchMatchingTypesByKeys(typeKeys).toCompletableFuture().join(); + + assertThat(matchingTypes).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingTypesByKeys_WithAnyExistingKeys_ShouldReturnASetOfTypes() { + final Set typeKeys = new HashSet<>(); + typeKeys.add(OLD_TYPE_KEY); + + final Set matchingTypes = + typeService.fetchMatchingTypesByKeys(typeKeys).toCompletableFuture().join(); + + assertThat(matchingTypes).hasSize(1); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void fetchMatchingTypesByKeys_WithBadGateWayExceptionAlways_ShouldFail() { + // Mock sphere client to return BadGatewayException on any request. + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + when(spyClient.execute(any(TypeQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())) + .thenCallRealMethod(); + + final TypeSyncOptions spyOptions = + TypeSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final TypeService spyTypeService = new TypeServiceImpl(spyOptions); - - //test - final Optional createdType = spyTypeService - .createType(newTypeDraft) - .toCompletableFuture().join(); - - final Optional queriedOptional = CTP_TARGET_CLIENT - .execute(TypeQuery.of().withPredicates(typeQueryModel -> - typeQueryModel.key().is(TYPE_KEY_1))) - .toCompletableFuture().join().head(); - - assertThat(queriedOptional).hasValueSatisfying(queried -> - assertThat(createdType).hasValueSatisfying(created -> { - assertThat(created.getKey()).isEqualTo(queried.getKey()); - assertThat(created.getDescription()).isEqualTo(queried.getDescription()); - assertThat(created.getName()).isEqualTo(queried.getName()); - assertThat(created.getFieldDefinitions()).isEqualTo(queried.getFieldDefinitions()); - })); - - // Assert that the created type is cached - final Optional typeId = - spyTypeService.fetchCachedTypeId(TYPE_KEY_1).toCompletableFuture().join(); - assertThat(typeId).isPresent(); - verify(spyClient, times(0)).execute(any(TypeQuery.class)); - } - - @Test - void createType_WithInvalidType_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - "", - TYPE_NAME_1, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) - .build(); - - final TypeSyncOptions options = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final TypeService spyTypeService = new TypeServiceImpl(spyOptions); + + final Set keys = new HashSet<>(); + keys.add(OLD_TYPE_KEY); + + // test and assert + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(spyTypeService.fetchMatchingTypesByKeys(keys)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + } + + @Test + void fetchMatchingTypesByKeys_WithAllExistingSetOfKeys_ShouldCacheFetchedTypeIds() { + final Set fetchedTypes = + typeService.fetchMatchingTypesByKeys(singleton(OLD_TYPE_KEY)).toCompletableFuture().join(); + assertThat(fetchedTypes).hasSize(1); + + final Optional typeOptional = + CTP_TARGET_CLIENT + .execute(TypeQuery.of().withPredicates(queryModel -> queryModel.key().is(OLD_TYPE_KEY))) + .toCompletableFuture() + .join() + .head(); + + assertThat(typeOptional).isNotNull(); + + // Change type old_type_key on ctp + final String newKey = "new_type_key"; + typeService + .updateType(typeOptional.get(), Collections.singletonList(ChangeKey.of(newKey))) + .toCompletableFuture() + .join(); + + // Fetch cached id by old key + final Optional cachedTypeId = + typeService.fetchCachedTypeId(OLD_TYPE_KEY).toCompletableFuture().join(); + + assertThat(cachedTypeId).isNotEmpty(); + assertThat(cachedTypeId).contains(typeOptional.get().getId()); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void createType_WithValidType_ShouldCreateTypeAndCacheId() { + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + TYPE_KEY_1, TYPE_NAME_1, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) .build(); - final TypeService typeService = new TypeServiceImpl(options); - - // test - final Optional result = - typeService.createType(newTypeDraft).toCompletableFuture().join(); - - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); - - } - - @Test - void createType_WithDuplicateKey_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - OLD_TYPE_KEY, - TYPE_NAME_1, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION_1) - .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) - .build(); - - final TypeSyncOptions options = TypeSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) + final SphereClient spyClient = spy(CTP_TARGET_CLIENT); + final TypeSyncOptions spyOptions = + TypeSyncOptionsBuilder.of(spyClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) .build(); - final TypeService typeService = new TypeServiceImpl(options); - - // test - final Optional result = - typeService.createType(newTypeDraft).toCompletableFuture().join(); - - // assertion - assertThat(result).isEmpty(); - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("A duplicate value")); - - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> { - assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); - final ErrorResponseException errorResponseException = (ErrorResponseException) exception; - - final List fieldErrors = errorResponseException - .getErrors() - .stream() - .map(sphereError -> { - assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); - return sphereError.as(DuplicateFieldError.class); - }) - .collect(toList()); - assertThat(fieldErrors).hasSize(1); - }); - } - - @Test - void updateType_WithValidChanges_ShouldUpdateTypeCorrectly() { - final Optional typeOptional = CTP_TARGET_CLIENT - .execute(TypeQuery.of() - .withPredicates(typeQueryModel -> typeQueryModel.key().is(OLD_TYPE_KEY))) - .toCompletableFuture().join().head(); - assertThat(typeOptional).isNotNull(); - - final ChangeName changeNameUpdateAction = ChangeName.of(ofEnglish("new_type_name")); - - final Type updatedType = typeService.updateType(typeOptional.get(), singletonList(changeNameUpdateAction)) - .toCompletableFuture().join(); - assertThat(updatedType).isNotNull(); - - final Optional updatedTypeOptional = CTP_TARGET_CLIENT - .execute(TypeQuery.of() - .withPredicates(typeQueryModel -> typeQueryModel.key().is(OLD_TYPE_KEY))) - .toCompletableFuture().join().head(); - - assertThat(typeOptional).isNotEmpty(); - final Type fetchedType = updatedTypeOptional.get(); - assertThat(fetchedType.getKey()).isEqualTo(updatedType.getKey()); - assertThat(fetchedType.getDescription()).isEqualTo(updatedType.getDescription()); - assertThat(fetchedType.getName()).isEqualTo(updatedType.getName()); - assertThat(fetchedType.getFieldDefinitions()).isEqualTo(updatedType.getFieldDefinitions()); - } - - @Test - void updateType_WithInvalidChanges_ShouldCompleteExceptionally() { - final Optional typeOptional = CTP_TARGET_CLIENT - .execute(TypeQuery.of() - .withPredicates(typeQueryModel -> typeQueryModel.key().is(OLD_TYPE_KEY))) - .toCompletableFuture().join().head(); - assertThat(typeOptional).isNotNull(); - - final ChangeName changeNameUpdateAction = ChangeName.of(null); - typeService.updateType(typeOptional.get(), singletonList(changeNameUpdateAction)) - .exceptionally(exception -> { - assertThat(exception).isNotNull(); - assertThat(exception.getMessage()).contains("Request body does not contain valid JSON."); - return null; - }) - .toCompletableFuture().join(); - } - - @Test - void fetchType_WithExistingTypeKey_ShouldFetchType() { - final Optional typeOptional = CTP_TARGET_CLIENT - .execute(TypeQuery.of() - .withPredicates(typeQueryModel -> typeQueryModel.key().is(OLD_TYPE_KEY))) - .toCompletableFuture().join().head(); - assertThat(typeOptional).isNotNull(); - - final Optional fetchedTypeOptional = - executeBlocking(typeService.fetchType(OLD_TYPE_KEY)); - assertThat(fetchedTypeOptional).isEqualTo(typeOptional); - } - - @Test - void fetchType_WithBlankKey_ShouldNotFetchType() { - final Optional fetchedTypeOptional = - executeBlocking(typeService.fetchType(StringUtils.EMPTY)); - assertThat(fetchedTypeOptional).isEmpty(); - } - - @Test - void fetchType_WithNullKey_ShouldNotFetchType() { - final Optional fetchedTypeOptional = - executeBlocking(typeService.fetchType(null)); - assertThat(fetchedTypeOptional).isEmpty(); - } + final TypeService spyTypeService = new TypeServiceImpl(spyOptions); + + // test + final Optional createdType = + spyTypeService.createType(newTypeDraft).toCompletableFuture().join(); + + final Optional queriedOptional = + CTP_TARGET_CLIENT + .execute( + TypeQuery.of() + .withPredicates(typeQueryModel -> typeQueryModel.key().is(TYPE_KEY_1))) + .toCompletableFuture() + .join() + .head(); + + assertThat(queriedOptional) + .hasValueSatisfying( + queried -> + assertThat(createdType) + .hasValueSatisfying( + created -> { + assertThat(created.getKey()).isEqualTo(queried.getKey()); + assertThat(created.getDescription()).isEqualTo(queried.getDescription()); + assertThat(created.getName()).isEqualTo(queried.getName()); + assertThat(created.getFieldDefinitions()) + .isEqualTo(queried.getFieldDefinitions()); + })); + + // Assert that the created type is cached + final Optional typeId = + spyTypeService.fetchCachedTypeId(TYPE_KEY_1).toCompletableFuture().join(); + assertThat(typeId).isPresent(); + verify(spyClient, times(0)).execute(any(TypeQuery.class)); + } + + @Test + void createType_WithInvalidType_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final TypeDraft newTypeDraft = + TypeDraftBuilder.of("", TYPE_NAME_1, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) + .build(); + final TypeSyncOptions options = + TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); + + final TypeService typeService = new TypeServiceImpl(options); + + // test + final Optional result = typeService.createType(newTypeDraft).toCompletableFuture().join(); + + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); + } + + @Test + void createType_WithDuplicateKey_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + OLD_TYPE_KEY, TYPE_NAME_1, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION_1) + .fieldDefinitions(singletonList(FIELD_DEFINITION_1)) + .build(); + + final TypeSyncOptions options = + TypeSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); + + final TypeService typeService = new TypeServiceImpl(options); + + // test + final Optional result = typeService.createType(newTypeDraft).toCompletableFuture().join(); + + // assertion + assertThat(result).isEmpty(); + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying(msg -> assertThat(msg).contains("A duplicate value")); + + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> { + assertThat(exception).isExactlyInstanceOf(ErrorResponseException.class); + final ErrorResponseException errorResponseException = + (ErrorResponseException) exception; + + final List fieldErrors = + errorResponseException.getErrors().stream() + .map( + sphereError -> { + assertThat(sphereError.getCode()).isEqualTo(DuplicateFieldError.CODE); + return sphereError.as(DuplicateFieldError.class); + }) + .collect(toList()); + assertThat(fieldErrors).hasSize(1); + }); + } + + @Test + void updateType_WithValidChanges_ShouldUpdateTypeCorrectly() { + final Optional typeOptional = + CTP_TARGET_CLIENT + .execute( + TypeQuery.of() + .withPredicates(typeQueryModel -> typeQueryModel.key().is(OLD_TYPE_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(typeOptional).isNotNull(); + + final ChangeName changeNameUpdateAction = ChangeName.of(ofEnglish("new_type_name")); + + final Type updatedType = + typeService + .updateType(typeOptional.get(), singletonList(changeNameUpdateAction)) + .toCompletableFuture() + .join(); + assertThat(updatedType).isNotNull(); + + final Optional updatedTypeOptional = + CTP_TARGET_CLIENT + .execute( + TypeQuery.of() + .withPredicates(typeQueryModel -> typeQueryModel.key().is(OLD_TYPE_KEY))) + .toCompletableFuture() + .join() + .head(); + + assertThat(typeOptional).isNotEmpty(); + final Type fetchedType = updatedTypeOptional.get(); + assertThat(fetchedType.getKey()).isEqualTo(updatedType.getKey()); + assertThat(fetchedType.getDescription()).isEqualTo(updatedType.getDescription()); + assertThat(fetchedType.getName()).isEqualTo(updatedType.getName()); + assertThat(fetchedType.getFieldDefinitions()).isEqualTo(updatedType.getFieldDefinitions()); + } + + @Test + void updateType_WithInvalidChanges_ShouldCompleteExceptionally() { + final Optional typeOptional = + CTP_TARGET_CLIENT + .execute( + TypeQuery.of() + .withPredicates(typeQueryModel -> typeQueryModel.key().is(OLD_TYPE_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(typeOptional).isNotNull(); + + final ChangeName changeNameUpdateAction = ChangeName.of(null); + typeService + .updateType(typeOptional.get(), singletonList(changeNameUpdateAction)) + .exceptionally( + exception -> { + assertThat(exception).isNotNull(); + assertThat(exception.getMessage()) + .contains("Request body does not contain valid JSON."); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void fetchType_WithExistingTypeKey_ShouldFetchType() { + final Optional typeOptional = + CTP_TARGET_CLIENT + .execute( + TypeQuery.of() + .withPredicates(typeQueryModel -> typeQueryModel.key().is(OLD_TYPE_KEY))) + .toCompletableFuture() + .join() + .head(); + assertThat(typeOptional).isNotNull(); + + final Optional fetchedTypeOptional = executeBlocking(typeService.fetchType(OLD_TYPE_KEY)); + assertThat(fetchedTypeOptional).isEqualTo(typeOptional); + } + + @Test + void fetchType_WithBlankKey_ShouldNotFetchType() { + final Optional fetchedTypeOptional = + executeBlocking(typeService.fetchType(StringUtils.EMPTY)); + assertThat(fetchedTypeOptional).isEmpty(); + } + + @Test + void fetchType_WithNullKey_ShouldNotFetchType() { + final Optional fetchedTypeOptional = executeBlocking(typeService.fetchType(null)); + assertThat(fetchedTypeOptional).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedReferencesServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedReferencesServiceImplIT.java index 9154645d9f..5973dba396 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedReferencesServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedReferencesServiceImplIT.java @@ -1,5 +1,15 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedCustomObjects; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_SPECIAL_CHARS_RESOURCE_PATH; +import static com.commercetools.sync.services.impl.UnresolvedReferencesServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.util.Collections.singleton; +import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.models.WaitingToBeResolved; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -9,199 +19,191 @@ import io.sphere.sdk.customobjects.queries.CustomObjectByKeyGet; import io.sphere.sdk.json.SphereJsonUtils; import io.sphere.sdk.products.ProductDraft; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedCustomObjects; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_SPECIAL_CHARS_RESOURCE_PATH; -import static com.commercetools.sync.services.impl.UnresolvedReferencesServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY; -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.util.Collections.singleton; -import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class UnresolvedReferencesServiceImplIT { - private UnresolvedReferencesService unresolvedReferencesService; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - @AfterEach - void tearDown() { - deleteWaitingToBeResolvedCustomObjects(CTP_TARGET_CLIENT); - } - - @BeforeEach - void setupTest() { - deleteWaitingToBeResolvedCustomObjects(CTP_TARGET_CLIENT); - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception); - }) - .warningCallback((syncException, productDraft, product) - -> warningCallBackMessages.add(syncException.getMessage())) + private UnresolvedReferencesService unresolvedReferencesService; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + @AfterEach + void tearDown() { + deleteWaitingToBeResolvedCustomObjects(CTP_TARGET_CLIENT); + } + + @BeforeEach + void setupTest() { + deleteWaitingToBeResolvedCustomObjects(CTP_TARGET_CLIENT); + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception); + }) + .warningCallback( + (syncException, productDraft, product) -> + warningCallBackMessages.add(syncException.getMessage())) .build(); - unresolvedReferencesService = new UnresolvedReferencesServiceImpl(productSyncOptions); - } + unresolvedReferencesService = new UnresolvedReferencesServiceImpl(productSyncOptions); + } - @Test - void saveFetchAndDelete_WithoutExceptions_shouldWorkCorrectly() { - // preparation - final ProductDraft productDraft = - SphereJsonUtils.readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, ProductDraft.class); + @Test + void saveFetchAndDelete_WithoutExceptions_shouldWorkCorrectly() { + // preparation + final ProductDraft productDraft = + SphereJsonUtils.readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, ProductDraft.class); - final WaitingToBeResolved productDraftWithUnresolvedRefs = - new WaitingToBeResolved(productDraft, asSet("foo", "bar")); + final WaitingToBeResolved productDraftWithUnresolvedRefs = + new WaitingToBeResolved(productDraft, asSet("foo", "bar")); - // test - final Optional result = unresolvedReferencesService + // test + final Optional result = + unresolvedReferencesService .save(productDraftWithUnresolvedRefs) .toCompletableFuture() .join(); - // assertions - assertThat(result).hasValueSatisfying(waitingToBeResolved -> - assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft)); + // assertions + assertThat(result) + .hasValueSatisfying( + waitingToBeResolved -> + assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft)); - // test - final Set waitingDrafts = unresolvedReferencesService + // test + final Set waitingDrafts = + unresolvedReferencesService .fetch(singleton(productDraft.getKey())) .toCompletableFuture() .join(); - // assertions - assertThat(waitingDrafts).containsExactly(productDraftWithUnresolvedRefs); - - // test - final Optional deletionResult = unresolvedReferencesService - .delete(productDraft.getKey()) - .toCompletableFuture() - .join(); - - // assertions - assertThat(deletionResult).hasValueSatisfying(waitingToBeResolved -> - assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft)); + // assertions + assertThat(waitingDrafts).containsExactly(productDraftWithUnresolvedRefs); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); + // test + final Optional deletionResult = + unresolvedReferencesService.delete(productDraft.getKey()).toCompletableFuture().join(); + // assertions + assertThat(deletionResult) + .hasValueSatisfying( + waitingToBeResolved -> + assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft)); - } + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } - @Test - void saveFetchAndDelete_WithKeyWithSpecialCharacter_shouldWorkCorrectly() { - // preparation - final ProductDraft productDraft = - SphereJsonUtils.readObjectFromResource(PRODUCT_KEY_SPECIAL_CHARS_RESOURCE_PATH, ProductDraft.class); + @Test + void saveFetchAndDelete_WithKeyWithSpecialCharacter_shouldWorkCorrectly() { + // preparation + final ProductDraft productDraft = + SphereJsonUtils.readObjectFromResource( + PRODUCT_KEY_SPECIAL_CHARS_RESOURCE_PATH, ProductDraft.class); - final WaitingToBeResolved productDraftWithUnresolvedRefs = - new WaitingToBeResolved(productDraft, asSet("foo", "bar")); + final WaitingToBeResolved productDraftWithUnresolvedRefs = + new WaitingToBeResolved(productDraft, asSet("foo", "bar")); - // test - final Optional result = unresolvedReferencesService + // test + final Optional result = + unresolvedReferencesService .save(productDraftWithUnresolvedRefs) .toCompletableFuture() .join(); - // assertions - assertThat(result).hasValueSatisfying(waitingToBeResolved -> - assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft)); - - // test - final CustomObjectByKeyGet customObjectByKeyGet = CustomObjectByKeyGet - .of(CUSTOM_OBJECT_CONTAINER_KEY, sha1Hex(productDraft.getKey()), WaitingToBeResolved.class); - final CustomObject createdCustomObject = CTP_TARGET_CLIENT - .execute(customObjectByKeyGet) - .toCompletableFuture() - .join(); - // assertions - assertThat(createdCustomObject.getKey()).isEqualTo(sha1Hex(productDraft.getKey())); - - // test - final Set waitingDrafts = unresolvedReferencesService + // assertions + assertThat(result) + .hasValueSatisfying( + waitingToBeResolved -> + assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft)); + + // test + final CustomObjectByKeyGet customObjectByKeyGet = + CustomObjectByKeyGet.of( + CUSTOM_OBJECT_CONTAINER_KEY, sha1Hex(productDraft.getKey()), WaitingToBeResolved.class); + final CustomObject createdCustomObject = + CTP_TARGET_CLIENT.execute(customObjectByKeyGet).toCompletableFuture().join(); + // assertions + assertThat(createdCustomObject.getKey()).isEqualTo(sha1Hex(productDraft.getKey())); + + // test + final Set waitingDrafts = + unresolvedReferencesService .fetch(singleton(productDraft.getKey())) .toCompletableFuture() .join(); - // assertions - assertThat(waitingDrafts).containsExactly(productDraftWithUnresolvedRefs); + // assertions + assertThat(waitingDrafts).containsExactly(productDraftWithUnresolvedRefs); - // test - final Optional deletionResult = unresolvedReferencesService - .delete(productDraft.getKey()) - .toCompletableFuture() - .join(); + // test + final Optional deletionResult = + unresolvedReferencesService.delete(productDraft.getKey()).toCompletableFuture().join(); - // assertions - assertThat(deletionResult).hasValueSatisfying(waitingToBeResolved -> - assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft)); + // assertions + assertThat(deletionResult) + .hasValueSatisfying( + waitingToBeResolved -> + assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft)); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } + @Test + void save_ExistingProductDraftWithoutException_overwritesOldCustomObjectValue() { + // preparation + final ProductDraft productDraft = + SphereJsonUtils.readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, ProductDraft.class); - } + final WaitingToBeResolved productDraftWithUnresolvedRefs = + new WaitingToBeResolved(productDraft, asSet("foo", "bar")); - @Test - void save_ExistingProductDraftWithoutException_overwritesOldCustomObjectValue() { - // preparation - final ProductDraft productDraft = - SphereJsonUtils.readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, ProductDraft.class); + unresolvedReferencesService.save(productDraftWithUnresolvedRefs).toCompletableFuture().join(); - final WaitingToBeResolved productDraftWithUnresolvedRefs = - new WaitingToBeResolved(productDraft, asSet("foo", "bar")); + final WaitingToBeResolved productDraftWithUnresolvedNewRefs = + new WaitingToBeResolved(productDraft, asSet("foo123", "bar123")); + // test + final Optional latestResult = unresolvedReferencesService - .save(productDraftWithUnresolvedRefs) - .toCompletableFuture() - .join(); - - final WaitingToBeResolved productDraftWithUnresolvedNewRefs = - new WaitingToBeResolved(productDraft, asSet("foo123", "bar123")); - - // test - final Optional latestResult = unresolvedReferencesService .save(productDraftWithUnresolvedNewRefs) .toCompletableFuture() .join(); - - // assertions - assertThat(latestResult).hasValueSatisfying(waitingToBeResolved -> { - assertThat(waitingToBeResolved.getProductDraft()) - .isEqualTo(productDraft); - assertThat(waitingToBeResolved.getMissingReferencedProductKeys()) - .isEqualTo(productDraftWithUnresolvedNewRefs.getMissingReferencedProductKeys()); - }); - - final CustomObjectByKeyGet customObjectByKeyGet = CustomObjectByKeyGet - .of(CUSTOM_OBJECT_CONTAINER_KEY, sha1Hex(productDraft.getKey()), WaitingToBeResolved.class); - final CustomObject createdCustomObject = CTP_TARGET_CLIENT - .execute(customObjectByKeyGet) - .toCompletableFuture() - .join(); - - assertThat(createdCustomObject.getValue()).isEqualTo(productDraftWithUnresolvedNewRefs); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } + // assertions + assertThat(latestResult) + .hasValueSatisfying( + waitingToBeResolved -> { + assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft); + assertThat(waitingToBeResolved.getMissingReferencedProductKeys()) + .isEqualTo(productDraftWithUnresolvedNewRefs.getMissingReferencedProductKeys()); + }); + + final CustomObjectByKeyGet customObjectByKeyGet = + CustomObjectByKeyGet.of( + CUSTOM_OBJECT_CONTAINER_KEY, sha1Hex(productDraft.getKey()), WaitingToBeResolved.class); + final CustomObject createdCustomObject = + CTP_TARGET_CLIENT.execute(customObjectByKeyGet).toCompletableFuture().join(); + + assertThat(createdCustomObject.getValue()).isEqualTo(productDraftWithUnresolvedNewRefs); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedTransitionsServiceImplIT.java b/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedTransitionsServiceImplIT.java index 1c30773e1c..826f7b042e 100644 --- a/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedTransitionsServiceImplIT.java +++ b/src/integration-test/java/com/commercetools/sync/integration/services/impl/UnresolvedTransitionsServiceImplIT.java @@ -1,5 +1,13 @@ package com.commercetools.sync.integration.services.impl; +import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedTransitionsCustomObjects; +import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; +import static com.commercetools.sync.services.impl.UnresolvedTransitionsServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.util.Collections.singleton; +import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.commons.models.WaitingToBeResolvedTransitions; import com.commercetools.sync.services.UnresolvedTransitionsService; import com.commercetools.sync.services.impl.UnresolvedTransitionsServiceImpl; @@ -12,211 +20,213 @@ import io.sphere.sdk.states.StateDraft; import io.sphere.sdk.states.StateDraftBuilder; import io.sphere.sdk.states.StateType; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.integration.commons.utils.CustomObjectITUtils.deleteWaitingToBeResolvedTransitionsCustomObjects; -import static com.commercetools.sync.integration.commons.utils.SphereClientUtils.CTP_TARGET_CLIENT; -import static com.commercetools.sync.services.impl.UnresolvedTransitionsServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY; -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.util.Collections.singleton; -import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class UnresolvedTransitionsServiceImplIT { - private UnresolvedTransitionsService unresolvedTransitionsService; - private List errorCallBackMessages; - private List warningCallBackMessages; - private List errorCallBackExceptions; - - @AfterEach - void tearDown() { - deleteWaitingToBeResolvedTransitionsCustomObjects(CTP_TARGET_CLIENT, CUSTOM_OBJECT_CONTAINER_KEY); - } - - @BeforeEach - void setupTest() { - deleteWaitingToBeResolvedTransitionsCustomObjects(CTP_TARGET_CLIENT, CUSTOM_OBJECT_CONTAINER_KEY); - - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - warningCallBackMessages = new ArrayList<>(); - - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(CTP_TARGET_CLIENT) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .warningCallback((exception, newResource, oldResource) -> - warningCallBackMessages.add(exception.getMessage())) + private UnresolvedTransitionsService unresolvedTransitionsService; + private List errorCallBackMessages; + private List warningCallBackMessages; + private List errorCallBackExceptions; + + @AfterEach + void tearDown() { + deleteWaitingToBeResolvedTransitionsCustomObjects( + CTP_TARGET_CLIENT, CUSTOM_OBJECT_CONTAINER_KEY); + } + + @BeforeEach + void setupTest() { + deleteWaitingToBeResolvedTransitionsCustomObjects( + CTP_TARGET_CLIENT, CUSTOM_OBJECT_CONTAINER_KEY); + + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + warningCallBackMessages = new ArrayList<>(); + + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_TARGET_CLIENT) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .warningCallback( + (exception, newResource, oldResource) -> + warningCallBackMessages.add(exception.getMessage())) .build(); - unresolvedTransitionsService = new UnresolvedTransitionsServiceImpl(stateSyncOptions); - } + unresolvedTransitionsService = new UnresolvedTransitionsServiceImpl(stateSyncOptions); + } - @Test - void saveFetchAndDelete_WithoutExceptions_shouldWorkCorrectly() { - // preparation - final Set> newTransitions = new HashSet<>( - Arrays.asList(State.referenceOfId("id1"), State.referenceOfId("id2"))); + @Test + void saveFetchAndDelete_WithoutExceptions_shouldWorkCorrectly() { + // preparation + final Set> newTransitions = + new HashSet<>(Arrays.asList(State.referenceOfId("id1"), State.referenceOfId("id2"))); - final StateDraft stateDraft = StateDraftBuilder.of("key", StateType.LINE_ITEM_STATE) - .transitions(newTransitions) - .build(); + final StateDraft stateDraft = + StateDraftBuilder.of("key", StateType.LINE_ITEM_STATE).transitions(newTransitions).build(); - final WaitingToBeResolvedTransitions stateDraftWithUnresolvedTransitions = - new WaitingToBeResolvedTransitions(stateDraft, asSet("id1", "id2")); + final WaitingToBeResolvedTransitions stateDraftWithUnresolvedTransitions = + new WaitingToBeResolvedTransitions(stateDraft, asSet("id1", "id2")); - // test - final Optional result = unresolvedTransitionsService + // test + final Optional result = + unresolvedTransitionsService .save(stateDraftWithUnresolvedTransitions) .toCompletableFuture() .join(); - // assertions - assertThat(result).hasValueSatisfying(WaitingToBeResolvedTransitions -> - assertThat(WaitingToBeResolvedTransitions.getStateDraft()).isEqualTo(stateDraft)); + // assertions + assertThat(result) + .hasValueSatisfying( + WaitingToBeResolvedTransitions -> + assertThat(WaitingToBeResolvedTransitions.getStateDraft()).isEqualTo(stateDraft)); - // test - final Set waitingDrafts = unresolvedTransitionsService + // test + final Set waitingDrafts = + unresolvedTransitionsService .fetch(singleton(stateDraft.getKey())) .toCompletableFuture() .join(); - // assertions - assertThat(waitingDrafts).containsExactly(stateDraftWithUnresolvedTransitions); - - // test - final Optional deletionResult = unresolvedTransitionsService - .delete(stateDraft.getKey()) - .toCompletableFuture() - .join(); - - // assertions - assertThat(deletionResult).hasValueSatisfying(WaitingToBeResolvedTransitions -> - assertThat(WaitingToBeResolvedTransitions.getStateDraft()).isEqualTo(stateDraft)); + // assertions + assertThat(waitingDrafts).containsExactly(stateDraftWithUnresolvedTransitions); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); + // test + final Optional deletionResult = + unresolvedTransitionsService.delete(stateDraft.getKey()).toCompletableFuture().join(); + // assertions + assertThat(deletionResult) + .hasValueSatisfying( + WaitingToBeResolvedTransitions -> + assertThat(WaitingToBeResolvedTransitions.getStateDraft()).isEqualTo(stateDraft)); - } + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } - @Test - void saveFetchAndDelete_WithKeyWithSpecialCharacter_shouldWorkCorrectly() { - // preparation - final Set> newTransitions = new HashSet<>( - Arrays.asList(State.referenceOfId("id1"), State.referenceOfId("id2"))); + @Test + void saveFetchAndDelete_WithKeyWithSpecialCharacter_shouldWorkCorrectly() { + // preparation + final Set> newTransitions = + new HashSet<>(Arrays.asList(State.referenceOfId("id1"), State.referenceOfId("id2"))); - final String key = "You’re having a key with special character®"; - final StateDraft stateDraft = StateDraftBuilder.of(key, StateType.LINE_ITEM_STATE) - .transitions(newTransitions) - .build(); + final String key = "You’re having a key with special character®"; + final StateDraft stateDraft = + StateDraftBuilder.of(key, StateType.LINE_ITEM_STATE).transitions(newTransitions).build(); - final WaitingToBeResolvedTransitions stateDraftWithUnresolvedTransitions = - new WaitingToBeResolvedTransitions(stateDraft, asSet("id1", "id2")); + final WaitingToBeResolvedTransitions stateDraftWithUnresolvedTransitions = + new WaitingToBeResolvedTransitions(stateDraft, asSet("id1", "id2")); - // test - final Optional result = unresolvedTransitionsService + // test + final Optional result = + unresolvedTransitionsService .save(stateDraftWithUnresolvedTransitions) .toCompletableFuture() .join(); - // assertions - assertThat(result).hasValueSatisfying(WaitingToBeResolvedTransitions -> - assertThat(WaitingToBeResolvedTransitions.getStateDraft()).isEqualTo(stateDraft)); - - // test - final CustomObjectByKeyGet customObjectByKeyGet = CustomObjectByKeyGet - .of(CUSTOM_OBJECT_CONTAINER_KEY, sha1Hex(stateDraft.getKey()), WaitingToBeResolvedTransitions.class); - final CustomObject createdCustomObject = CTP_TARGET_CLIENT - .execute(customObjectByKeyGet) - .toCompletableFuture() - .join(); - // assertions - assertThat(createdCustomObject.getKey()).isEqualTo(sha1Hex(stateDraft.getKey())); - - // test - final Set waitingDrafts = unresolvedTransitionsService + // assertions + assertThat(result) + .hasValueSatisfying( + WaitingToBeResolvedTransitions -> + assertThat(WaitingToBeResolvedTransitions.getStateDraft()).isEqualTo(stateDraft)); + + // test + final CustomObjectByKeyGet customObjectByKeyGet = + CustomObjectByKeyGet.of( + CUSTOM_OBJECT_CONTAINER_KEY, + sha1Hex(stateDraft.getKey()), + WaitingToBeResolvedTransitions.class); + final CustomObject createdCustomObject = + CTP_TARGET_CLIENT.execute(customObjectByKeyGet).toCompletableFuture().join(); + // assertions + assertThat(createdCustomObject.getKey()).isEqualTo(sha1Hex(stateDraft.getKey())); + + // test + final Set waitingDrafts = + unresolvedTransitionsService .fetch(singleton(stateDraft.getKey())) .toCompletableFuture() .join(); - // assertions - assertThat(waitingDrafts).containsExactly(stateDraftWithUnresolvedTransitions); + // assertions + assertThat(waitingDrafts).containsExactly(stateDraftWithUnresolvedTransitions); - // test - final Optional deletionResult = unresolvedTransitionsService - .delete(stateDraft.getKey()) - .toCompletableFuture() - .join(); + // test + final Optional deletionResult = + unresolvedTransitionsService.delete(stateDraft.getKey()).toCompletableFuture().join(); - // assertions - assertThat(deletionResult).hasValueSatisfying(WaitingToBeResolvedTransitions -> - assertThat(WaitingToBeResolvedTransitions.getStateDraft()).isEqualTo(stateDraft)); + // assertions + assertThat(deletionResult) + .hasValueSatisfying( + WaitingToBeResolvedTransitions -> + assertThat(WaitingToBeResolvedTransitions.getStateDraft()).isEqualTo(stateDraft)); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - } + assertThat(errorCallBackMessages).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } - @Test - void save_ExistingStateDraftWithoutException_overwritesOldCustomObjectValue() { - // preparation - final Set> newTransitions = new HashSet<>( - Arrays.asList(State.referenceOfId("id1"), State.referenceOfId("id2"))); + @Test + void save_ExistingStateDraftWithoutException_overwritesOldCustomObjectValue() { + // preparation + final Set> newTransitions = + new HashSet<>(Arrays.asList(State.referenceOfId("id1"), State.referenceOfId("id2"))); - final StateDraft stateDraft = StateDraftBuilder.of("key", StateType.LINE_ITEM_STATE) - .transitions(newTransitions) - .build(); + final StateDraft stateDraft = + StateDraftBuilder.of("key", StateType.LINE_ITEM_STATE).transitions(newTransitions).build(); - final WaitingToBeResolvedTransitions stateDraftWithUnresolvedTransitions = - new WaitingToBeResolvedTransitions(stateDraft, asSet("id1", "id2")); + final WaitingToBeResolvedTransitions stateDraftWithUnresolvedTransitions = + new WaitingToBeResolvedTransitions(stateDraft, asSet("id1", "id2")); - unresolvedTransitionsService - .save(stateDraftWithUnresolvedTransitions) - .toCompletableFuture() - .join(); + unresolvedTransitionsService + .save(stateDraftWithUnresolvedTransitions) + .toCompletableFuture() + .join(); - final WaitingToBeResolvedTransitions newStateDraftWithUnresolvedTransitions = - new WaitingToBeResolvedTransitions(stateDraft, asSet("id1_123", "id2_123")); + final WaitingToBeResolvedTransitions newStateDraftWithUnresolvedTransitions = + new WaitingToBeResolvedTransitions(stateDraft, asSet("id1_123", "id2_123")); - // test - final Optional latestResult = unresolvedTransitionsService + // test + final Optional latestResult = + unresolvedTransitionsService .save(newStateDraftWithUnresolvedTransitions) .toCompletableFuture() .join(); - - // assertions - assertThat(latestResult).hasValueSatisfying(WaitingToBeResolvedTransitions -> { - assertThat(WaitingToBeResolvedTransitions.getStateDraft()) - .isEqualTo(stateDraft); - assertThat(WaitingToBeResolvedTransitions.getMissingTransitionStateKeys()) - .isEqualTo(newStateDraftWithUnresolvedTransitions.getMissingTransitionStateKeys()); - }); - - final CustomObjectByKeyGet customObjectByKeyGet = CustomObjectByKeyGet - .of(CUSTOM_OBJECT_CONTAINER_KEY, sha1Hex(stateDraft.getKey()), WaitingToBeResolvedTransitions.class); - final CustomObject createdCustomObject = CTP_TARGET_CLIENT - .execute(customObjectByKeyGet) - .toCompletableFuture() - .join(); - - assertThat(createdCustomObject.getValue()).isEqualTo(newStateDraftWithUnresolvedTransitions); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - assertThat(warningCallBackMessages).isEmpty(); - } + // assertions + assertThat(latestResult) + .hasValueSatisfying( + WaitingToBeResolvedTransitions -> { + assertThat(WaitingToBeResolvedTransitions.getStateDraft()).isEqualTo(stateDraft); + assertThat(WaitingToBeResolvedTransitions.getMissingTransitionStateKeys()) + .isEqualTo( + newStateDraftWithUnresolvedTransitions.getMissingTransitionStateKeys()); + }); + + final CustomObjectByKeyGet customObjectByKeyGet = + CustomObjectByKeyGet.of( + CUSTOM_OBJECT_CONTAINER_KEY, + sha1Hex(stateDraft.getKey()), + WaitingToBeResolvedTransitions.class); + final CustomObject createdCustomObject = + CTP_TARGET_CLIENT.execute(customObjectByKeyGet).toCompletableFuture().join(); + + assertThat(createdCustomObject.getValue()).isEqualTo(newStateDraftWithUnresolvedTransitions); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + assertThat(warningCallBackMessages).isEmpty(); + } } diff --git a/src/integration-test/java/com/commercetools/tests/utils/CompletionStageUtil.java b/src/integration-test/java/com/commercetools/tests/utils/CompletionStageUtil.java index 67398fa361..8443fb2c2d 100644 --- a/src/integration-test/java/com/commercetools/tests/utils/CompletionStageUtil.java +++ b/src/integration-test/java/com/commercetools/tests/utils/CompletionStageUtil.java @@ -1,14 +1,13 @@ package com.commercetools.tests.utils; -import javax.annotation.Nonnull; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; public final class CompletionStageUtil { - public static T executeBlocking(@Nonnull final CompletionStage request) { - return request.toCompletableFuture().join(); - } + public static T executeBlocking(@Nonnull final CompletionStage request) { + return request.toCompletableFuture().join(); + } - private CompletionStageUtil() { - } + private CompletionStageUtil() {} } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSync.java b/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSync.java index 5089b43644..36322db45f 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSync.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSync.java @@ -1,5 +1,14 @@ package com.commercetools.sync.cartdiscounts; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountSyncUtils.buildActions; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; + import com.commercetools.sync.cartdiscounts.helpers.CartDiscountBatchValidator; import com.commercetools.sync.cartdiscounts.helpers.CartDiscountReferenceResolver; import com.commercetools.sync.cartdiscounts.helpers.CartDiscountSyncStatistics; @@ -12,324 +21,350 @@ import io.sphere.sdk.cartdiscounts.CartDiscount; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.commands.UpdateAction; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountSyncUtils.buildActions; -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; /** * This class syncs cart discount drafts with the corresponding cart discounts in the CTP project. */ -public class CartDiscountSync extends BaseSync { - - private static final String CTP_CART_DISCOUNT_FETCH_FAILED = - "Failed to fetch existing cart discounts with keys: '%s'."; - private static final String CTP_CART_DISCOUNT_UPDATE_FAILED = - "Failed to update cart discount with key: '%s'. Reason: %s"; - private static final String FAILED_TO_PROCESS = "Failed to process the CartDiscountDraft with key:'%s'. Reason: %s"; - - private final CartDiscountService cartDiscountService; - private final TypeService typeService; - private final CartDiscountReferenceResolver referenceResolver; - private final CartDiscountBatchValidator batchValidator; - - /** - * Takes a {@link CartDiscountSyncOptions} to instantiate a new {@link CartDiscountSync} instance - * that could be used to sync cart discount drafts in the CTP project - * specified in the injected {@link CartDiscountSyncOptions} instance. - * - * @param cartDiscountSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - */ - public CartDiscountSync(@Nonnull final CartDiscountSyncOptions cartDiscountSyncOptions) { - this(cartDiscountSyncOptions, new TypeServiceImpl(cartDiscountSyncOptions), - new CartDiscountServiceImpl(cartDiscountSyncOptions)); - } - - /** - * Takes a {@link CartDiscountSyncOptions} and a {@link CartDiscountService} instances to instantiate - * a new {@link CartDiscountSync} instance that could be used to sync cart discount drafts in the CTP project - * specified in the injected {@link CartDiscountSyncOptions} instance. - * - *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and passed to. - * - * @param cartDiscountSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param typeService the type service which is responsible for fetching/caching the Types from the CTP - * project. - * @param cartDiscountService the cart discount service which is responsible for fetching/caching - * the CartDiscounts from the CTP project. - */ - CartDiscountSync(@Nonnull final CartDiscountSyncOptions cartDiscountSyncOptions, - @Nonnull final TypeService typeService, - @Nonnull final CartDiscountService cartDiscountService) { - super(new CartDiscountSyncStatistics(), cartDiscountSyncOptions); - this.cartDiscountService = cartDiscountService; - this.typeService = typeService; - this.referenceResolver = new CartDiscountReferenceResolver(getSyncOptions(), typeService); - this.batchValidator = new CartDiscountBatchValidator(getSyncOptions(), getStatistics()); - } - - /** - * Iterates through the whole {@code cartDiscountDrafts} list and accumulates its valid drafts to batches. - * Every batch is then processed by {@link CartDiscountSync#processBatch(List)}. - * - *

Inherited doc: - * {@inheritDoc} - * - * @param cartDiscountDrafts {@link List} of {@link CartDiscountDraft}'s that would be synced into CTP project. - * @return {@link CompletionStage} with {@link CartDiscountSyncStatistics} holding statistics of all sync - * processes performed by this sync instance. - */ - @Override - protected CompletionStage process( - @Nonnull final List cartDiscountDrafts) { - - final List> batches = batchElements(cartDiscountDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, completedFuture(statistics)); +public class CartDiscountSync + extends BaseSync { + + private static final String CTP_CART_DISCOUNT_FETCH_FAILED = + "Failed to fetch existing cart discounts with keys: '%s'."; + private static final String CTP_CART_DISCOUNT_UPDATE_FAILED = + "Failed to update cart discount with key: '%s'. Reason: %s"; + private static final String FAILED_TO_PROCESS = + "Failed to process the CartDiscountDraft with key:'%s'. Reason: %s"; + + private final CartDiscountService cartDiscountService; + private final TypeService typeService; + private final CartDiscountReferenceResolver referenceResolver; + private final CartDiscountBatchValidator batchValidator; + + /** + * Takes a {@link CartDiscountSyncOptions} to instantiate a new {@link CartDiscountSync} instance + * that could be used to sync cart discount drafts in the CTP project specified in the injected + * {@link CartDiscountSyncOptions} instance. + * + * @param cartDiscountSyncOptions the container of all the options of the sync process including + * the CTP project client and/or configuration and other sync-specific options. + */ + public CartDiscountSync(@Nonnull final CartDiscountSyncOptions cartDiscountSyncOptions) { + this( + cartDiscountSyncOptions, + new TypeServiceImpl(cartDiscountSyncOptions), + new CartDiscountServiceImpl(cartDiscountSyncOptions)); + } + + /** + * Takes a {@link CartDiscountSyncOptions} and a {@link CartDiscountService} instances to + * instantiate a new {@link CartDiscountSync} instance that could be used to sync cart discount + * drafts in the CTP project specified in the injected {@link CartDiscountSyncOptions} instance. + * + *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and + * passed to. + * + * @param cartDiscountSyncOptions the container of all the options of the sync process including + * the CTP project client and/or configuration and other sync-specific options. + * @param typeService the type service which is responsible for fetching/caching the Types from + * the CTP project. + * @param cartDiscountService the cart discount service which is responsible for fetching/caching + * the CartDiscounts from the CTP project. + */ + CartDiscountSync( + @Nonnull final CartDiscountSyncOptions cartDiscountSyncOptions, + @Nonnull final TypeService typeService, + @Nonnull final CartDiscountService cartDiscountService) { + super(new CartDiscountSyncStatistics(), cartDiscountSyncOptions); + this.cartDiscountService = cartDiscountService; + this.typeService = typeService; + this.referenceResolver = new CartDiscountReferenceResolver(getSyncOptions(), typeService); + this.batchValidator = new CartDiscountBatchValidator(getSyncOptions(), getStatistics()); + } + + /** + * Iterates through the whole {@code cartDiscountDrafts} list and accumulates its valid drafts to + * batches. Every batch is then processed by {@link CartDiscountSync#processBatch(List)}. + * + *

Inherited doc: {@inheritDoc} + * + * @param cartDiscountDrafts {@link List} of {@link CartDiscountDraft}'s that would be synced into + * CTP project. + * @return {@link CompletionStage} with {@link CartDiscountSyncStatistics} holding statistics of + * all sync processes performed by this sync instance. + */ + @Override + protected CompletionStage process( + @Nonnull final List cartDiscountDrafts) { + + final List> batches = + batchElements(cartDiscountDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, completedFuture(statistics)); + } + + @Override + protected CompletionStage processBatch( + @Nonnull final List batch) { + + final ImmutablePair, Set> result = + batchValidator.validateAndCollectReferencedKeys(batch); + + final Set validDrafts = result.getLeft(); + if (validDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return completedFuture(statistics); } - @Override - protected CompletionStage processBatch(@Nonnull final List batch) { - - final ImmutablePair, Set> result = - batchValidator.validateAndCollectReferencedKeys(batch); - - final Set validDrafts = result.getLeft(); - if (validDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return completedFuture(statistics); - } - - return typeService - .cacheKeysToIds(result.getRight()) - .handle(ImmutablePair::new) - .thenCompose(cachingResponse -> { - final Throwable cachingException = cachingResponse.getValue(); - if (cachingException != null) { - handleError("Failed to build a cache of keys to ids.", cachingException, - validDrafts.size()); - return CompletableFuture.completedFuture(null); - } - - final Set keys = validDrafts.stream().map(CartDiscountDraft::getKey).collect(toSet()); - - return cartDiscountService - .fetchMatchingCartDiscountsByKeys(keys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { + return typeService + .cacheKeysToIds(result.getRight()) + .handle(ImmutablePair::new) + .thenCompose( + cachingResponse -> { + final Throwable cachingException = cachingResponse.getValue(); + if (cachingException != null) { + handleError( + "Failed to build a cache of keys to ids.", + cachingException, + validDrafts.size()); + return CompletableFuture.completedFuture(null); + } + + final Set keys = + validDrafts.stream().map(CartDiscountDraft::getKey).collect(toSet()); + + return cartDiscountService + .fetchMatchingCartDiscountsByKeys(keys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { final Set fetchedCartDiscounts = fetchResponse.getKey(); final Throwable exception = fetchResponse.getValue(); if (exception != null) { - final String errorMessage = format(CTP_CART_DISCOUNT_FETCH_FAILED, keys); - handleError(errorMessage, exception, keys.size()); - return CompletableFuture.completedFuture(null); + final String errorMessage = format(CTP_CART_DISCOUNT_FETCH_FAILED, keys); + handleError(errorMessage, exception, keys.size()); + return CompletableFuture.completedFuture(null); } else { - return syncBatch(fetchedCartDiscounts, validDrafts); + return syncBatch(fetchedCartDiscounts, validDrafts); } - }); + }); }) - .thenApply(ignoredResult -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignoredResult -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); - } - - /** - * This method calls the optional error callback specified in the {@code syncOptions} - * and updates the {@code statistics} instance by - * incrementing the total number of failed cart discounts to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - * @param exception The exception that called caused the failure, if any. - * @param failedTimes The number of times that the failed cart discount statistic counter is incremented. - */ - private void handleError(@Nonnull final String errorMessage, - @Nullable final Throwable exception, - final int failedTimes) { - syncOptions.applyErrorCallback(new SyncException(errorMessage, exception)); - statistics.incrementFailed(failedTimes); - } - - /** - * Given a set of cart discount drafts, attempts to sync the drafts with the existing cart discounts in the CTP - * project. The cart discount and the draft are considered to match if they have the same key. - * - * @param oldCartDiscounts old cart discounts. - * @param newCartDiscounts drafts that need to be synced. - * @return a {@link CompletionStage} which contains an empty result after execution of the update - */ - @Nonnull - private CompletionStage syncBatch( - @Nonnull final Set oldCartDiscounts, - @Nonnull final Set newCartDiscounts) { - - final Map oldCartDiscountMap = oldCartDiscounts - .stream() - .collect(toMap(CartDiscount::getKey, identity())); - - return CompletableFuture.allOf(newCartDiscounts - .stream() - .map(newCartDiscount -> + } + + /** + * This method calls the optional error callback specified in the {@code syncOptions} and updates + * the {@code statistics} instance by incrementing the total number of failed cart discounts to + * sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + * @param exception The exception that called caused the failure, if any. + * @param failedTimes The number of times that the failed cart discount statistic counter is + * incremented. + */ + private void handleError( + @Nonnull final String errorMessage, + @Nullable final Throwable exception, + final int failedTimes) { + syncOptions.applyErrorCallback(new SyncException(errorMessage, exception)); + statistics.incrementFailed(failedTimes); + } + + /** + * Given a set of cart discount drafts, attempts to sync the drafts with the existing cart + * discounts in the CTP project. The cart discount and the draft are considered to match if they + * have the same key. + * + * @param oldCartDiscounts old cart discounts. + * @param newCartDiscounts drafts that need to be synced. + * @return a {@link CompletionStage} which contains an empty result after execution of the update + */ + @Nonnull + private CompletionStage syncBatch( + @Nonnull final Set oldCartDiscounts, + @Nonnull final Set newCartDiscounts) { + + final Map oldCartDiscountMap = + oldCartDiscounts.stream().collect(toMap(CartDiscount::getKey, identity())); + + return CompletableFuture.allOf( + newCartDiscounts.stream() + .map( + newCartDiscount -> referenceResolver .resolveReferences(newCartDiscount) .thenCompose(resolvedDraft -> syncDraft(oldCartDiscountMap, resolvedDraft)) - .exceptionally(completionException -> { - final String errorMessage = format(FAILED_TO_PROCESS, newCartDiscount.getKey(), - completionException.getMessage()); - handleError(errorMessage, completionException, 1); - return null; - }) - ) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)); - } - - @Nonnull - private CompletionStage syncDraft( - @Nonnull final Map oldCartDiscountMap, - @Nonnull final CartDiscountDraft newCartDiscount) { - - final CartDiscount oldCartDiscount = oldCartDiscountMap.get(newCartDiscount.getKey()); - - return ofNullable(oldCartDiscount) - .map(cartDiscount -> buildActionsAndUpdate(oldCartDiscount, newCartDiscount)) - .orElseGet(() -> applyCallbackAndCreate(newCartDiscount)); - } - - @Nonnull - private CompletionStage buildActionsAndUpdate( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - - final List> updateActions = - buildActions(oldCartDiscount, newCartDiscount, syncOptions); - - final List> updateActionsAfterCallback = - syncOptions.applyBeforeUpdateCallback(updateActions, newCartDiscount, oldCartDiscount); - - if (!updateActionsAfterCallback.isEmpty()) { - return updateCartDiscount(oldCartDiscount, newCartDiscount, updateActionsAfterCallback); - } - - return completedFuture(null); - } - - /** - * Given a cart discount draft, this method applies the beforeCreateCallback and then issues a create request to the - * CTP project to create the corresponding CartDiscount. - * - * @param cartDiscountDraft the cart discount draft to create the cart discount from. - * @return a {@link CompletionStage} which contains an empty result after execution of the create. - */ - @Nonnull - private CompletionStage applyCallbackAndCreate(@Nonnull final CartDiscountDraft cartDiscountDraft) { - - return syncOptions - .applyBeforeCreateCallback(cartDiscountDraft) - .map(draft -> cartDiscountService - .createCartDiscount(draft) - .thenAccept(cartDiscountOptional -> { - if (cartDiscountOptional.isPresent()) { - statistics.incrementCreated(); - } else { - statistics.incrementFailed(); - } - })) - .orElseGet(() -> CompletableFuture.completedFuture(null)); + .exceptionally( + completionException -> { + final String errorMessage = + format( + FAILED_TO_PROCESS, + newCartDiscount.getKey(), + completionException.getMessage()); + handleError(errorMessage, completionException, 1); + return null; + })) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)); + } + + @Nonnull + private CompletionStage syncDraft( + @Nonnull final Map oldCartDiscountMap, + @Nonnull final CartDiscountDraft newCartDiscount) { + + final CartDiscount oldCartDiscount = oldCartDiscountMap.get(newCartDiscount.getKey()); + + return ofNullable(oldCartDiscount) + .map(cartDiscount -> buildActionsAndUpdate(oldCartDiscount, newCartDiscount)) + .orElseGet(() -> applyCallbackAndCreate(newCartDiscount)); + } + + @Nonnull + private CompletionStage buildActionsAndUpdate( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + + final List> updateActions = + buildActions(oldCartDiscount, newCartDiscount, syncOptions); + + final List> updateActionsAfterCallback = + syncOptions.applyBeforeUpdateCallback(updateActions, newCartDiscount, oldCartDiscount); + + if (!updateActionsAfterCallback.isEmpty()) { + return updateCartDiscount(oldCartDiscount, newCartDiscount, updateActionsAfterCallback); } - /** - * Given an existing {@link CartDiscount} and a new {@link CartDiscountDraft}, the method calculates all the - * update actions required to synchronize the existing cart discount to be the same as the new one. If there are - * update actions found, a request is made to CTP to update the existing cart discount, otherwise it doesn't issue a - * request. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP,the error handling method - * is called. - * - * @param oldCartDiscount existing cart discount that could be updated. - * @param newCartDiscount draft containing data that could differ from data in {@code oldCartDiscount}. - * @param updateActions the update actions to update the {@link CartDiscount} with. - * @return a {@link CompletionStage} which contains an empty result after execution of the update. - */ - @Nonnull - private CompletionStage updateCartDiscount( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount, - @Nonnull final List> updateActions) { - - return cartDiscountService - .updateCartDiscount(oldCartDiscount, updateActions) - .handle(ImmutablePair::new) - .thenCompose(updateResponse -> { - final Throwable sphereException = updateResponse.getValue(); - if (sphereException != null) { - return executeSupplierIfConcurrentModificationException( - sphereException, - () -> fetchAndUpdate(oldCartDiscount, newCartDiscount), - () -> { - final String errorMessage = - format(CTP_CART_DISCOUNT_UPDATE_FAILED, newCartDiscount.getKey(), - sphereException.getMessage()); - handleError(errorMessage, sphereException, 1); - return CompletableFuture.completedFuture(null); - }); - } else { - statistics.incrementUpdated(); - return CompletableFuture.completedFuture(null); - } + return completedFuture(null); + } + + /** + * Given a cart discount draft, this method applies the beforeCreateCallback and then issues a + * create request to the CTP project to create the corresponding CartDiscount. + * + * @param cartDiscountDraft the cart discount draft to create the cart discount from. + * @return a {@link CompletionStage} which contains an empty result after execution of the create. + */ + @Nonnull + private CompletionStage applyCallbackAndCreate( + @Nonnull final CartDiscountDraft cartDiscountDraft) { + + return syncOptions + .applyBeforeCreateCallback(cartDiscountDraft) + .map( + draft -> + cartDiscountService + .createCartDiscount(draft) + .thenAccept( + cartDiscountOptional -> { + if (cartDiscountOptional.isPresent()) { + statistics.incrementCreated(); + } else { + statistics.incrementFailed(); + } + })) + .orElseGet(() -> CompletableFuture.completedFuture(null)); + } + + /** + * Given an existing {@link CartDiscount} and a new {@link CartDiscountDraft}, the method + * calculates all the update actions required to synchronize the existing cart discount to be the + * same as the new one. If there are update actions found, a request is made to CTP to update the + * existing cart discount, otherwise it doesn't issue a request. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP,the + * error handling method is called. + * + * @param oldCartDiscount existing cart discount that could be updated. + * @param newCartDiscount draft containing data that could differ from data in {@code + * oldCartDiscount}. + * @param updateActions the update actions to update the {@link CartDiscount} with. + * @return a {@link CompletionStage} which contains an empty result after execution of the update. + */ + @Nonnull + private CompletionStage updateCartDiscount( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount, + @Nonnull final List> updateActions) { + + return cartDiscountService + .updateCartDiscount(oldCartDiscount, updateActions) + .handle(ImmutablePair::new) + .thenCompose( + updateResponse -> { + final Throwable sphereException = updateResponse.getValue(); + if (sphereException != null) { + return executeSupplierIfConcurrentModificationException( + sphereException, + () -> fetchAndUpdate(oldCartDiscount, newCartDiscount), + () -> { + final String errorMessage = + format( + CTP_CART_DISCOUNT_UPDATE_FAILED, + newCartDiscount.getKey(), + sphereException.getMessage()); + handleError(errorMessage, sphereException, 1); + return CompletableFuture.completedFuture(null); + }); + } else { + statistics.incrementUpdated(); + return CompletableFuture.completedFuture(null); + } }); - } - - @Nonnull - private CompletionStage fetchAndUpdate( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - - final String key = oldCartDiscount.getKey(); - return cartDiscountService - .fetchCartDiscount(key) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Optional fetchedCartDiscountOptional = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(CTP_CART_DISCOUNT_UPDATE_FAILED, key, - "Failed to fetch from CTP while retrying after concurrency modification."); - handleError(errorMessage, exception, 1); + } + + @Nonnull + private CompletionStage fetchAndUpdate( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + + final String key = oldCartDiscount.getKey(); + return cartDiscountService + .fetchCartDiscount(key) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Optional fetchedCartDiscountOptional = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + CTP_CART_DISCOUNT_UPDATE_FAILED, + key, + "Failed to fetch from CTP while retrying after concurrency modification."); + handleError(errorMessage, exception, 1); + return CompletableFuture.completedFuture(null); + } + + return fetchedCartDiscountOptional + .map( + fetchedCartDiscount -> + buildActionsAndUpdate(fetchedCartDiscount, newCartDiscount)) + .orElseGet( + () -> { + final String errorMessage = + format( + CTP_CART_DISCOUNT_UPDATE_FAILED, + key, + "Not found when attempting to fetch while retrying " + + "after concurrency modification."); + handleError(errorMessage, null, 1); return CompletableFuture.completedFuture(null); - } - - return fetchedCartDiscountOptional - .map(fetchedCartDiscount -> buildActionsAndUpdate(fetchedCartDiscount, newCartDiscount)) - .orElseGet(() -> { - final String errorMessage = - format(CTP_CART_DISCOUNT_UPDATE_FAILED, key, - "Not found when attempting to fetch while retrying " - + "after concurrency modification."); - handleError(errorMessage, null, 1); - return CompletableFuture.completedFuture(null); - }); - }); - } - + }); + }); + } } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptions.java b/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptions.java index 4508f04deb..2cacb89d57 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptions.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptions.java @@ -9,33 +9,45 @@ import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -public final class CartDiscountSyncOptions extends BaseSyncOptions { +public final class CartDiscountSyncOptions + extends BaseSyncOptions { - CartDiscountSyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallback, - @Nullable final TriConsumer, Optional> - warningCallback, - final int batchSize, - @Nullable final TriFunction>, CartDiscountDraft, - CartDiscount, List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, - final long cacheSize) { + CartDiscountSyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallback, + @Nullable + final TriConsumer, Optional> + warningCallback, + final int batchSize, + @Nullable + final TriFunction< + List>, + CartDiscountDraft, + CartDiscount, + List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { - super(ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize); - } + super( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptionsBuilder.java index aadcc14b47..0b7de39e9f 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptionsBuilder.java @@ -4,56 +4,57 @@ import io.sphere.sdk.cartdiscounts.CartDiscount; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.client.SphereClient; - import javax.annotation.Nonnull; -public final class CartDiscountSyncOptionsBuilder extends BaseSyncOptionsBuilder { - - public static final int BATCH_SIZE_DEFAULT = 50; - - private CartDiscountSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } - - /** - * Creates a new instance of {@link CartDiscountSyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 50). - * - * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link CartDiscountSyncOptionsBuilder} - */ - public static CartDiscountSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new CartDiscountSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); - } - - /** - * Creates new instance of {@link CartDiscountSyncOptions} enriched with all attributes provided to {@code this} - * builder. - * - * @return new instance of {@link CartDiscountSyncOptions} - */ - @Override - public CartDiscountSyncOptions build() { - return new CartDiscountSyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } - - /** - * Returns an instance of this class to be used in the superclass's generic methods. Please see the JavaDoc in the - * overridden method for further details. - * - * @return an instance of this class. - */ - @Override - protected CartDiscountSyncOptionsBuilder getThis() { - return this; - } +public final class CartDiscountSyncOptionsBuilder + extends BaseSyncOptionsBuilder< + CartDiscountSyncOptionsBuilder, CartDiscountSyncOptions, CartDiscount, CartDiscountDraft> { + + public static final int BATCH_SIZE_DEFAULT = 50; + + private CartDiscountSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } + + /** + * Creates a new instance of {@link CartDiscountSyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 50). + * + * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the + * target CTP project. + * @return new instance of {@link CartDiscountSyncOptionsBuilder} + */ + public static CartDiscountSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new CartDiscountSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } + + /** + * Creates new instance of {@link CartDiscountSyncOptions} enriched with all attributes provided + * to {@code this} builder. + * + * @return new instance of {@link CartDiscountSyncOptions} + */ + @Override + public CartDiscountSyncOptions build() { + return new CartDiscountSyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } + + /** + * Returns an instance of this class to be used in the superclass's generic methods. Please see + * the JavaDoc in the overridden method for further details. + * + * @return an instance of this class. + */ + @Override + protected CartDiscountSyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountBatchValidator.java b/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountBatchValidator.java index fa83badb13..5bbba309d0 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountBatchValidator.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountBatchValidator.java @@ -1,76 +1,80 @@ package com.commercetools.sync.cartdiscounts.helpers; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; import com.commercetools.sync.commons.helpers.BaseBatchValidator; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.producttypes.ProductTypeDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; - -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class CartDiscountBatchValidator - extends BaseBatchValidator { + extends BaseBatchValidator< + CartDiscountDraft, CartDiscountSyncOptions, CartDiscountSyncStatistics> { - static final String CART_DISCOUNT_DRAFT_KEY_NOT_SET = "CartDiscountDraft with name: %s doesn't have a key. " - + "Please make sure all cart discount drafts have keys."; - static final String CART_DISCOUNT_DRAFT_IS_NULL = "CartDiscountDraft is null."; + static final String CART_DISCOUNT_DRAFT_KEY_NOT_SET = + "CartDiscountDraft with name: %s doesn't have a key. " + + "Please make sure all cart discount drafts have keys."; + static final String CART_DISCOUNT_DRAFT_IS_NULL = "CartDiscountDraft is null."; - public CartDiscountBatchValidator( - @Nonnull final CartDiscountSyncOptions syncOptions, - @Nonnull final CartDiscountSyncStatistics syncStatistics) { + public CartDiscountBatchValidator( + @Nonnull final CartDiscountSyncOptions syncOptions, + @Nonnull final CartDiscountSyncStatistics syncStatistics) { - super(syncOptions, syncStatistics); - } + super(syncOptions, syncStatistics); + } - /** - * Given the {@link List}<{@link CartDiscountDraft}> of drafts this method attempts to validate - * drafts and collect referenced type keys from the draft - * and return an {@link ImmutablePair}<{@link Set}<{@link CartDiscountDraft}> - * ,{@link Set}<{@link String}>> - * which contains the {@link Set} of valid drafts and referenced type keys. - * - *

A valid cart discount draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a key which is not blank (null/empty)
  4. - *
- * - * @param cartDiscountDrafts the cart discount drafts to validate and collect referenced type keys. - * @return {@link ImmutablePair}<{@link Set}<{@link ProductTypeDraft}>, - * {@link Set}<{@link String}>> which contains the {@link Set} of valid drafts and - * referenced type keys. - */ - @Override - public ImmutablePair, Set> validateAndCollectReferencedKeys( - @Nonnull final List cartDiscountDrafts) { - final Set typeKeys = new HashSet<>(); - final Set validDrafts = cartDiscountDrafts - .stream() + /** + * Given the {@link List}<{@link CartDiscountDraft}> of drafts this method attempts to + * validate drafts and collect referenced type keys from the draft and return an {@link + * ImmutablePair}<{@link Set}<{@link CartDiscountDraft}> ,{@link Set}<{@link + * String}>> which contains the {@link Set} of valid drafts and referenced type keys. + * + *

A valid cart discount draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a key which is not blank (null/empty) + *
+ * + * @param cartDiscountDrafts the cart discount drafts to validate and collect referenced type + * keys. + * @return {@link ImmutablePair}<{@link Set}<{@link ProductTypeDraft}>, {@link + * Set}<{@link String}>> which contains the {@link Set} of valid drafts and + * referenced type keys. + */ + @Override + public ImmutablePair, Set> validateAndCollectReferencedKeys( + @Nonnull final List cartDiscountDrafts) { + final Set typeKeys = new HashSet<>(); + final Set validDrafts = + cartDiscountDrafts.stream() .filter(this::isValidCartDiscountDraft) - .peek(cartDiscountDraft -> - collectReferencedKeyFromCustomFieldsDraft(cartDiscountDraft.getCustom(), typeKeys::add)) + .peek( + cartDiscountDraft -> + collectReferencedKeyFromCustomFieldsDraft( + cartDiscountDraft.getCustom(), typeKeys::add)) .collect(Collectors.toSet()); - return ImmutablePair.of(validDrafts, typeKeys); - } - - private boolean isValidCartDiscountDraft(@Nullable final CartDiscountDraft cartDiscountDraft) { - if (cartDiscountDraft == null) { - handleError(CART_DISCOUNT_DRAFT_IS_NULL); - } else if (isBlank(cartDiscountDraft.getKey())) { - handleError(format(CART_DISCOUNT_DRAFT_KEY_NOT_SET, cartDiscountDraft.getName())); - } else { - return true; - } + return ImmutablePair.of(validDrafts, typeKeys); + } - return false; + private boolean isValidCartDiscountDraft(@Nullable final CartDiscountDraft cartDiscountDraft) { + if (cartDiscountDraft == null) { + handleError(CART_DISCOUNT_DRAFT_IS_NULL); + } else if (isBlank(cartDiscountDraft.getKey())) { + handleError(format(CART_DISCOUNT_DRAFT_KEY_NOT_SET, cartDiscountDraft.getName())); + } else { + return true; } + + return false; + } } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountCustomActionBuilder.java b/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountCustomActionBuilder.java index 3aaa2da28c..fd05df2284 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountCustomActionBuilder.java @@ -6,36 +6,36 @@ import io.sphere.sdk.cartdiscounts.commands.updateactions.SetCustomField; import io.sphere.sdk.cartdiscounts.commands.updateactions.SetCustomType; import io.sphere.sdk.commands.UpdateAction; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; public class CartDiscountCustomActionBuilder implements GenericCustomActionBuilder { - @Nonnull - @Override - public UpdateAction buildRemoveCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String objectId) { - return SetCustomType.ofRemoveType(); - } + @Nonnull + @Override + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String objectId) { + return SetCustomType.ofRemoveType(); + } - @Nonnull - @Override - public UpdateAction buildSetCustomTypeAction( - @Nullable final Integer variantId, - @Nullable final String objectId, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); - } + @Nonnull + @Override + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); + } - @Nonnull - @Override - public UpdateAction buildSetCustomFieldAction(@Nullable final Integer variantId, - @Nullable final String objectId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - return SetCustomField.ofJson(customFieldName, customFieldValue); - } + @Nonnull + @Override + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + return SetCustomField.ofJson(customFieldName, customFieldValue); + } } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountReferenceResolver.java b/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountReferenceResolver.java index db9a533828..1655c949bb 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountReferenceResolver.java @@ -1,62 +1,64 @@ package com.commercetools.sync.cartdiscounts.helpers; +import static java.lang.String.format; + import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.CustomReferenceResolver; import com.commercetools.sync.services.TypeService; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.cartdiscounts.CartDiscountDraftBuilder; - -import javax.annotation.Nonnull; import java.util.concurrent.CompletionStage; - -import static java.lang.String.format; +import javax.annotation.Nonnull; public final class CartDiscountReferenceResolver - extends CustomReferenceResolver { - - static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type reference on " - + "CartDiscountDraft with key:'%s'."; - - /** - * Takes a {@link CartDiscountSyncOptions} instance, a {@link TypeService} to instantiate a - * {@link CartDiscountReferenceResolver} instance that could be used to resolve the cartDiscount drafts in the - * CTP project specified in the injected {@link CartDiscountSyncOptions} instance. - * - * @param options the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - * @param typeService the service to fetch the custom types for reference resolution. - */ - public CartDiscountReferenceResolver( - @Nonnull final CartDiscountSyncOptions options, - @Nonnull final TypeService typeService) { - super(options, typeService); - } - - /** - * Given a {@link CartDiscountDraft} this method attempts to resolve the custom type reference to - * return a {@link CompletionStage} which contains a new instance of the draft with the resolved - * reference or, in case an error occurs during reference resolution, a {@link ReferenceResolutionException}. - * - * @param draft the CartDiscountDraft to resolve its references. - * @return a {@link CompletionStage} that contains as a result a new CartDiscountDraft instance with resolved - * custom type reference or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Override - @Nonnull - public CompletionStage resolveReferences(@Nonnull final CartDiscountDraft draft) { - return resolveCustomTypeReference(CartDiscountDraftBuilder.of(draft)) - .thenApply(CartDiscountDraftBuilder::build); - } - - @Override - protected CompletionStage resolveCustomTypeReference( - @Nonnull final CartDiscountDraftBuilder draftBuilder) { - - return resolveCustomTypeReference(draftBuilder, - CartDiscountDraftBuilder::getCustom, - CartDiscountDraftBuilder::custom, - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getKey())); - } + extends CustomReferenceResolver< + CartDiscountDraft, CartDiscountDraftBuilder, CartDiscountSyncOptions> { + + static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = + "Failed to resolve custom type reference on CartDiscountDraft with key:'%s'."; + + /** + * Takes a {@link CartDiscountSyncOptions} instance, a {@link TypeService} to instantiate a {@link + * CartDiscountReferenceResolver} instance that could be used to resolve the cartDiscount drafts + * in the CTP project specified in the injected {@link CartDiscountSyncOptions} instance. + * + * @param options the container of all the options of the sync process including the CTP project + * client and/or configuration and other sync-specific options. + * @param typeService the service to fetch the custom types for reference resolution. + */ + public CartDiscountReferenceResolver( + @Nonnull final CartDiscountSyncOptions options, @Nonnull final TypeService typeService) { + super(options, typeService); + } + + /** + * Given a {@link CartDiscountDraft} this method attempts to resolve the custom type reference to + * return a {@link CompletionStage} which contains a new instance of the draft with the resolved + * reference or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + * + * @param draft the CartDiscountDraft to resolve its references. + * @return a {@link CompletionStage} that contains as a result a new CartDiscountDraft instance + * with resolved custom type reference or, in case an error occurs during reference + * resolution, a {@link ReferenceResolutionException}. + */ + @Override + @Nonnull + public CompletionStage resolveReferences( + @Nonnull final CartDiscountDraft draft) { + return resolveCustomTypeReference(CartDiscountDraftBuilder.of(draft)) + .thenApply(CartDiscountDraftBuilder::build); + } + + @Override + protected CompletionStage resolveCustomTypeReference( + @Nonnull final CartDiscountDraftBuilder draftBuilder) { + + return resolveCustomTypeReference( + draftBuilder, + CartDiscountDraftBuilder::getCustom, + CartDiscountDraftBuilder::custom, + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getKey())); + } } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountSyncStatistics.java b/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountSyncStatistics.java index 3b8f7be625..38af206da3 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountSyncStatistics.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountSyncStatistics.java @@ -4,15 +4,17 @@ public final class CartDiscountSyncStatistics extends BaseSyncStatistics { - /** - * Builds a summary of the cart discount sync statistics instance that looks like the following example: - * - *

"Summary: 2 cart discounts were processed in total (0 created, 0 updated and 0 failed to sync)." - * - * @return a summary message of the cart discount sync statistics instance. - */ - @Override - public String getReportMessage() { - return getDefaultReportMessageForResource("cart discounts"); - } + /** + * Builds a summary of the cart discount sync statistics instance that looks like the following + * example: + * + *

"Summary: 2 cart discounts were processed in total (0 created, 0 updated and 0 failed to + * sync)." + * + * @return a summary message of the cart discount sync statistics instance. + */ + @Override + public String getReportMessage() { + return getDefaultReportMessageForResource("cart discounts"); + } } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtils.java index cec13a0a8a..e914d05cbe 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtils.java @@ -1,5 +1,7 @@ package com.commercetools.sync.cartdiscounts.utils; +import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; + import io.sphere.sdk.cartdiscounts.CartDiscount; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.cartdiscounts.CartDiscountDraftBuilder; @@ -8,95 +10,94 @@ import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; import java.util.List; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; +import javax.annotation.Nonnull; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class CartDiscountReferenceResolutionUtils { - /** - * Returns an {@link List}<{@link CartDiscountDraft}> consisting of the results of applying the - * mapping from {@link CartDiscount} to {@link CartDiscountDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
- * - *

Note: The {@link Type} reference should has an expanded reference with a key. - * Any reference that is not expanded will have its id in place and not replaced by the key will be - * considered as existing resources on the target commercetools project and - * the library will issues an update/create API request without reference resolution. - * - * @param cartDiscounts the cart discounts with expanded references. - * @return a {@link List} of {@link CartDiscountDraft} built from the - * supplied {@link List} of {@link CartDiscount}. - */ - @Nonnull - public static List mapToCartDiscountDrafts( - @Nonnull final List cartDiscounts) { - return cartDiscounts - .stream() - .map(CartDiscountReferenceResolutionUtils::mapToCartDiscountDraft) - .collect(Collectors.toList()); - } + /** + * Returns an {@link List}<{@link CartDiscountDraft}> consisting of the results of applying + * the mapping from {@link CartDiscount} to {@link CartDiscountDraft} with considering reference + * resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
+ * + *

Note: The {@link Type} reference should has an expanded reference with a key. Any + * reference that is not expanded will have its id in place and not replaced by the key will be + * considered as existing resources on the target commercetools project and the library will + * issues an update/create API request without reference resolution. + * + * @param cartDiscounts the cart discounts with expanded references. + * @return a {@link List} of {@link CartDiscountDraft} built from the supplied {@link List} of + * {@link CartDiscount}. + */ + @Nonnull + public static List mapToCartDiscountDrafts( + @Nonnull final List cartDiscounts) { + return cartDiscounts.stream() + .map(CartDiscountReferenceResolutionUtils::mapToCartDiscountDraft) + .collect(Collectors.toList()); + } - @Nonnull - private static CartDiscountDraft mapToCartDiscountDraft(@Nonnull final CartDiscount cartDiscount) { - return CartDiscountDraftBuilder.of( + @Nonnull + private static CartDiscountDraft mapToCartDiscountDraft( + @Nonnull final CartDiscount cartDiscount) { + return CartDiscountDraftBuilder.of( cartDiscount.getCartPredicate(), cartDiscount.getName(), cartDiscount.isRequiringDiscountCode(), cartDiscount.getSortOrder(), cartDiscount.getTarget(), cartDiscount.getValue()) - .key(cartDiscount.getKey()) - .description(cartDiscount.getDescription()) - .active(cartDiscount.isActive()) - .validFrom(cartDiscount.getValidFrom()) - .validUntil(cartDiscount.getValidUntil()) - .stackingMode(cartDiscount.getStackingMode()) - .custom(mapToCustomFieldsDraft(cartDiscount)) - .build(); - } + .key(cartDiscount.getKey()) + .description(cartDiscount.getDescription()) + .active(cartDiscount.isActive()) + .validFrom(cartDiscount.getValidFrom()) + .validUntil(cartDiscount.getValidUntil()) + .stackingMode(cartDiscount.getStackingMode()) + .custom(mapToCustomFieldsDraft(cartDiscount)) + .build(); + } - /** - * Builds a {@link CartDiscountQuery} for fetching cart discounts from a source CTP project with all the needed - * references expanded for the sync: - *

    - *
  • Custom Type
  • - *
- * - *

Note: Please only use this util if you desire to sync all the aforementioned references from - * a source commercetools project. Otherwise, it is more efficient to build the query without expansions, if they - * are not needed, to avoid unnecessarily bigger payloads fetched from the source project. - * - * @return the query for fetching cart discounts from the source CTP project with all the aforementioned references - * expanded. - */ - public static CartDiscountQuery buildCartDiscountQuery() { - return CartDiscountQuery.of().withExpansionPaths(ExpansionPath.of("custom.type")); - } + /** + * Builds a {@link CartDiscountQuery} for fetching cart discounts from a source CTP project with + * all the needed references expanded for the sync: + * + *

    + *
  • Custom Type + *
+ * + *

Note: Please only use this util if you desire to sync all the aforementioned references from + * a source commercetools project. Otherwise, it is more efficient to build the query without + * expansions, if they are not needed, to avoid unnecessarily bigger payloads fetched from the + * source project. + * + * @return the query for fetching cart discounts from the source CTP project with all the + * aforementioned references expanded. + */ + public static CartDiscountQuery buildCartDiscountQuery() { + return CartDiscountQuery.of().withExpansionPaths(ExpansionPath.of("custom.type")); + } - private CartDiscountReferenceResolutionUtils() { - } + private CartDiscountReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountSyncUtils.java b/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountSyncUtils.java index 95b8a107d6..0fb7f2219b 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountSyncUtils.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountSyncUtils.java @@ -1,14 +1,5 @@ package com.commercetools.sync.cartdiscounts.utils; -import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; -import com.commercetools.sync.cartdiscounts.helpers.CartDiscountCustomActionBuilder; -import io.sphere.sdk.cartdiscounts.CartDiscount; -import io.sphere.sdk.cartdiscounts.CartDiscountDraft; -import io.sphere.sdk.commands.UpdateAction; - -import javax.annotation.Nonnull; -import java.util.List; - import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeCartPredicateUpdateAction; import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeIsActiveUpdateAction; import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeNameUpdateAction; @@ -22,32 +13,41 @@ import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildPrimaryResourceCustomUpdateActions; import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; +import com.commercetools.sync.cartdiscounts.helpers.CartDiscountCustomActionBuilder; +import io.sphere.sdk.cartdiscounts.CartDiscount; +import io.sphere.sdk.cartdiscounts.CartDiscountDraft; +import io.sphere.sdk.commands.UpdateAction; +import java.util.List; +import javax.annotation.Nonnull; + public final class CartDiscountSyncUtils { - private static final CartDiscountCustomActionBuilder cartDiscountCustomActionBuilder = - new CartDiscountCustomActionBuilder(); + private static final CartDiscountCustomActionBuilder cartDiscountCustomActionBuilder = + new CartDiscountCustomActionBuilder(); - /** - * Compares all the fields of a {@link CartDiscount} and a {@link CartDiscountDraft}. It returns a {@link List} of - * {@link UpdateAction}<{@link CartDiscount}> as a result. If no update action is needed, for example in - * case where both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same fields, an empty - * {@link List} is returned. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new data. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied - * by the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link CartDiscountSyncOptions} - * for more info. - * @return A list of cart discount specific update actions. - */ - @Nonnull - public static List> buildActions( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount, - @Nonnull final CartDiscountSyncOptions syncOptions) { + /** + * Compares all the fields of a {@link CartDiscount} and a {@link CartDiscountDraft}. It returns a + * {@link List} of {@link UpdateAction}<{@link CartDiscount}> as a result. If no update + * action is needed, for example in case where both the {@link CartDiscount} and the {@link + * CartDiscountDraft} have the same fields, an empty {@link List} is returned. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new data. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * CartDiscountSyncOptions} for more info. + * @return A list of cart discount specific update actions. + */ + @Nonnull + public static List> buildActions( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount, + @Nonnull final CartDiscountSyncOptions syncOptions) { - final List> updateActions = filterEmptyOptionals( + final List> updateActions = + filterEmptyOptionals( buildChangeValueUpdateAction(oldCartDiscount, newCartDiscount), buildChangeCartPredicateUpdateAction(oldCartDiscount, newCartDiscount), buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscount), @@ -57,19 +57,16 @@ public static List> buildActions( buildChangeSortOrderUpdateAction(oldCartDiscount, newCartDiscount), buildChangeRequiresDiscountCodeUpdateAction(oldCartDiscount, newCartDiscount), buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscount), - buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscount) - ); + buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscount)); - final List> cartDiscountCustomUpdateActions = - buildPrimaryResourceCustomUpdateActions(oldCartDiscount, - newCartDiscount, - cartDiscountCustomActionBuilder, - syncOptions); + final List> cartDiscountCustomUpdateActions = + buildPrimaryResourceCustomUpdateActions( + oldCartDiscount, newCartDiscount, cartDiscountCustomActionBuilder, syncOptions); - updateActions.addAll(cartDiscountCustomUpdateActions); + updateActions.addAll(cartDiscountCustomUpdateActions); - return updateActions; - } + return updateActions; + } - private CartDiscountSyncUtils() { } + private CartDiscountSyncUtils() {} } diff --git a/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountUpdateActionUtils.java b/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountUpdateActionUtils.java index 784c0a2634..c43189dee9 100644 --- a/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountUpdateActionUtils.java @@ -1,5 +1,10 @@ package com.commercetools.sync.cartdiscounts.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; +import static java.util.Optional.empty; +import static java.util.Optional.ofNullable; + import io.sphere.sdk.cartdiscounts.AbsoluteCartDiscountValue; import io.sphere.sdk.cartdiscounts.CartDiscount; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; @@ -19,352 +24,396 @@ import io.sphere.sdk.cartdiscounts.commands.updateactions.SetValidUntil; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; - -import javax.annotation.Nonnull; import java.time.ZonedDateTime; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; -import static java.util.Optional.empty; -import static java.util.Optional.ofNullable; +import javax.annotation.Nonnull; public final class CartDiscountUpdateActionUtils { - /** - * Compares the cart discount values of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same cart discount value, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - *

Note: Order is not significant when comparing {@link AbsoluteCartDiscountValue}s - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new cart discount value. - * @return A filled optional with the update action or an empty optional if the cart discount values are identical. - */ - @Nonnull - public static Optional> buildChangeValueUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - - if (oldCartDiscount.getValue() instanceof AbsoluteCartDiscountValue - && newCartDiscount.getValue() instanceof AbsoluteCartDiscountValue) { - - final AbsoluteCartDiscountValue oldValue = - ((AbsoluteCartDiscountValue) oldCartDiscount.getValue()); - - final AbsoluteCartDiscountValue newValue = - ((AbsoluteCartDiscountValue) newCartDiscount.getValue()); - - return buildChangeAbsoluteValueUpdateAction(oldValue, newValue); - } - - if (oldCartDiscount.getValue() instanceof GiftLineItemCartDiscountValue - && newCartDiscount.getValue() instanceof GiftLineItemCartDiscountValue) { - - final GiftLineItemCartDiscountValue oldValue = (GiftLineItemCartDiscountValue) oldCartDiscount.getValue(); - final GiftLineItemCartDiscountValue newValue = (GiftLineItemCartDiscountValue) newCartDiscount.getValue(); - - return Optional.ofNullable( - buildActionIfDifferentProducts(oldValue, newValue) - .orElse(buildActionIfDifferentProductVariantIds(oldValue, newValue) - .orElse(buildActionIfDifferentSupplyChannels(oldValue, newValue) - .orElse(buildActionIfDifferentDistributionChannels(oldValue, newValue) - .orElse(null))))); - } - - return buildUpdateAction(oldCartDiscount.getValue(), newCartDiscount.getValue(), - () -> ChangeValue.of(newCartDiscount.getValue())); + /** + * Compares the cart discount values of a {@link CartDiscount} and a {@link CartDiscountDraft} and + * returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. + * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same cart discount + * value, then no update action is needed and hence an empty {@link Optional} is returned. + * + *

Note: Order is not significant when comparing {@link AbsoluteCartDiscountValue}s + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new cart discount value. + * @return A filled optional with the update action or an empty optional if the cart discount + * values are identical. + */ + @Nonnull + public static Optional> buildChangeValueUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + + if (oldCartDiscount.getValue() instanceof AbsoluteCartDiscountValue + && newCartDiscount.getValue() instanceof AbsoluteCartDiscountValue) { + + final AbsoluteCartDiscountValue oldValue = + ((AbsoluteCartDiscountValue) oldCartDiscount.getValue()); + + final AbsoluteCartDiscountValue newValue = + ((AbsoluteCartDiscountValue) newCartDiscount.getValue()); + + return buildChangeAbsoluteValueUpdateAction(oldValue, newValue); } - @Nonnull - private static Optional buildActionIfDifferentProducts( - @Nonnull final GiftLineItemCartDiscountValue oldValue, - @Nonnull final GiftLineItemCartDiscountValue newValue) { - return buildUpdateActionForReferences(oldValue.getProduct(), newValue.getProduct(), - () -> ChangeValue.of(newValue)); + if (oldCartDiscount.getValue() instanceof GiftLineItemCartDiscountValue + && newCartDiscount.getValue() instanceof GiftLineItemCartDiscountValue) { + + final GiftLineItemCartDiscountValue oldValue = + (GiftLineItemCartDiscountValue) oldCartDiscount.getValue(); + final GiftLineItemCartDiscountValue newValue = + (GiftLineItemCartDiscountValue) newCartDiscount.getValue(); + + return Optional.ofNullable( + buildActionIfDifferentProducts(oldValue, newValue) + .orElse( + buildActionIfDifferentProductVariantIds(oldValue, newValue) + .orElse( + buildActionIfDifferentSupplyChannels(oldValue, newValue) + .orElse( + buildActionIfDifferentDistributionChannels(oldValue, newValue) + .orElse(null))))); } - @Nonnull - private static Optional buildActionIfDifferentProductVariantIds( - @Nonnull final GiftLineItemCartDiscountValue oldValue, - @Nonnull final GiftLineItemCartDiscountValue newValue) { - return buildUpdateAction(oldValue.getVariantId(), newValue.getVariantId(), - () -> ChangeValue.of(newValue)); + return buildUpdateAction( + oldCartDiscount.getValue(), + newCartDiscount.getValue(), + () -> ChangeValue.of(newCartDiscount.getValue())); + } + + @Nonnull + private static Optional buildActionIfDifferentProducts( + @Nonnull final GiftLineItemCartDiscountValue oldValue, + @Nonnull final GiftLineItemCartDiscountValue newValue) { + return buildUpdateActionForReferences( + oldValue.getProduct(), newValue.getProduct(), () -> ChangeValue.of(newValue)); + } + + @Nonnull + private static Optional buildActionIfDifferentProductVariantIds( + @Nonnull final GiftLineItemCartDiscountValue oldValue, + @Nonnull final GiftLineItemCartDiscountValue newValue) { + return buildUpdateAction( + oldValue.getVariantId(), newValue.getVariantId(), () -> ChangeValue.of(newValue)); + } + + @Nonnull + private static Optional buildActionIfDifferentSupplyChannels( + @Nonnull final GiftLineItemCartDiscountValue oldValue, + @Nonnull final GiftLineItemCartDiscountValue newValue) { + return buildUpdateActionForReferences( + oldValue.getSupplyChannel(), newValue.getSupplyChannel(), () -> ChangeValue.of(newValue)); + } + + @Nonnull + private static Optional buildActionIfDifferentDistributionChannels( + @Nonnull final GiftLineItemCartDiscountValue oldValue, + @Nonnull final GiftLineItemCartDiscountValue newValue) { + return buildUpdateActionForReferences( + oldValue.getDistributionChannel(), + newValue.getDistributionChannel(), + () -> ChangeValue.of(newValue)); + } + + @Nonnull + private static Optional> buildChangeAbsoluteValueUpdateAction( + @Nonnull final AbsoluteCartDiscountValue oldValue, + @Nonnull final AbsoluteCartDiscountValue newValue) { + + if (newValue.getMoney() == null) { + return Optional.of(ChangeValue.of(newValue)); } - @Nonnull - private static Optional buildActionIfDifferentSupplyChannels( - @Nonnull final GiftLineItemCartDiscountValue oldValue, - @Nonnull final GiftLineItemCartDiscountValue newValue) { - return buildUpdateActionForReferences(oldValue.getSupplyChannel(), newValue.getSupplyChannel(), - () -> ChangeValue.of(newValue)); + if (oldValue.getMoney().size() != newValue.getMoney().size()) { + return Optional.of(ChangeValue.of(newValue)); } - @Nonnull - private static Optional buildActionIfDifferentDistributionChannels( - @Nonnull final GiftLineItemCartDiscountValue oldValue, - @Nonnull final GiftLineItemCartDiscountValue newValue) { - return buildUpdateActionForReferences(oldValue.getDistributionChannel(), newValue.getDistributionChannel(), - () -> ChangeValue.of(newValue)); - } - - @Nonnull - private static Optional> buildChangeAbsoluteValueUpdateAction( - @Nonnull final AbsoluteCartDiscountValue oldValue, - @Nonnull final AbsoluteCartDiscountValue newValue) { - - if (newValue.getMoney() == null) { - return Optional.of(ChangeValue.of(newValue)); - } - - if (oldValue.getMoney().size() != newValue.getMoney().size()) { - return Optional.of(ChangeValue.of(newValue)); - } - - final boolean allOldValuesFoundInNewValues = oldValue.getMoney().stream().allMatch(oldAmount -> - newValue.getMoney().stream() - .filter(newAmount -> newAmount.getCurrency().equals(oldAmount.getCurrency())) + final boolean allOldValuesFoundInNewValues = + oldValue.getMoney().stream() + .allMatch( + oldAmount -> + newValue.getMoney().stream() + .filter( + newAmount -> newAmount.getCurrency().equals(oldAmount.getCurrency())) .anyMatch(newAmount -> newAmount.isEqualTo(oldAmount))); - return allOldValuesFoundInNewValues ? empty() : Optional.of(ChangeValue.of(newValue)); + return allOldValuesFoundInNewValues ? empty() : Optional.of(ChangeValue.of(newValue)); + } + + /** + * Compares the cartPredicates of a {@link CartDiscount} and a {@link CartDiscountDraft} and + * returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. + * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same cartPredicate, + * then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new cartPredicate. + * @return A filled optional with the update action or an empty optional if the cartPredicates are + * identical. + */ + @Nonnull + public static Optional> buildChangeCartPredicateUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + return buildUpdateAction( + oldCartDiscount.getCartPredicate(), + newCartDiscount.getCartPredicate(), + () -> ChangeCartPredicate.of(newCartDiscount.getCartPredicate())); + } + + /** + * Compares the cart discount target values of a {@link CartDiscount} and a {@link + * CartDiscountDraft} and returns an {@link UpdateAction}<{@link CartDiscount}> as a result + * in an {@link Optional}. If both the {@link CartDiscount} and the {@link CartDiscountDraft} have + * the same cart discount target, then no update action is needed and hence an empty {@link + * Optional} is returned. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new cart discount target. + * @return A filled optional with the update action or an empty optional if the cart discount + * targets are identical. + */ + @Nonnull + public static Optional> buildChangeTargetUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + return buildUpdateAction( + oldCartDiscount.getTarget(), + newCartDiscount.getTarget(), + () -> ChangeTarget.of(newCartDiscount.getTarget())); + } + + /** + * Compares the {@link Boolean} isActive values of a {@link CartDiscount} and a {@link + * CartDiscountDraft} and returns an {@link UpdateAction}<{@link CartDiscount}> as a result + * in an {@link Optional}. If both the {@link CartDiscount} and the {@link CartDiscountDraft} have + * the same 'isActive' value, then no update action is needed and hence an empty {@link Optional} + * is returned. + * + *

Note: A {@code null} {@code isActive} value in the {@link CartDiscount} is treated as a + * {@code true} value which is the default value of CTP. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new isActive. + * @return A filled optional with the update action or an empty optional if the isActive values + * are identical. + */ + @Nonnull + public static Optional> buildChangeIsActiveUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + final Boolean isActive = ofNullable(newCartDiscount.isActive()).orElse(true); + + return buildUpdateAction( + oldCartDiscount.isActive(), isActive, () -> ChangeIsActive.of(isActive)); + } + + /** + * Compares the {@link LocalizedString} names of a {@link CartDiscount} and a {@link + * CartDiscountDraft} and returns an {@link UpdateAction}<{@link CartDiscount}> as a result + * in an {@link Optional}. If both the {@link CartDiscount} and the {@link CartDiscountDraft} have + * the same name, then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new name. + * @return A filled optional with the update action or an empty optional if the names are + * identical. + */ + @Nonnull + public static Optional> buildChangeNameUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + return buildUpdateAction( + oldCartDiscount.getName(), + newCartDiscount.getName(), + () -> ChangeName.of(newCartDiscount.getName())); + } + + /** + * Compares the {@link LocalizedString} descriptions of a {@link CartDiscount} and a {@link + * CartDiscountDraft} and returns an {@link UpdateAction}<{@link CartDiscount}> as a result + * in an {@link Optional}. If both the {@link CartDiscount} and the {@link CartDiscountDraft} have + * the same description, then no update action is needed and hence an empty {@link Optional} is + * returned. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new description. + * @return A filled optional with the update action or an empty optional if the descriptions are + * identical. + */ + @Nonnull + public static Optional> buildSetDescriptionUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + return buildUpdateAction( + oldCartDiscount.getDescription(), + newCartDiscount.getDescription(), + () -> SetDescription.of(newCartDiscount.getDescription())); + } + + /** + * Compares the sortOrder values of a {@link CartDiscount} and a {@link CartDiscountDraft} and + * returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. + * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same sortOrder, + * then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new sortOrder. + * @return A filled optional with the update action or an empty optional if the sortOrders are + * identical. + */ + @Nonnull + public static Optional> buildChangeSortOrderUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + return buildUpdateAction( + oldCartDiscount.getSortOrder(), + newCartDiscount.getSortOrder(), + () -> ChangeSortOrder.of(newCartDiscount.getSortOrder())); + } + + /** + * Compares the {@link Boolean} requiresDiscountCode values of a {@link CartDiscount} and a {@link + * CartDiscountDraft} and returns an {@link UpdateAction}<{@link CartDiscount}> as a result + * in an {@link Optional}. If both the {@link CartDiscount} and the {@link CartDiscountDraft} have + * the same requiresDiscountCode value, then no update action is needed and hence an empty {@link + * Optional} is returned. + * + *

Note: A {@code null} {@code requiresDiscountCode} value in the {@link CartDiscount} is + * treated as a {@code false} value which is the default value of CTP. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new 'requiresDiscountCode'. + * @return A filled optional with the update action or an empty optional if the + * 'requiresDiscountCode' values are identical. + */ + @Nonnull + public static Optional> buildChangeRequiresDiscountCodeUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + final Boolean requiresDiscountCode = + ofNullable(newCartDiscount.isRequiresDiscountCode()).orElse(false); + + return buildUpdateAction( + oldCartDiscount.isRequiringDiscountCode(), + requiresDiscountCode, + () -> ChangeRequiresDiscountCode.of(requiresDiscountCode)); + } + + /** + * Compares the {@link ZonedDateTime} validFrom values of a {@link CartDiscount} and a {@link + * CartDiscountDraft} and returns an {@link UpdateAction}<{@link CartDiscount}> as a result + * in an {@link Optional}. If both the {@link CartDiscount} and the {@link CartDiscountDraft} have + * the same validFrom, then no update action is needed and hence an empty {@link Optional} is + * returned. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new validFrom. + * @return A filled optional with the update action or an empty optional if the validFrom values + * are identical. + */ + @Nonnull + public static Optional> buildSetValidFromUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + + return buildUpdateAction( + oldCartDiscount.getValidFrom(), + newCartDiscount.getValidFrom(), + () -> SetValidFrom.of(newCartDiscount.getValidFrom())); + } + + /** + * Compares the {@link ZonedDateTime} validUntil values of a {@link CartDiscount} and a {@link + * CartDiscountDraft} and returns an {@link UpdateAction}<{@link CartDiscount}> as a result + * in an {@link Optional}. If both the {@link CartDiscount} and the {@link CartDiscountDraft} have + * the same validUntil, then no update action is needed and hence an empty {@link Optional} is + * returned. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new validUntil. + * @return A filled optional with the update action or an empty optional if the validUntil values + * are identical. + */ + @Nonnull + public static Optional> buildSetValidUntilUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + + return buildUpdateAction( + oldCartDiscount.getValidUntil(), + newCartDiscount.getValidUntil(), + () -> SetValidUntil.of(newCartDiscount.getValidUntil())); + } + + /** + * Compares the {@link ZonedDateTime} validFrom and {@link ZonedDateTime} validUntil values of a + * {@link CartDiscount} and a {@link CartDiscountDraft} and returns an {@link + * UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - If both the + * {@link CartDiscount} and the {@link CartDiscountDraft} have different validFrom and same + * validUntil values, then 'setValidFrom' update action returned. - If both the {@link + * CartDiscount} and the {@link CartDiscountDraft} have the same validFrom and different + * validUntil values, then 'setValidUntil' update action returned. - If both the {@link + * CartDiscount} and the {@link CartDiscountDraft} have different validFrom and different + * validUntil values, then 'setValidFromAndUntil' update action returned. - If both the {@link + * CartDiscount} and the {@link CartDiscountDraft} have same validFrom and validUntil values, then + * no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new validFrom and validUntil. + * @return A filled optional with the update action or an empty optional if the validFrom and + * validUntil values are identical. + */ + @Nonnull + public static Optional> buildSetValidDatesUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + + final Optional> setValidFromUpdateAction = + buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscount); + + final Optional> setValidUntilUpdateAction = + buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscount); + + if (setValidFromUpdateAction.isPresent() && setValidUntilUpdateAction.isPresent()) { + return Optional.of( + SetValidFromAndUntil.of(newCartDiscount.getValidFrom(), newCartDiscount.getValidUntil())); } - /** - * Compares the cartPredicates of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same cartPredicate, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new cartPredicate. - * @return A filled optional with the update action or an empty optional if the cartPredicates are identical. - */ - @Nonnull - public static Optional> buildChangeCartPredicateUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - return buildUpdateAction(oldCartDiscount.getCartPredicate(), newCartDiscount.getCartPredicate(), - () -> ChangeCartPredicate.of(newCartDiscount.getCartPredicate())); - } - - /** - * Compares the cart discount target values of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same cart discount target, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new cart discount target. - * @return A filled optional with the update action or an empty optional if the cart discount targets are identical. - */ - @Nonnull - public static Optional> buildChangeTargetUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - return buildUpdateAction(oldCartDiscount.getTarget(), newCartDiscount.getTarget(), - () -> ChangeTarget.of(newCartDiscount.getTarget())); - } - - /** - * Compares the {@link Boolean} isActive values of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same 'isActive' value, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - *

Note: A {@code null} {@code isActive} value in the {@link CartDiscount} is treated as a - * {@code true} value which is the default value of CTP. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new isActive. - * @return A filled optional with the update action or an empty optional if the isActive values are identical. - */ - @Nonnull - public static Optional> buildChangeIsActiveUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - final Boolean isActive = ofNullable(newCartDiscount.isActive()).orElse(true); - - return buildUpdateAction(oldCartDiscount.isActive(), isActive, () -> ChangeIsActive.of(isActive)); - } - - /** - * Compares the {@link LocalizedString} names of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same name, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new name. - * @return A filled optional with the update action or an empty optional if the names are identical. - */ - @Nonnull - public static Optional> buildChangeNameUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - return buildUpdateAction(oldCartDiscount.getName(), newCartDiscount.getName(), - () -> ChangeName.of(newCartDiscount.getName())); - } - - /** - * Compares the {@link LocalizedString} descriptions of a {@link CartDiscount} and a {@link CartDiscountDraft} and - * returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. If both the - * {@link CartDiscount} and the {@link CartDiscountDraft} have the same description, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new description. - * @return A filled optional with the update action or an empty optional if the descriptions are identical. - */ - @Nonnull - public static Optional> buildSetDescriptionUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - return buildUpdateAction(oldCartDiscount.getDescription(), newCartDiscount.getDescription(), - () -> SetDescription.of(newCartDiscount.getDescription())); - } - - /** - * Compares the sortOrder values of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same sortOrder, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new sortOrder. - * @return A filled optional with the update action or an empty optional if the sortOrders are identical. - */ - @Nonnull - public static Optional> buildChangeSortOrderUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - return buildUpdateAction(oldCartDiscount.getSortOrder(), newCartDiscount.getSortOrder(), - () -> ChangeSortOrder.of(newCartDiscount.getSortOrder())); - } - - /** - * Compares the {@link Boolean} requiresDiscountCode values of a {@link CartDiscount} - * and a {@link CartDiscountDraft} and returns an {@link UpdateAction}<{@link CartDiscount}> as a result - * in an {@link Optional}. If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same - * requiresDiscountCode value, then no update action is needed and hence an empty {@link Optional} is returned. - * - *

Note: A {@code null} {@code requiresDiscountCode} value in the {@link CartDiscount} is treated as a - * {@code false} value which is the default value of CTP. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new 'requiresDiscountCode'. - * @return A filled optional with the update action or an empty optional if the 'requiresDiscountCode' values - * are identical. - */ - @Nonnull - public static Optional> buildChangeRequiresDiscountCodeUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - final Boolean requiresDiscountCode = ofNullable(newCartDiscount.isRequiresDiscountCode()).orElse(false); - - return buildUpdateAction(oldCartDiscount.isRequiringDiscountCode(), requiresDiscountCode, - () -> ChangeRequiresDiscountCode.of(requiresDiscountCode)); - } - - /** - * Compares the {@link ZonedDateTime} validFrom values of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same validFrom, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new validFrom. - * @return A filled optional with the update action or an empty optional if the validFrom values are identical. - */ - @Nonnull - public static Optional> buildSetValidFromUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - - return buildUpdateAction(oldCartDiscount.getValidFrom(), newCartDiscount.getValidFrom(), - () -> SetValidFrom.of(newCartDiscount.getValidFrom())); - } - - /** - * Compares the {@link ZonedDateTime} validUntil values of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same validUntil, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new validUntil. - * @return A filled optional with the update action or an empty optional if the validUntil values are identical. - */ - @Nonnull - public static Optional> buildSetValidUntilUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - - return buildUpdateAction(oldCartDiscount.getValidUntil(), newCartDiscount.getValidUntil(), - () -> SetValidUntil.of(newCartDiscount.getValidUntil())); - } - - /** - * Compares the {@link ZonedDateTime} validFrom and {@link ZonedDateTime} validUntil values - * of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * - If both the {@link CartDiscount} and the {@link CartDiscountDraft} have different validFrom - * and same validUntil values, then 'setValidFrom' update action returned. - * - If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same validFrom - * and different validUntil values, then 'setValidUntil' update action returned. - * - If both the {@link CartDiscount} and the {@link CartDiscountDraft} have different validFrom - * and different validUntil values, then 'setValidFromAndUntil' update action returned. - * - If both the {@link CartDiscount} and the {@link CartDiscountDraft} have same validFrom and validUntil values, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new validFrom and validUntil. - * @return A filled optional with the update action or an empty optional if the validFrom and validUntil - * values are identical. - */ - @Nonnull - public static Optional> buildSetValidDatesUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - - final Optional> setValidFromUpdateAction = - buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscount); - - final Optional> setValidUntilUpdateAction = - buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscount); - - if (setValidFromUpdateAction.isPresent() && setValidUntilUpdateAction.isPresent()) { - return Optional.of( - SetValidFromAndUntil.of(newCartDiscount.getValidFrom(), newCartDiscount.getValidUntil())); - } - - return setValidFromUpdateAction.isPresent() ? setValidFromUpdateAction : setValidUntilUpdateAction; - } - - /** - * Compares the {@link StackingMode} stacking modes of a {@link CartDiscount} and a {@link CartDiscountDraft} - * and returns an {@link UpdateAction}<{@link CartDiscount}> as a result in an {@link Optional}. - * If both the {@link CartDiscount} and the {@link CartDiscountDraft} have the same stacking mode, - * then no update action is needed and hence an empty {@link Optional} is returned. - * - *

Note: A {@code null} {@code stackingMode} value in the {@link CartDiscount} is treated as a - * {@link StackingMode#STACKING} value which is the default value of CTP. - * - * @param oldCartDiscount the cart discount which should be updated. - * @param newCartDiscount the cart discount draft where we get the new stacking mode. - * @return A filled optional with the update action or an empty optional if the stacking modes are identical. - */ - @Nonnull - public static Optional> buildChangeStackingModeUpdateAction( - @Nonnull final CartDiscount oldCartDiscount, - @Nonnull final CartDiscountDraft newCartDiscount) { - final StackingMode stackingMode = ofNullable(newCartDiscount.getStackingMode()).orElse(StackingMode.STACKING); - - return buildUpdateAction(oldCartDiscount.getStackingMode(), stackingMode, - () -> ChangeStackingMode.of(stackingMode)); - } - - private CartDiscountUpdateActionUtils() { - } + return setValidFromUpdateAction.isPresent() + ? setValidFromUpdateAction + : setValidUntilUpdateAction; + } + + /** + * Compares the {@link StackingMode} stacking modes of a {@link CartDiscount} and a {@link + * CartDiscountDraft} and returns an {@link UpdateAction}<{@link CartDiscount}> as a result + * in an {@link Optional}. If both the {@link CartDiscount} and the {@link CartDiscountDraft} have + * the same stacking mode, then no update action is needed and hence an empty {@link Optional} is + * returned. + * + *

Note: A {@code null} {@code stackingMode} value in the {@link CartDiscount} is treated as a + * {@link StackingMode#STACKING} value which is the default value of CTP. + * + * @param oldCartDiscount the cart discount which should be updated. + * @param newCartDiscount the cart discount draft where we get the new stacking mode. + * @return A filled optional with the update action or an empty optional if the stacking modes are + * identical. + */ + @Nonnull + public static Optional> buildChangeStackingModeUpdateAction( + @Nonnull final CartDiscount oldCartDiscount, + @Nonnull final CartDiscountDraft newCartDiscount) { + final StackingMode stackingMode = + ofNullable(newCartDiscount.getStackingMode()).orElse(StackingMode.STACKING); + + return buildUpdateAction( + oldCartDiscount.getStackingMode(), stackingMode, () -> ChangeStackingMode.of(stackingMode)); + } + + private CartDiscountUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/categories/CategorySync.java b/src/main/java/com/commercetools/sync/categories/CategorySync.java index 201d97d536..8ad42bc7c3 100644 --- a/src/main/java/com/commercetools/sync/categories/CategorySync.java +++ b/src/main/java/com/commercetools/sync/categories/CategorySync.java @@ -1,5 +1,13 @@ package com.commercetools.sync.categories; +import static com.commercetools.sync.categories.helpers.CategoryReferenceResolver.getParentCategoryKey; +import static com.commercetools.sync.categories.utils.CategorySyncUtils.buildActions; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.areResourceIdentifiersEqual; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.toResourceIdentifierIfNotNull; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static java.lang.String.format; + import com.commercetools.sync.categories.helpers.CategoryBatchValidator; import com.commercetools.sync.categories.helpers.CategoryReferenceResolver; import com.commercetools.sync.categories.helpers.CategorySyncStatistics; @@ -16,10 +24,6 @@ import io.sphere.sdk.client.ConcurrentModificationException; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.ResourceIdentifier; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -31,683 +35,755 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; -import static com.commercetools.sync.categories.helpers.CategoryReferenceResolver.getParentCategoryKey; -import static com.commercetools.sync.categories.utils.CategorySyncUtils.buildActions; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.areResourceIdentifiersEqual; -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.toResourceIdentifierIfNotNull; -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static java.lang.String.format; - -public class CategorySync extends BaseSync { - - private static final String FAILED_TO_PROCESS = "Failed to process the CategoryDraft with key:'%s'. Reason: %s"; - private static final String UPDATE_FAILED = "Failed to update Category with key: '%s'. Reason: %s"; - - private final CategoryService categoryService; - private final CategoryReferenceResolver referenceResolver; - private final CategoryBatchValidator batchValidator; - - /** - * The following set ({@code processedCategoryKeys}) is thread-safe because it is accessed/modified in a concurrent - * context, specifically when updating products in parallel in - * {@link #updateCategory(Category, CategoryDraft, List)}. It also has a global scope across the entire sync - * process, which means the same instance is used across all batch executions. - */ - private final ConcurrentHashMap.KeySetView processedCategoryKeys = ConcurrentHashMap.newKeySet(); - - /** - * The following set ({@code categoryKeysWithResolvedParents}) and map ({@code categoryDraftsToUpdate}) are - * thread-safe because they are accessed/modified in a concurrent context, specifically when updating products in - * parallel in {@link #updateCategoriesInParallel(Map)}. They have a local scope within every batch execution, which - * means that they are re-initialized on every {@link #processBatch(List)} call. - */ - private ConcurrentHashMap.KeySetView categoryKeysWithResolvedParents = - ConcurrentHashMap.newKeySet(); - private ConcurrentHashMap categoryDraftsToUpdate = new ConcurrentHashMap<>(); - - /** - * The following sets ({@code existingCategoryDrafts}, {@code newCategoryDrafts}, {@code referencesResolvedDrafts} - * and {@code categoryKeysToFetch}) are not thread-safe because they are accessed/modified in a - * non-concurrent/sequential context. They have a local scope within every batch execution, which - * means that they are re-initialized on every {@link #processBatch(List)} call. - */ - private Set existingCategoryDrafts = new HashSet<>(); - private Set newCategoryDrafts = new HashSet<>(); - private Set referencesResolvedDrafts = new HashSet<>(); - private Set categoryKeysToFetch = new HashSet<>(); - - - /** - * Takes a {@link CategorySyncOptions} instance to instantiate a new {@link CategorySync} instance that could be - * used to sync category drafts with the given categories in the CTP project specified in the injected - * {@link CategorySyncOptions} instance. - * - * @param syncOptions the container of all the options of the sync process including the CTP project client and/or - * configuration and other sync-specific options. - */ - public CategorySync(@Nonnull final CategorySyncOptions syncOptions) { - this(syncOptions, new TypeServiceImpl(syncOptions), new CategoryServiceImpl(syncOptions)); - } - - /** - * Takes a {@link CategorySyncOptions}, a {@link TypeService} and {@link CategoryService} instances to instantiate - * a new {@link CategorySync} instance that could be used to sync categories or category drafts with the given - * categories in the CTP project specified in the injected {@link CategorySyncOptions} instance. - * - *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and passed to. - * - * @param syncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param typeService the type service which is responsible for fetching/caching the Types from the CTP project. - * @param categoryService the category service which is responsible for fetching, creating and updating categories - * from and to the CTP project. - */ - CategorySync(@Nonnull final CategorySyncOptions syncOptions, - @Nonnull final TypeService typeService, - @Nonnull final CategoryService categoryService) { - super(new CategorySyncStatistics(), syncOptions); - this.categoryService = categoryService; - this.referenceResolver = new CategoryReferenceResolver(getSyncOptions(), typeService, categoryService); - this.batchValidator = new CategoryBatchValidator(getSyncOptions(), getStatistics()); - } - - /** - * Given a list of {@code CategoryDraft} that represent a batch of category drafts, this method for the first batch - * only caches a list of all the categories in the CTP project in a cached map that representing each category's - * key to the id. It then validates the category drafts, then resolves all the references. Then it creates all - * categories that need to be created in parallel while keeping track of the categories that have their - * non-existing parents. Then it does update actions that don't require parent changes in parallel. Then in a - * blocking fashion issues update actions that don't involve parent changes sequentially. - * - *

More on the exact implementation of how the sync works here: - * https://sphere.atlassian.net/wiki/spaces/PS/pages/145193124/Category+Parallelisation+Technical+Concept - * - * @param categoryDrafts the list of new category drafts to sync to the CTP project. - * @return an instance of {@link CompletionStage}<{@link CategorySyncStatistics}> which contains as a result - * an instance of {@link CategorySyncStatistics} representing the {@code statistics} instance attribute of - * {@code this} {@link CategorySync}. - */ - @Override - protected CompletionStage process(@Nonnull final List categoryDrafts) { - final List> batches = batchElements(categoryDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, CompletableFuture.completedFuture(statistics)); +public class CategorySync + extends BaseSync { + + private static final String FAILED_TO_PROCESS = + "Failed to process the CategoryDraft with key:'%s'. Reason: %s"; + private static final String UPDATE_FAILED = + "Failed to update Category with key: '%s'. Reason: %s"; + + private final CategoryService categoryService; + private final CategoryReferenceResolver referenceResolver; + private final CategoryBatchValidator batchValidator; + + /** + * The following set ({@code processedCategoryKeys}) is thread-safe because it is + * accessed/modified in a concurrent context, specifically when updating products in parallel in + * {@link #updateCategory(Category, CategoryDraft, List)}. It also has a global scope across the + * entire sync process, which means the same instance is used across all batch executions. + */ + private final ConcurrentHashMap.KeySetView processedCategoryKeys = + ConcurrentHashMap.newKeySet(); + + /** + * The following set ({@code categoryKeysWithResolvedParents}) and map ({@code + * categoryDraftsToUpdate}) are thread-safe because they are accessed/modified in a concurrent + * context, specifically when updating products in parallel in {@link + * #updateCategoriesInParallel(Map)}. They have a local scope within every batch execution, which + * means that they are re-initialized on every {@link #processBatch(List)} call. + */ + private ConcurrentHashMap.KeySetView categoryKeysWithResolvedParents = + ConcurrentHashMap.newKeySet(); + + private ConcurrentHashMap categoryDraftsToUpdate = + new ConcurrentHashMap<>(); + + /** + * The following sets ({@code existingCategoryDrafts}, {@code newCategoryDrafts}, {@code + * referencesResolvedDrafts} and {@code categoryKeysToFetch}) are not thread-safe because they are + * accessed/modified in a non-concurrent/sequential context. They have a local scope within every + * batch execution, which means that they are re-initialized on every {@link #processBatch(List)} + * call. + */ + private Set existingCategoryDrafts = new HashSet<>(); + + private Set newCategoryDrafts = new HashSet<>(); + private Set referencesResolvedDrafts = new HashSet<>(); + private Set categoryKeysToFetch = new HashSet<>(); + + /** + * Takes a {@link CategorySyncOptions} instance to instantiate a new {@link CategorySync} instance + * that could be used to sync category drafts with the given categories in the CTP project + * specified in the injected {@link CategorySyncOptions} instance. + * + * @param syncOptions the container of all the options of the sync process including the CTP + * project client and/or configuration and other sync-specific options. + */ + public CategorySync(@Nonnull final CategorySyncOptions syncOptions) { + this(syncOptions, new TypeServiceImpl(syncOptions), new CategoryServiceImpl(syncOptions)); + } + + /** + * Takes a {@link CategorySyncOptions}, a {@link TypeService} and {@link CategoryService} + * instances to instantiate a new {@link CategorySync} instance that could be used to sync + * categories or category drafts with the given categories in the CTP project specified in the + * injected {@link CategorySyncOptions} instance. + * + *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and + * passed to. + * + * @param syncOptions the container of all the options of the sync process including the CTP + * project client and/or configuration and other sync-specific options. + * @param typeService the type service which is responsible for fetching/caching the Types from + * the CTP project. + * @param categoryService the category service which is responsible for fetching, creating and + * updating categories from and to the CTP project. + */ + CategorySync( + @Nonnull final CategorySyncOptions syncOptions, + @Nonnull final TypeService typeService, + @Nonnull final CategoryService categoryService) { + super(new CategorySyncStatistics(), syncOptions); + this.categoryService = categoryService; + this.referenceResolver = + new CategoryReferenceResolver(getSyncOptions(), typeService, categoryService); + this.batchValidator = new CategoryBatchValidator(getSyncOptions(), getStatistics()); + } + + /** + * Given a list of {@code CategoryDraft} that represent a batch of category drafts, this method + * for the first batch only caches a list of all the categories in the CTP project in a cached map + * that representing each category's key to the id. It then validates the category drafts, then + * resolves all the references. Then it creates all categories that need to be created in parallel + * while keeping track of the categories that have their non-existing parents. Then it does update + * actions that don't require parent changes in parallel. Then in a blocking fashion issues update + * actions that don't involve parent changes sequentially. + * + *

More on the exact implementation of how the sync works here: + * https://sphere.atlassian.net/wiki/spaces/PS/pages/145193124/Category+Parallelisation+Technical+Concept + * + * @param categoryDrafts the list of new category drafts to sync to the CTP project. + * @return an instance of {@link CompletionStage}<{@link CategorySyncStatistics}> which + * contains as a result an instance of {@link CategorySyncStatistics} representing the {@code + * statistics} instance attribute of {@code this} {@link CategorySync}. + */ + @Override + protected CompletionStage process( + @Nonnull final List categoryDrafts) { + final List> batches = + batchElements(categoryDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, CompletableFuture.completedFuture(statistics)); + } + + /** + * Given a list of {@code CategoryDraft} that represent a batch of category drafts, this method + * for the first batch only caches a mapping of key to the id of all categories in the CTP + * project. It then validates the category drafts, then resolves all the references. Then it + * creates all categories that need to be created in parallel while keeping track of the + * categories that have their non-existing parents. Then it does update actions that don't require + * parent changes in parallel. Then in a blocking fashion issues update actions that don't involve + * parent changes sequentially. + * + *

In case of error during of fetch during the caching of category keys or during of fetching + * of existing categories, the error callback will be triggered. And the sync process would stop + * for the given batch. + * + *

More on the exact implementation of how the sync works here: + * https://github.com/commercetools/commercetools-sync-java/wiki/Category-Sync-Underlying-Concept + * + * @param categoryDrafts the list of new category drafts to sync to the CTP project. + * @return an instance of {@link CompletionStage}<{@link CategorySyncStatistics}> which + * contains as a result an instance of {@link CategorySyncStatistics} representing the {@code + * statistics} instance attribute of {@code this} {@link CategorySync}. + */ + @Override + protected CompletionStage processBatch( + @Nonnull final List categoryDrafts) { + final int numberOfNewDraftsToProcess = getNumberOfDraftsToProcess(categoryDrafts); + referencesResolvedDrafts = new HashSet<>(); + existingCategoryDrafts = new HashSet<>(); + newCategoryDrafts = new HashSet<>(); + + categoryKeysWithResolvedParents = ConcurrentHashMap.newKeySet(); + categoryDraftsToUpdate = new ConcurrentHashMap<>(); + + final ImmutablePair, CategoryBatchValidator.ReferencedKeys> result = + batchValidator.validateAndCollectReferencedKeys(categoryDrafts); + + final Set validDrafts = result.getLeft(); + if (validDrafts.isEmpty()) { + statistics.incrementProcessed(categoryDrafts.size()); + return CompletableFuture.completedFuture(statistics); } - /** - * Given a list of {@code CategoryDraft} that represent a batch of category drafts, this method for the first batch - * only caches a mapping of key to the id of all categories in the CTP project. It then validates the - * category drafts, then resolves all the references. Then it creates all categories that need to be created in - * parallel while keeping track of the categories that have their non-existing parents. Then it does update actions - * that don't require parent changes in parallel. Then in a blocking fashion issues update actions that don't - * involve parent changes sequentially. - * - * - *

In case of error during of fetch during the caching of category keys or during of fetching of existing - * categories, the error callback will be triggered. And the sync process would stop for the given batch. - *

- * - * - *

More on the exact implementation of how the sync works here: - * https://github.com/commercetools/commercetools-sync-java/wiki/Category-Sync-Underlying-Concept - * - * @param categoryDrafts the list of new category drafts to sync to the CTP project. - * @return an instance of {@link CompletionStage}<{@link CategorySyncStatistics}> which contains as a result - * an instance of {@link CategorySyncStatistics} representing the {@code statistics} instance attribute of - * {@code this} {@link CategorySync}. - */ - @Override - protected CompletionStage processBatch(@Nonnull final List categoryDrafts) { - final int numberOfNewDraftsToProcess = getNumberOfDraftsToProcess(categoryDrafts); - referencesResolvedDrafts = new HashSet<>(); - existingCategoryDrafts = new HashSet<>(); - newCategoryDrafts = new HashSet<>(); - - categoryKeysWithResolvedParents = ConcurrentHashMap.newKeySet(); - categoryDraftsToUpdate = new ConcurrentHashMap<>(); - - final ImmutablePair, CategoryBatchValidator.ReferencedKeys> result = - batchValidator.validateAndCollectReferencedKeys(categoryDrafts); - - final Set validDrafts = result.getLeft(); - if (validDrafts.isEmpty()) { - statistics.incrementProcessed(categoryDrafts.size()); - return CompletableFuture.completedFuture(statistics); - } - - return referenceResolver - .populateKeyToIdCachesForReferencedKeys(result.getRight()) - .handle(ImmutablePair::new) - .thenCompose(cachingResponse -> { - final Throwable cachingException = cachingResponse.getValue(); - if (cachingException != null) { - handleError(new SyncException("Failed to build a cache of keys to ids.", cachingException), - validDrafts.size()); - return CompletableFuture.completedFuture(null); - } - - final Map categoryKeyToIdCache = cachingResponse.getKey(); - prepareDraftsForProcessing(new ArrayList<>(validDrafts), categoryKeyToIdCache); - - categoryKeysToFetch = existingCategoryDrafts - .stream() - .map(CategoryDraft::getKey) - .collect(Collectors.toSet()); - - return createAndUpdate(categoryKeyToIdCache); + return referenceResolver + .populateKeyToIdCachesForReferencedKeys(result.getRight()) + .handle(ImmutablePair::new) + .thenCompose( + cachingResponse -> { + final Throwable cachingException = cachingResponse.getValue(); + if (cachingException != null) { + handleError( + new SyncException("Failed to build a cache of keys to ids.", cachingException), + validDrafts.size()); + return CompletableFuture.completedFuture(null); + } + + final Map categoryKeyToIdCache = cachingResponse.getKey(); + prepareDraftsForProcessing(new ArrayList<>(validDrafts), categoryKeyToIdCache); + + categoryKeysToFetch = + existingCategoryDrafts.stream() + .map(CategoryDraft::getKey) + .collect(Collectors.toSet()); + + return createAndUpdate(categoryKeyToIdCache); }) - .thenApply(ignoredResult -> { - statistics.incrementProcessed(numberOfNewDraftsToProcess); - return statistics; + .thenApply( + ignoredResult -> { + statistics.incrementProcessed(numberOfNewDraftsToProcess); + return statistics; }); - } - - private CompletionStage fetchAndUpdate(@Nonnull final Map keyToIdCache) { - return categoryService - .fetchMatchingCategoriesByKeys(categoryKeysToFetch) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Set fetchedCategories = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = - format("Failed to fetch existing categories with keys: '%s'.", categoryKeysToFetch); - handleError(new SyncException(errorMessage, exception), categoryKeysToFetch.size()); - return CompletableFuture.completedFuture(null); - } - - return processFetchedCategoriesAndUpdate(keyToIdCache, fetchedCategories); + } + + private CompletionStage fetchAndUpdate(@Nonnull final Map keyToIdCache) { + return categoryService + .fetchMatchingCategoriesByKeys(categoryKeysToFetch) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Set fetchedCategories = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + "Failed to fetch existing categories with keys: '%s'.", + categoryKeysToFetch); + handleError(new SyncException(errorMessage, exception), categoryKeysToFetch.size()); + return CompletableFuture.completedFuture(null); + } + + return processFetchedCategoriesAndUpdate(keyToIdCache, fetchedCategories); }); - } - - @Nonnull - private CompletionStage processFetchedCategoriesAndUpdate(@Nonnull final Map keyToIdCache, - @Nonnull final Set fetchedCategories) { - - processFetchedCategories(fetchedCategories, referencesResolvedDrafts, keyToIdCache); - updateCategoriesSequentially(categoryDraftsToUpdate); - return updateCategoriesInParallel(categoryDraftsToUpdate); - } - - /** - * Given a list of {@code CategoryDraft} elements, this method calculates the number of drafts that need to be - * processed in this batch, given they weren't processed before, plus the number of null drafts and drafts with null - * keys. Drafts which were processed before, will have their keys stored in {@code processedCategoryKeys}. - * - * @param categoryDrafts the input list of category drafts in the sync batch. - * @return the number of drafts that are needed to be processed. - */ - private int getNumberOfDraftsToProcess(@Nonnull final List categoryDrafts) { - final long numberOfNullCategoryDrafts = categoryDrafts - .stream() - .filter(Objects::isNull) - .count(); - - final long numberOfCategoryDraftsNotProcessedBefore = categoryDrafts - .stream() + } + + @Nonnull + private CompletionStage processFetchedCategoriesAndUpdate( + @Nonnull final Map keyToIdCache, + @Nonnull final Set fetchedCategories) { + + processFetchedCategories(fetchedCategories, referencesResolvedDrafts, keyToIdCache); + updateCategoriesSequentially(categoryDraftsToUpdate); + return updateCategoriesInParallel(categoryDraftsToUpdate); + } + + /** + * Given a list of {@code CategoryDraft} elements, this method calculates the number of drafts + * that need to be processed in this batch, given they weren't processed before, plus the number + * of null drafts and drafts with null keys. Drafts which were processed before, will have their + * keys stored in {@code processedCategoryKeys}. + * + * @param categoryDrafts the input list of category drafts in the sync batch. + * @return the number of drafts that are needed to be processed. + */ + private int getNumberOfDraftsToProcess(@Nonnull final List categoryDrafts) { + final long numberOfNullCategoryDrafts = categoryDrafts.stream().filter(Objects::isNull).count(); + + final long numberOfCategoryDraftsNotProcessedBefore = + categoryDrafts.stream() .filter(Objects::nonNull) .map(CategoryDraft::getKey) - .filter(categoryDraftKey -> categoryDraftKey == null || !processedCategoryKeys.contains(categoryDraftKey)) + .filter( + categoryDraftKey -> + categoryDraftKey == null || !processedCategoryKeys.contains(categoryDraftKey)) .count(); - return (int) (numberOfCategoryDraftsNotProcessedBefore + numberOfNullCategoryDrafts); - } - - /** - * This method does the following on each category draft input in the sync batch: - *

    - *
  1. First checks if the key is set on the draft, if not then the error callback is triggered and the - * draft is skipped.
  2. - *
  3. Checks if the draft is {@code null}, then the error callback is triggered and the - * draft is skipped.
  4. - *
  5. Then for each draft adds each with a non-existing parent in keyToId cached map to a map - * {@code statistics#categoryKeysWithMissingParents} (mapping from parent key to list of subcategory keys)
  6. - *
  7. Then it resolves the references (parent category reference and custom type reference) on each draft. For - * each draft with resolved references: - *
      - *
    1. Checks if the draft exists, then it adds it to the {@code existingCategoryDrafts} array.
    2. - *
    3. If the draft doesn't exist, then it adds it to the {@code newCategoryDrafts} array.
    4. - *
    - *
  8. - *
- * If reference resolution failed either during getting the parent category key or during actual reference - * resolution, the error callback is triggered and the category is skipped. - * - * @param categoryDrafts the input list of category drafts in the sync batch. - * @param keyToIdCache the cache containing the mapping of all existing category keys to ids. - */ - private void prepareDraftsForProcessing(@Nonnull final List categoryDrafts, - @Nonnull final Map keyToIdCache) { - for (CategoryDraft categoryDraft : categoryDrafts) { - final String categoryKey = categoryDraft.getKey(); - try { - categoryDraft = updateCategoriesWithMissingParents(categoryDraft, keyToIdCache); - referenceResolver.resolveReferences(categoryDraft) - .thenAccept(referencesResolvedDraft -> { - referencesResolvedDrafts.add(referencesResolvedDraft); - if (keyToIdCache.containsKey(categoryKey)) { - existingCategoryDrafts.add(referencesResolvedDraft); - } else { - newCategoryDrafts.add(referencesResolvedDraft); - } - }) - .exceptionally(completionException -> { - final String errorMessage = format(FAILED_TO_PROCESS, categoryKey, - completionException.getMessage()); - handleError(errorMessage, completionException); - return null; - }).toCompletableFuture().join(); - } catch (Exception exception) { - final String errorMessage = format(FAILED_TO_PROCESS, categoryKey, - exception); - handleError(errorMessage, exception); - } - } - } - - @Nonnull - private CompletionStage createAndUpdate(@Nonnull final Map keyToIdCache) { - return createCategories(newCategoryDrafts) - .thenAccept(this::processCreatedCategories) - .thenCompose(ignoredResult -> fetchAndUpdate(keyToIdCache)); - } - - @Nonnull - private CompletionStage> createCategories(@Nonnull final Set categoryDrafts) { - return mapValuesToFutureOfCompletedValues(categoryDrafts, this::applyCallbackAndCreate) - .thenApply(results -> results.filter(Optional::isPresent).map(Optional::get)) - .thenApply(createdCategories -> createdCategories.collect(Collectors.toSet())); - } - - @Nonnull - private CompletionStage> applyCallbackAndCreate(@Nonnull final CategoryDraft categoryDraft) { - return syncOptions - .applyBeforeCreateCallback(categoryDraft) - .map(categoryService::createCategory) - .orElse(CompletableFuture.completedFuture(Optional.empty())); - } - - /** - * This method first gets the parent key either from the expanded category object or from the id field on the - * reference and validates it. If it is valid, then it checks if the parent category is missing, this is done by - * checking if the key exists in the {@code keyToIdCache} map. If it is missing, then it adds the key to the map - * {@code statistics#categoryKeysWithMissingParents}, then it returns a category draft identical to the supplied one - * but with a {@code null} parent. If it is not missing, then the same identical category draft is returned with the - * same parent. - * - * @param categoryDraft the category draft to check whether it's parent is missing or not. - * @param keyToIdCache the cache containing the mapping of all existing category keys to ids. - * @return the same identical supplied category draft. However, with a null parent field, if the parent is missing. - * @throws ReferenceResolutionException thrown if the parent key is not valid. - */ - private CategoryDraft updateCategoriesWithMissingParents(@Nonnull final CategoryDraft categoryDraft, - @Nonnull final Map keyToIdCache) - throws ReferenceResolutionException { - return getParentCategoryKey(categoryDraft) - .map(parentCategoryKey -> { - if (isMissingCategory(parentCategoryKey, keyToIdCache)) { - statistics.putMissingParentCategoryChildKey(parentCategoryKey, categoryDraft.getKey()); - return CategoryDraftBuilder.of(categoryDraft) - .parent((ResourceIdentifier) null) - .build(); - } - return categoryDraft; - }).orElse(categoryDraft); - } - - /** - * Checks if the category with the given {@code categoryKey} exists or not, by checking if its key - * exists in the {@code keyToIdCache} map. - * - * @param categoryKey the key of the category to check for existence. - * @param keyToIdCache the cache of existing category keys to ids. - * @return true or false, whether the category exists or not. - */ - private boolean isMissingCategory(@Nonnull final String categoryKey, - @Nonnull final Map keyToIdCache) { - return !keyToIdCache.containsKey(categoryKey); + return (int) (numberOfCategoryDraftsNotProcessedBefore + numberOfNullCategoryDrafts); + } + + /** + * This method does the following on each category draft input in the sync batch: + * + *
    + *
  1. First checks if the key is set on the draft, if not then the error callback is triggered + * and the draft is skipped. + *
  2. Checks if the draft is {@code null}, then the error callback is triggered and the draft + * is skipped. + *
  3. Then for each draft adds each with a non-existing parent in keyToId cached map to a map + * {@code statistics#categoryKeysWithMissingParents} (mapping from parent key to list of + * subcategory keys) + *
  4. Then it resolves the references (parent category reference and custom type reference) on + * each draft. For each draft with resolved references: + *
      + *
    1. Checks if the draft exists, then it adds it to the {@code existingCategoryDrafts} + * array. + *
    2. If the draft doesn't exist, then it adds it to the {@code newCategoryDrafts} array. + *
    + *
+ * + * If reference resolution failed either during getting the parent category key or during actual + * reference resolution, the error callback is triggered and the category is skipped. + * + * @param categoryDrafts the input list of category drafts in the sync batch. + * @param keyToIdCache the cache containing the mapping of all existing category keys to ids. + */ + private void prepareDraftsForProcessing( + @Nonnull final List categoryDrafts, + @Nonnull final Map keyToIdCache) { + for (CategoryDraft categoryDraft : categoryDrafts) { + final String categoryKey = categoryDraft.getKey(); + try { + categoryDraft = updateCategoriesWithMissingParents(categoryDraft, keyToIdCache); + referenceResolver + .resolveReferences(categoryDraft) + .thenAccept( + referencesResolvedDraft -> { + referencesResolvedDrafts.add(referencesResolvedDraft); + if (keyToIdCache.containsKey(categoryKey)) { + existingCategoryDrafts.add(referencesResolvedDraft); + } else { + newCategoryDrafts.add(referencesResolvedDraft); + } + }) + .exceptionally( + completionException -> { + final String errorMessage = + format(FAILED_TO_PROCESS, categoryKey, completionException.getMessage()); + handleError(errorMessage, completionException); + return null; + }) + .toCompletableFuture() + .join(); + } catch (Exception exception) { + final String errorMessage = format(FAILED_TO_PROCESS, categoryKey, exception); + handleError(errorMessage, exception); + } } - - - /** - * This method does the following on each category created from the provided {@link Set} of categories: - *
    - *
  1. Adds its keys to {@code processedCategoryKeys} in order to not increment updated and processed counters of - * statistics more than needed. For example, when updating the parent later on of this created category. - *
  2. - * - *
  3. Check if it exists in {@code statistics#categoryKeysWithMissingParents} as a missing parent, if it does then - * its children are now ready to update their parent field references with the created parent category. For each of - * these child categories do the following: - *
      - *
    1. Add its key to the list {@code categoryKeysWithResolvedParents}.
    2. - *
    3. If the key was in the {@code newCategoryDrafts} list, then it means it should have just been created. - * Then it adds the category created to the {@code categoryDraftsToUpdate}. The draft is created from the - * created Category response from CTP but parent is taken from the {@code statistics#categoryKeysWithMissingParents} - *
    4. - *
    5. Otherwise, if it wasn't in the {@code newCategoryDrafts} list, then it means it needs to be - * fetched. Therefore, its key is added it to the {@code categoryKeysToFetch}
    6. - *
    - *
  4. - *
- * - * @param createdCategories the set of created categories that needs to be processed. - */ - private void processCreatedCategories(@Nonnull final Set createdCategories) { - final int numberOfFailedCategories = newCategoryDrafts.size() - createdCategories.size(); - statistics.incrementFailed(numberOfFailedCategories); - - statistics.incrementCreated(createdCategories.size()); - createdCategories.forEach(createdCategory -> { - final String createdCategoryKey = createdCategory.getKey(); - processedCategoryKeys.add(createdCategoryKey); - final Set childCategoryKeys = statistics.getCategoryKeysWithMissingParents() - .get(createdCategoryKey); - - if (childCategoryKeys != null) { - for (String childCategoryKey : childCategoryKeys) { - categoryKeysWithResolvedParents.add(childCategoryKey); - final Optional createdChild = getCategoryByKeyIfExists(createdCategories, - childCategoryKey); - if (createdChild.isPresent()) { - final Category category = createdChild.get(); - final CategoryDraft categoryDraft = - CategoryDraftBuilder.of(category) - .parent(createdCategory.toResourceIdentifier()) - .build(); - categoryDraftsToUpdate.put(categoryDraft, category); - } else { - categoryKeysToFetch.add(childCategoryKey); - } - } + } + + @Nonnull + private CompletionStage createAndUpdate(@Nonnull final Map keyToIdCache) { + return createCategories(newCategoryDrafts) + .thenAccept(this::processCreatedCategories) + .thenCompose(ignoredResult -> fetchAndUpdate(keyToIdCache)); + } + + @Nonnull + private CompletionStage> createCategories( + @Nonnull final Set categoryDrafts) { + return mapValuesToFutureOfCompletedValues(categoryDrafts, this::applyCallbackAndCreate) + .thenApply(results -> results.filter(Optional::isPresent).map(Optional::get)) + .thenApply(createdCategories -> createdCategories.collect(Collectors.toSet())); + } + + @Nonnull + private CompletionStage> applyCallbackAndCreate( + @Nonnull final CategoryDraft categoryDraft) { + return syncOptions + .applyBeforeCreateCallback(categoryDraft) + .map(categoryService::createCategory) + .orElse(CompletableFuture.completedFuture(Optional.empty())); + } + + /** + * This method first gets the parent key either from the expanded category object or from the id + * field on the reference and validates it. If it is valid, then it checks if the parent category + * is missing, this is done by checking if the key exists in the {@code keyToIdCache} map. If it + * is missing, then it adds the key to the map {@code statistics#categoryKeysWithMissingParents}, + * then it returns a category draft identical to the supplied one but with a {@code null} parent. + * If it is not missing, then the same identical category draft is returned with the same parent. + * + * @param categoryDraft the category draft to check whether it's parent is missing or not. + * @param keyToIdCache the cache containing the mapping of all existing category keys to ids. + * @return the same identical supplied category draft. However, with a null parent field, if the + * parent is missing. + * @throws ReferenceResolutionException thrown if the parent key is not valid. + */ + private CategoryDraft updateCategoriesWithMissingParents( + @Nonnull final CategoryDraft categoryDraft, @Nonnull final Map keyToIdCache) + throws ReferenceResolutionException { + return getParentCategoryKey(categoryDraft) + .map( + parentCategoryKey -> { + if (isMissingCategory(parentCategoryKey, keyToIdCache)) { + statistics.putMissingParentCategoryChildKey( + parentCategoryKey, categoryDraft.getKey()); + return CategoryDraftBuilder.of(categoryDraft) + .parent((ResourceIdentifier) null) + .build(); + } + return categoryDraft; + }) + .orElse(categoryDraft); + } + + /** + * Checks if the category with the given {@code categoryKey} exists or not, by checking if its key + * exists in the {@code keyToIdCache} map. + * + * @param categoryKey the key of the category to check for existence. + * @param keyToIdCache the cache of existing category keys to ids. + * @return true or false, whether the category exists or not. + */ + private boolean isMissingCategory( + @Nonnull final String categoryKey, @Nonnull final Map keyToIdCache) { + return !keyToIdCache.containsKey(categoryKey); + } + + /** + * This method does the following on each category created from the provided {@link Set} of + * categories: + * + *
    + *
  1. Adds its keys to {@code processedCategoryKeys} in order to not increment updated and + * processed counters of statistics more than needed. For example, when updating the parent + * later on of this created category. + *
  2. Check if it exists in {@code statistics#categoryKeysWithMissingParents} as a missing + * parent, if it does then its children are now ready to update their parent field + * references with the created parent category. For each of these child categories do the + * following: + *
      + *
    1. Add its key to the list {@code categoryKeysWithResolvedParents}. + *
    2. If the key was in the {@code newCategoryDrafts} list, then it means it should have + * just been created. Then it adds the category created to the {@code + * categoryDraftsToUpdate}. The draft is created from the created Category response + * from CTP but parent is taken from the {@code + * statistics#categoryKeysWithMissingParents} + *
    3. Otherwise, if it wasn't in the {@code newCategoryDrafts} list, then it means it + * needs to be fetched. Therefore, its key is added it to the {@code + * categoryKeysToFetch} + *
    + *
+ * + * @param createdCategories the set of created categories that needs to be processed. + */ + private void processCreatedCategories(@Nonnull final Set createdCategories) { + final int numberOfFailedCategories = newCategoryDrafts.size() - createdCategories.size(); + statistics.incrementFailed(numberOfFailedCategories); + + statistics.incrementCreated(createdCategories.size()); + createdCategories.forEach( + createdCategory -> { + final String createdCategoryKey = createdCategory.getKey(); + processedCategoryKeys.add(createdCategoryKey); + final Set childCategoryKeys = + statistics.getCategoryKeysWithMissingParents().get(createdCategoryKey); + + if (childCategoryKeys != null) { + for (String childCategoryKey : childCategoryKeys) { + categoryKeysWithResolvedParents.add(childCategoryKey); + final Optional createdChild = + getCategoryByKeyIfExists(createdCategories, childCategoryKey); + if (createdChild.isPresent()) { + final Category category = createdChild.get(); + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of(category) + .parent(createdCategory.toResourceIdentifier()) + .build(); + categoryDraftsToUpdate.put(categoryDraft, category); + } else { + categoryKeysToFetch.add(childCategoryKey); + } } + } }); - } - - /** - * Given a {@code Set} of categories which have just been fetched, this method does the following on each category: - *
    - *
  1. If the the draft exists in the input list: - *
      - *
    1. a copy is created from it.
    2. - *
    3. If the parent reference on the draft is null, It means the parent might not yet be created, therefore - * the parent from the fetched category is used. This is to avoid having the error callback being called for - * un setting a parent (which is not possible by the CTP API).
    4. - *
    - *
  2. - *
  3. If not, the draft is copied from the fetched category.
  4. - *
  5. If the key of this draft exists in the {@code categoryKeysWithResolvedParents}, overwrite the parent with - * the parent saved in {@code statistics#categoryKeysWithMissingParents} for the draft.
  6. - *
  7. After a draft has been created, it is added to {@code categoryDraftsToUpdate} map as a key and - * the value is the fetched {@link Category}.
  8. - *
- * - * @param fetchedCategories {@code Set} of categories which have just been fetched and - * @param resolvedReferencesDrafts {@code Set} of CategoryDrafts with resolved references, they are used to get - * a draft with a resolved reference for the input list of drafts. - * @param keyToIdCache the cache containing mapping of all existing category keys to ids. - */ - private void processFetchedCategories(@Nonnull final Set fetchedCategories, - @Nonnull final Set resolvedReferencesDrafts, - @Nonnull final Map keyToIdCache) { - fetchedCategories.forEach(fetchedCategory -> { - final String fetchedCategoryKey = fetchedCategory.getKey(); - final Optional draftByKeyIfExists = - getDraftByKeyIfExists(resolvedReferencesDrafts, fetchedCategoryKey); - final CategoryDraftBuilder categoryDraftBuilder = - draftByKeyIfExists.map(categoryDraft -> { - if (categoryDraft.getParent() == null) { - return CategoryDraftBuilder.of(categoryDraft) - .parent(toResourceIdentifierIfNotNull(fetchedCategory.getParent())); - } - return CategoryDraftBuilder.of(categoryDraft); - }) - .orElseGet(() -> CategoryDraftBuilder.of(fetchedCategory)); - if (categoryKeysWithResolvedParents.contains(fetchedCategoryKey)) { - statistics.getMissingParentKey(fetchedCategoryKey) - .ifPresent(missingParentKey -> { - final String parentId = keyToIdCache.get(missingParentKey); - categoryDraftBuilder - .parent(Category.referenceOfId(parentId).toResourceIdentifier()); - }); - } - categoryDraftsToUpdate.put(categoryDraftBuilder.build(), fetchedCategory); + } + + /** + * Given a {@code Set} of categories which have just been fetched, this method does the following + * on each category: + * + *
    + *
  1. If the the draft exists in the input list: + *
      + *
    1. a copy is created from it. + *
    2. If the parent reference on the draft is null, It means the parent might not yet be + * created, therefore the parent from the fetched category is used. This is to avoid + * having the error callback being called for un setting a parent (which is not + * possible by the CTP API). + *
    + *
  2. If not, the draft is copied from the fetched category. + *
  3. If the key of this draft exists in the {@code categoryKeysWithResolvedParents}, overwrite + * the parent with the parent saved in {@code statistics#categoryKeysWithMissingParents} for + * the draft. + *
  4. After a draft has been created, it is added to {@code categoryDraftsToUpdate} map as a + * key and the value is the fetched {@link Category}. + *
+ * + * @param fetchedCategories {@code Set} of categories which have just been fetched and + * @param resolvedReferencesDrafts {@code Set} of CategoryDrafts with resolved references, they + * are used to get a draft with a resolved reference for the input list of drafts. + * @param keyToIdCache the cache containing mapping of all existing category keys to ids. + */ + private void processFetchedCategories( + @Nonnull final Set fetchedCategories, + @Nonnull final Set resolvedReferencesDrafts, + @Nonnull final Map keyToIdCache) { + fetchedCategories.forEach( + fetchedCategory -> { + final String fetchedCategoryKey = fetchedCategory.getKey(); + final Optional draftByKeyIfExists = + getDraftByKeyIfExists(resolvedReferencesDrafts, fetchedCategoryKey); + final CategoryDraftBuilder categoryDraftBuilder = + draftByKeyIfExists + .map( + categoryDraft -> { + if (categoryDraft.getParent() == null) { + return CategoryDraftBuilder.of(categoryDraft) + .parent(toResourceIdentifierIfNotNull(fetchedCategory.getParent())); + } + return CategoryDraftBuilder.of(categoryDraft); + }) + .orElseGet(() -> CategoryDraftBuilder.of(fetchedCategory)); + if (categoryKeysWithResolvedParents.contains(fetchedCategoryKey)) { + statistics + .getMissingParentKey(fetchedCategoryKey) + .ifPresent( + missingParentKey -> { + final String parentId = keyToIdCache.get(missingParentKey); + categoryDraftBuilder.parent( + Category.referenceOfId(parentId).toResourceIdentifier()); + }); + } + categoryDraftsToUpdate.put(categoryDraftBuilder.build(), fetchedCategory); }); - } - - - /** - * Given a {@link Set} of categories and a {@code key}, this method tries to find a category with this key in this - * set and returns an optional containing it or an empty optional if no category exists with such key. - * - * @param categories set of categories to look for a category with such key. - * @param key the key to look for a category for in the supplied set of categories. - * @return an optional containing the category or an empty optional if no category exists with such key. - */ - private static Optional getCategoryByKeyIfExists(@Nonnull final Set categories, - @Nonnull final String key) { - return categories.stream() - .filter(category -> Objects.equals(category.getKey(), key)) - .findFirst(); - } - - /** - * Given a {@link Set} of categoryDrafts and a {@code key}. This method tries to find a categoryDraft with this key - * in this set and returns an optional containing it or an empty optional if no categoryDraft exists with such key. - * - * @param categoryDrafts set of categoryDrafts to look for a categoryDraft with such key. - * @param key the key to look for a categoryDraft for in the supplied set of categoryDrafts. - * @return an optional containing the categoryDraft or an empty optional if no category exists with such key. - */ - private static Optional getDraftByKeyIfExists(@Nonnull final Set categoryDrafts, - @Nonnull final String key) { - return categoryDrafts.stream() - .filter(categoryDraft -> Objects.equals(categoryDraft.getKey(), key)) - .findFirst(); - } - - /** - * Given a {@link Map} of categoryDrafts to Categories that require syncing, this method filters out the pairs that - * need a {@link io.sphere.sdk.categories.commands.updateactions.ChangeParent} update action, and in turn performs - * the sync on them in a sequential/blocking fashion as advised by the CTP documentation: - * http://dev.commercetools.com/http-api-projects-categories.html#change-parent - * - * @param matchingCategories a {@link Map} of categoryDrafts to Categories that require syncing. - */ - private void updateCategoriesSequentially(@Nonnull final Map matchingCategories) { + } + + /** + * Given a {@link Set} of categories and a {@code key}, this method tries to find a category with + * this key in this set and returns an optional containing it or an empty optional if no category + * exists with such key. + * + * @param categories set of categories to look for a category with such key. + * @param key the key to look for a category for in the supplied set of categories. + * @return an optional containing the category or an empty optional if no category exists with + * such key. + */ + private static Optional getCategoryByKeyIfExists( + @Nonnull final Set categories, @Nonnull final String key) { + return categories.stream() + .filter(category -> Objects.equals(category.getKey(), key)) + .findFirst(); + } + + /** + * Given a {@link Set} of categoryDrafts and a {@code key}. This method tries to find a + * categoryDraft with this key in this set and returns an optional containing it or an empty + * optional if no categoryDraft exists with such key. + * + * @param categoryDrafts set of categoryDrafts to look for a categoryDraft with such key. + * @param key the key to look for a categoryDraft for in the supplied set of categoryDrafts. + * @return an optional containing the categoryDraft or an empty optional if no category exists + * with such key. + */ + private static Optional getDraftByKeyIfExists( + @Nonnull final Set categoryDrafts, @Nonnull final String key) { + return categoryDrafts.stream() + .filter(categoryDraft -> Objects.equals(categoryDraft.getKey(), key)) + .findFirst(); + } + + /** + * Given a {@link Map} of categoryDrafts to Categories that require syncing, this method filters + * out the pairs that need a {@link io.sphere.sdk.categories.commands.updateactions.ChangeParent} + * update action, and in turn performs the sync on them in a sequential/blocking fashion as + * advised by the CTP documentation: + * http://dev.commercetools.com/http-api-projects-categories.html#change-parent + * + * @param matchingCategories a {@link Map} of categoryDrafts to Categories that require syncing. + */ + private void updateCategoriesSequentially( + @Nonnull final Map matchingCategories) { + matchingCategories.entrySet().stream() + .filter(entry -> requiresChangeParentUpdateAction(entry.getValue(), entry.getKey())) + .map(entry -> buildUpdateActionsAndUpdate(entry.getValue(), entry.getKey())) + .map(CompletionStage::toCompletableFuture) + .forEach(CompletableFuture::join); + } + + /** + * Compares the parent references of a {@link Category} and a {@link CategoryDraft} to check + * whether a {@link io.sphere.sdk.categories.commands.updateactions.ChangeParent} update action is + * required to sync the draft to the category or not + * + * @param category the old category to sync to. + * @param categoryDraft the new category draft to sync. + * @return true or false whether a {@link + * io.sphere.sdk.categories.commands.updateactions.ChangeParent} is needed to sync the draft + * to the category. + */ + static boolean requiresChangeParentUpdateAction( + @Nonnull final Category category, @Nonnull final CategoryDraft categoryDraft) { + return !areResourceIdentifiersEqual(category.getParent(), categoryDraft.getParent()); + } + + /** + * Given a {@link Map} of categoryDrafts to Categories that require syncing, this method filters + * out the pairs that don't need a {@link + * io.sphere.sdk.categories.commands.updateactions.ChangeParent} update action, and in turn + * performs the sync on them in a parallel/non-blocking fashion. + * + * @param matchingCategories a {@link Map} of categoryDrafts to Categories that require syncing. + */ + private CompletionStage updateCategoriesInParallel( + @Nonnull final Map matchingCategories) { + + final List> futures = matchingCategories.entrySet().stream() - .filter(entry -> requiresChangeParentUpdateAction(entry.getValue(), entry.getKey())) - .map(entry -> buildUpdateActionsAndUpdate(entry.getValue(), entry.getKey())) - .map(CompletionStage::toCompletableFuture) - .forEach(CompletableFuture::join); - } - - /** - * Compares the parent references of a {@link Category} and a {@link CategoryDraft} to check whether a - * {@link io.sphere.sdk.categories.commands.updateactions.ChangeParent} update action is required to sync the - * draft to the category or not - * - * @param category the old category to sync to. - * @param categoryDraft the new category draft to sync. - * @return true or false whether a {@link io.sphere.sdk.categories.commands.updateactions.ChangeParent} is needed to - * sync the draft to the category. - */ - static boolean requiresChangeParentUpdateAction(@Nonnull final Category category, - @Nonnull final CategoryDraft categoryDraft) { - return !areResourceIdentifiersEqual(category.getParent(), categoryDraft.getParent()); - } - - /** - * Given a {@link Map} of categoryDrafts to Categories that require syncing, this method filters out the pairs that - * don't need a {@link io.sphere.sdk.categories.commands.updateactions.ChangeParent} update action, and in turn - * performs the sync on them in a parallel/non-blocking fashion. - * - * @param matchingCategories a {@link Map} of categoryDrafts to Categories that require syncing. - */ - private CompletionStage updateCategoriesInParallel( - @Nonnull final Map matchingCategories) { - - final List> futures = - matchingCategories.entrySet().stream() - .filter(entry -> !requiresChangeParentUpdateAction(entry.getValue(), entry.getKey())) - .map(entry -> buildUpdateActionsAndUpdate(entry.getValue(), entry.getKey())) - .map(CompletionStage::toCompletableFuture) - .collect(Collectors.toList()); - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])); - } - - /** - * Given an existing {@link Category} and a new {@link CategoryDraft}, first resolves all references on the category - * draft, then it calculates all the update actions required to synchronize the existing category to be the same as - * the new one. Then the method applies the {@code ProductSyncOptions#beforeUpdateCallback} on the resultant list - * of actions. - * - *

If there are update actions in the resulting list, a request is made to CTP to update the existing category, - * otherwise it doesn't issue a request. - * - * @param oldCategory the category which could be updated. - * @param newCategory the category draft where we get the new data. - * @return a future which contains an empty result after execution of the update. - */ - private CompletionStage buildUpdateActionsAndUpdate(@Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory) { - - final List> updateActions = buildActions(oldCategory, newCategory, syncOptions); - final List> beforeUpdateCallBackApplied = - syncOptions.applyBeforeUpdateCallback(updateActions, newCategory, oldCategory); - - if (!beforeUpdateCallBackApplied.isEmpty()) { - return updateCategory(oldCategory, newCategory, beforeUpdateCallBackApplied); - } - - return CompletableFuture.completedFuture(null); - } - - - /** - * Given a {@link Category} and a {@link List} of {@link UpdateAction} elements, this method issues a request to - * the CTP project defined by the client configuration stored in the {@code syncOptions} instance - * of this class to update the specified category with this list of update actions. If the update request failed - * due to a {@link ConcurrentModificationException}, the method recalculates the update actions required for - * syncing the {@link Category} and reissues the update request. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP, - * the optional error callback specified in the {@code syncOptions} is called. - * - * @param oldCategory the category to update. - * @param newCategory the category draft where we get the new data. - * @param updateActions the list of update actions to update the category with. - * @return a future which contains an empty result after execution of the update. - */ - private CompletionStage updateCategory(@Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory, - @Nonnull final List> updateActions) { - final String categoryKey = oldCategory.getKey(); - return categoryService.updateCategory(oldCategory, updateActions) - .handle((updatedCategory, sphereException) -> sphereException) - .thenCompose(sphereException -> { - if (sphereException != null) { - return executeSupplierIfConcurrentModificationException( - sphereException, - () -> refetchAndUpdate(oldCategory, newCategory), - () -> { - if (!processedCategoryKeys.contains(categoryKey)) { - handleError(format(UPDATE_FAILED, categoryKey, sphereException), - sphereException, oldCategory, newCategory, updateActions); - processedCategoryKeys.add(categoryKey); - } - return CompletableFuture.completedFuture(null); - }); - } else { - if (!processedCategoryKeys.contains(categoryKey)) { - statistics.incrementUpdated(); - processedCategoryKeys.add(categoryKey); - } - if (categoryKeysWithResolvedParents.contains(categoryKey)) { - statistics.removeChildCategoryKeyFromMissingParentsMap(categoryKey); - } - return CompletableFuture.completedFuture(null); - } - }); + .filter(entry -> !requiresChangeParentUpdateAction(entry.getValue(), entry.getKey())) + .map(entry -> buildUpdateActionsAndUpdate(entry.getValue(), entry.getKey())) + .map(CompletionStage::toCompletableFuture) + .collect(Collectors.toList()); + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])); + } + + /** + * Given an existing {@link Category} and a new {@link CategoryDraft}, first resolves all + * references on the category draft, then it calculates all the update actions required to + * synchronize the existing category to be the same as the new one. Then the method applies the + * {@code ProductSyncOptions#beforeUpdateCallback} on the resultant list of actions. + * + *

If there are update actions in the resulting list, a request is made to CTP to update the + * existing category, otherwise it doesn't issue a request. + * + * @param oldCategory the category which could be updated. + * @param newCategory the category draft where we get the new data. + * @return a future which contains an empty result after execution of the update. + */ + private CompletionStage buildUpdateActionsAndUpdate( + @Nonnull final Category oldCategory, @Nonnull final CategoryDraft newCategory) { + + final List> updateActions = + buildActions(oldCategory, newCategory, syncOptions); + final List> beforeUpdateCallBackApplied = + syncOptions.applyBeforeUpdateCallback(updateActions, newCategory, oldCategory); + + if (!beforeUpdateCallBackApplied.isEmpty()) { + return updateCategory(oldCategory, newCategory, beforeUpdateCallBackApplied); } - private CompletionStage refetchAndUpdate(@Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory) { - - final String key = oldCategory.getKey(); - return categoryService - .fetchCategory(key) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Optional fetchedCategoryOptional = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(UPDATE_FAILED, key, "Failed to fetch from CTP while " - + "retrying after concurrency modification."); - handleError(errorMessage, exception, oldCategory, newCategory, null); - return CompletableFuture.completedFuture(null); - } - - return fetchedCategoryOptional - .map(fetchedCategory -> buildUpdateActionsAndUpdate(fetchedCategory, newCategory)) - .orElseGet(() -> { - final String errorMessage = - format(UPDATE_FAILED, key, "Not found when attempting to fetch while retrying " + return CompletableFuture.completedFuture(null); + } + + /** + * Given a {@link Category} and a {@link List} of {@link UpdateAction} elements, this method + * issues a request to the CTP project defined by the client configuration stored in the {@code + * syncOptions} instance of this class to update the specified category with this list of update + * actions. If the update request failed due to a {@link ConcurrentModificationException}, the + * method recalculates the update actions required for syncing the {@link Category} and reissues + * the update request. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP, + * the optional error callback specified in the {@code syncOptions} is called. + * + * @param oldCategory the category to update. + * @param newCategory the category draft where we get the new data. + * @param updateActions the list of update actions to update the category with. + * @return a future which contains an empty result after execution of the update. + */ + private CompletionStage updateCategory( + @Nonnull final Category oldCategory, + @Nonnull final CategoryDraft newCategory, + @Nonnull final List> updateActions) { + final String categoryKey = oldCategory.getKey(); + return categoryService + .updateCategory(oldCategory, updateActions) + .handle((updatedCategory, sphereException) -> sphereException) + .thenCompose( + sphereException -> { + if (sphereException != null) { + return executeSupplierIfConcurrentModificationException( + sphereException, + () -> refetchAndUpdate(oldCategory, newCategory), + () -> { + if (!processedCategoryKeys.contains(categoryKey)) { + handleError( + format(UPDATE_FAILED, categoryKey, sphereException), + sphereException, + oldCategory, + newCategory, + updateActions); + processedCategoryKeys.add(categoryKey); + } + return CompletableFuture.completedFuture(null); + }); + } else { + if (!processedCategoryKeys.contains(categoryKey)) { + statistics.incrementUpdated(); + processedCategoryKeys.add(categoryKey); + } + if (categoryKeysWithResolvedParents.contains(categoryKey)) { + statistics.removeChildCategoryKeyFromMissingParentsMap(categoryKey); + } + return CompletableFuture.completedFuture(null); + } + }); + } + + private CompletionStage refetchAndUpdate( + @Nonnull final Category oldCategory, @Nonnull final CategoryDraft newCategory) { + + final String key = oldCategory.getKey(); + return categoryService + .fetchCategory(key) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Optional fetchedCategoryOptional = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + UPDATE_FAILED, + key, + "Failed to fetch from CTP while retrying after concurrency modification."); + handleError(errorMessage, exception, oldCategory, newCategory, null); + return CompletableFuture.completedFuture(null); + } + + return fetchedCategoryOptional + .map(fetchedCategory -> buildUpdateActionsAndUpdate(fetchedCategory, newCategory)) + .orElseGet( + () -> { + final String errorMessage = + format( + UPDATE_FAILED, + key, + "Not found when attempting to fetch while retrying " + "after concurrency modification."); - handleError(errorMessage, null, oldCategory, newCategory, null); - return CompletableFuture.completedFuture(null); - }); - }); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed categories to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - * @param exception The exception that called caused the failure, if any. - */ - private void handleError(@Nonnull final String errorMessage, @Nullable final Throwable exception) { - handleError(errorMessage, exception, null, null, null); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed categories to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - * @param exception The exception that called caused the failure, if any. - * @param oldCategory the category to update. - * @param newCategory the category draft where we get the new data. - * @param updateActions the list of update actions to update the category with. - */ - private void handleError(@Nonnull final String errorMessage, @Nullable final Throwable exception, - @Nullable final Category oldCategory, - @Nullable final CategoryDraft newCategory, - @Nullable final List> updateActions) { - SyncException syncException = exception != null ? new SyncException(errorMessage, exception) + handleError(errorMessage, null, oldCategory, newCategory, null); + return CompletableFuture.completedFuture(null); + }); + }); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed categories to sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + * @param exception The exception that called caused the failure, if any. + */ + private void handleError( + @Nonnull final String errorMessage, @Nullable final Throwable exception) { + handleError(errorMessage, exception, null, null, null); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed categories to sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + * @param exception The exception that called caused the failure, if any. + * @param oldCategory the category to update. + * @param newCategory the category draft where we get the new data. + * @param updateActions the list of update actions to update the category with. + */ + private void handleError( + @Nonnull final String errorMessage, + @Nullable final Throwable exception, + @Nullable final Category oldCategory, + @Nullable final CategoryDraft newCategory, + @Nullable final List> updateActions) { + SyncException syncException = + exception != null + ? new SyncException(errorMessage, exception) : new SyncException(errorMessage); - syncOptions.applyErrorCallback(syncException, oldCategory, newCategory, updateActions); - statistics.incrementFailed(); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed category to sync with the supplied {@code failedTimes}. - * - * @param syncException The exception that caused the failure. - * @param failedTimes The number of times that the failed category counter is incremented. - */ - private void handleError(@Nonnull final SyncException syncException, - final int failedTimes) { - syncOptions.applyErrorCallback(syncException); - statistics.incrementFailed(failedTimes); - } + syncOptions.applyErrorCallback(syncException, oldCategory, newCategory, updateActions); + statistics.incrementFailed(); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed category to sync with + * the supplied {@code failedTimes}. + * + * @param syncException The exception that caused the failure. + * @param failedTimes The number of times that the failed category counter is incremented. + */ + private void handleError(@Nonnull final SyncException syncException, final int failedTimes) { + syncOptions.applyErrorCallback(syncException); + statistics.incrementFailed(failedTimes); + } } diff --git a/src/main/java/com/commercetools/sync/categories/CategorySyncOptions.java b/src/main/java/com/commercetools/sync/categories/CategorySyncOptions.java index 87fee0d784..b599bffbae 100644 --- a/src/main/java/com/commercetools/sync/categories/CategorySyncOptions.java +++ b/src/main/java/com/commercetools/sync/categories/CategorySyncOptions.java @@ -9,32 +9,43 @@ import io.sphere.sdk.categories.CategoryDraft; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class CategorySyncOptions extends BaseSyncOptions { - CategorySyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallback, - @Nullable final TriConsumer, Optional> - warningCallback, - final int batchSize, - @Nullable final TriFunction>, CategoryDraft, Category, - List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, - final long cacheSize) { - super(ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize); - } + CategorySyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallback, + @Nullable + final TriConsumer, Optional> + warningCallback, + final int batchSize, + @Nullable + final TriFunction< + List>, + CategoryDraft, + Category, + List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { + super( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } } diff --git a/src/main/java/com/commercetools/sync/categories/CategorySyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/categories/CategorySyncOptionsBuilder.java index c55369d4f7..3f9d748241 100644 --- a/src/main/java/com/commercetools/sync/categories/CategorySyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/categories/CategorySyncOptionsBuilder.java @@ -1,59 +1,59 @@ package com.commercetools.sync.categories; - import com.commercetools.sync.commons.BaseSyncOptionsBuilder; import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.CategoryDraft; import io.sphere.sdk.client.SphereClient; - import javax.annotation.Nonnull; -public final class CategorySyncOptionsBuilder extends BaseSyncOptionsBuilder { - public static final int BATCH_SIZE_DEFAULT = 50; - - private CategorySyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } - - /** - * Creates a new instance of {@link CategorySyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 50). - * - * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link CategorySyncOptionsBuilder} - */ - public static CategorySyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new CategorySyncOptionsBuilder(ctpClient) - .batchSize(BATCH_SIZE_DEFAULT); - } - - /** - * Creates a new instance of {@link CategorySyncOptions} enriched with all attributes provided to {@code this} - * builder. - * - * @return new instance of {@link CategorySyncOptions} - */ - @Override - public CategorySyncOptions build() { - return new CategorySyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize); - } - - /** - * Returns an instance of this class to be used in the superclass's generic methods. Please see the JavaDoc in the - * overridden method for further details. - * - * @return an instance of this class. - */ - @Override - protected CategorySyncOptionsBuilder getThis() { - return this; - } +public final class CategorySyncOptionsBuilder + extends BaseSyncOptionsBuilder< + CategorySyncOptionsBuilder, CategorySyncOptions, Category, CategoryDraft> { + public static final int BATCH_SIZE_DEFAULT = 50; + + private CategorySyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } + + /** + * Creates a new instance of {@link CategorySyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 50). + * + * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the + * target CTP project. + * @return new instance of {@link CategorySyncOptionsBuilder} + */ + public static CategorySyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new CategorySyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } + + /** + * Creates a new instance of {@link CategorySyncOptions} enriched with all attributes provided to + * {@code this} builder. + * + * @return new instance of {@link CategorySyncOptions} + */ + @Override + public CategorySyncOptions build() { + return new CategorySyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } + + /** + * Returns an instance of this class to be used in the superclass's generic methods. Please see + * the JavaDoc in the overridden method for further details. + * + * @return an instance of this class. + */ + @Override + protected CategorySyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/categories/helpers/AssetCustomActionBuilder.java b/src/main/java/com/commercetools/sync/categories/helpers/AssetCustomActionBuilder.java index dca72c8bbb..9a09943c65 100644 --- a/src/main/java/com/commercetools/sync/categories/helpers/AssetCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/categories/helpers/AssetCustomActionBuilder.java @@ -1,5 +1,6 @@ package com.commercetools.sync.categories.helpers; +import static io.sphere.sdk.types.CustomFieldsDraft.ofTypeIdAndJson; import com.commercetools.sync.commons.helpers.GenericCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -7,37 +8,36 @@ import io.sphere.sdk.categories.commands.updateactions.SetAssetCustomField; import io.sphere.sdk.categories.commands.updateactions.SetAssetCustomType; import io.sphere.sdk.commands.UpdateAction; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; - -import static io.sphere.sdk.types.CustomFieldsDraft.ofTypeIdAndJson; public class AssetCustomActionBuilder implements GenericCustomActionBuilder { - @Override - @Nonnull - public UpdateAction buildRemoveCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String assetKey) { - return SetAssetCustomType.ofKey(assetKey, null); - } - - @Override - @Nonnull - public UpdateAction buildSetCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String assetKey, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - return SetAssetCustomType.ofKey(assetKey, ofTypeIdAndJson(customTypeId, customFieldsJsonMap)); - } - - @Override - @Nonnull - public UpdateAction buildSetCustomFieldAction(@Nullable final Integer variantId, - @Nullable final String assetKey, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - return SetAssetCustomField.ofJsonValueWithKey(assetKey, customFieldName, customFieldValue); - } + @Override + @Nonnull + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String assetKey) { + return SetAssetCustomType.ofKey(assetKey, null); + } + + @Override + @Nonnull + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String assetKey, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + return SetAssetCustomType.ofKey(assetKey, ofTypeIdAndJson(customTypeId, customFieldsJsonMap)); + } + + @Override + @Nonnull + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String assetKey, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + return SetAssetCustomField.ofJsonValueWithKey(assetKey, customFieldName, customFieldValue); + } } diff --git a/src/main/java/com/commercetools/sync/categories/helpers/CategoryAssetActionFactory.java b/src/main/java/com/commercetools/sync/categories/helpers/CategoryAssetActionFactory.java index f3a4f67485..18e0a5fc29 100644 --- a/src/main/java/com/commercetools/sync/categories/helpers/CategoryAssetActionFactory.java +++ b/src/main/java/com/commercetools/sync/categories/helpers/CategoryAssetActionFactory.java @@ -1,5 +1,7 @@ package com.commercetools.sync.categories.helpers; +import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildActions; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.commons.helpers.AssetActionFactory; import io.sphere.sdk.categories.Category; @@ -10,41 +12,39 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.Asset; import io.sphere.sdk.models.AssetDraft; - -import javax.annotation.Nonnull; import java.util.List; - -import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildActions; +import javax.annotation.Nonnull; public final class CategoryAssetActionFactory extends AssetActionFactory { - public CategoryAssetActionFactory(@Nonnull final CategorySyncOptions syncOptions) { - this.syncOptions = syncOptions; - } - - - - @Override - public List> buildAssetActions(@Nonnull final Category oldResource, - @Nonnull final CategoryDraft newResource, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAssetDraft) { - return buildActions(oldResource, newResource, oldAsset, newAssetDraft, (CategorySyncOptions) syncOptions); - } - - @Override - public UpdateAction buildRemoveAssetAction(@Nonnull final String assetKey) { - return RemoveAsset.ofKey(assetKey); - } - - @Override - public UpdateAction buildChangeAssetOrderAction(@Nonnull final List newAssetOrder) { - return ChangeAssetOrder.of(newAssetOrder); - } - - @Override - public UpdateAction buildAddAssetAction(@Nonnull final AssetDraft assetDraft, - @Nonnull final Integer position) { - return AddAsset.of(assetDraft, position); - } + public CategoryAssetActionFactory(@Nonnull final CategorySyncOptions syncOptions) { + this.syncOptions = syncOptions; + } + + @Override + public List> buildAssetActions( + @Nonnull final Category oldResource, + @Nonnull final CategoryDraft newResource, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAssetDraft) { + return buildActions( + oldResource, newResource, oldAsset, newAssetDraft, (CategorySyncOptions) syncOptions); + } + + @Override + public UpdateAction buildRemoveAssetAction(@Nonnull final String assetKey) { + return RemoveAsset.ofKey(assetKey); + } + + @Override + public UpdateAction buildChangeAssetOrderAction( + @Nonnull final List newAssetOrder) { + return ChangeAssetOrder.of(newAssetOrder); + } + + @Override + public UpdateAction buildAddAssetAction( + @Nonnull final AssetDraft assetDraft, @Nonnull final Integer position) { + return AddAsset.of(assetDraft, position); + } } diff --git a/src/main/java/com/commercetools/sync/categories/helpers/CategoryBatchValidator.java b/src/main/java/com/commercetools/sync/categories/helpers/CategoryBatchValidator.java index 0297f315c8..511c843332 100644 --- a/src/main/java/com/commercetools/sync/categories/helpers/CategoryBatchValidator.java +++ b/src/main/java/com/commercetools/sync/categories/helpers/CategoryBatchValidator.java @@ -1,98 +1,98 @@ package com.commercetools.sync.categories.helpers; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.commons.helpers.BaseBatchValidator; import io.sphere.sdk.categories.CategoryDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; - -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class CategoryBatchValidator extends BaseBatchValidator { - static final String CATEGORY_DRAFT_KEY_NOT_SET = "CategoryDraft with name: %s doesn't have a key. " - + "Please make sure all category drafts have keys."; - static final String CATEGORY_DRAFT_IS_NULL = "CategoryDraft is null."; - - public CategoryBatchValidator( - @Nonnull final CategorySyncOptions syncOptions, - @Nonnull final CategorySyncStatistics syncStatistics) { - - super(syncOptions, syncStatistics); - } - - /** - * Given the {@link List}<{@link CategoryDraft}> of drafts this method attempts to validate - * drafts and collect referenced keys from the draft - * and return an {@link ImmutablePair}<{@link Set}<{@link CategoryDraft}>,{@link ReferencedKeys}> - * which contains the {@link Set} of valid drafts and referenced keys within a wrapper. - * - *

A valid category draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a key which is not blank (null/empty)
  4. - *
- * - * @param categoryDrafts the category drafts to validate and collect referenced keys. - * @return {@link ImmutablePair}<{@link Set}<{@link CategoryDraft}>,{@link ReferencedKeys}> - * which contains the {@link Set} of valid drafts and referenced keys within a wrapper. - */ - @Override - public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( - @Nonnull final List categoryDrafts) { - final ReferencedKeys referencedKeys = new ReferencedKeys(); - final Set validDrafts = categoryDrafts - .stream() + static final String CATEGORY_DRAFT_KEY_NOT_SET = + "CategoryDraft with name: %s doesn't have a key. " + + "Please make sure all category drafts have keys."; + static final String CATEGORY_DRAFT_IS_NULL = "CategoryDraft is null."; + + public CategoryBatchValidator( + @Nonnull final CategorySyncOptions syncOptions, + @Nonnull final CategorySyncStatistics syncStatistics) { + + super(syncOptions, syncStatistics); + } + + /** + * Given the {@link List}<{@link CategoryDraft}> of drafts this method attempts to validate + * drafts and collect referenced keys from the draft and return an {@link ImmutablePair}<{@link + * Set}<{@link CategoryDraft}>,{@link ReferencedKeys}> which contains the {@link Set} of + * valid drafts and referenced keys within a wrapper. + * + *

A valid category draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a key which is not blank (null/empty) + *
+ * + * @param categoryDrafts the category drafts to validate and collect referenced keys. + * @return {@link ImmutablePair}<{@link Set}<{@link CategoryDraft}>,{@link + * ReferencedKeys}> which contains the {@link Set} of valid drafts and referenced keys + * within a wrapper. + */ + @Override + public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( + @Nonnull final List categoryDrafts) { + final ReferencedKeys referencedKeys = new ReferencedKeys(); + final Set validDrafts = + categoryDrafts.stream() .filter(this::isValidCategoryDraft) .peek(categoryDraft -> collectReferencedKeys(referencedKeys, categoryDraft)) .collect(Collectors.toSet()); - return ImmutablePair.of(validDrafts, referencedKeys); - } - - private boolean isValidCategoryDraft(@Nullable final CategoryDraft categoryDraft) { - if (categoryDraft == null) { - handleError(CATEGORY_DRAFT_IS_NULL); - } else if (isBlank(categoryDraft.getKey())) { - handleError(format(CATEGORY_DRAFT_KEY_NOT_SET, categoryDraft.getName())); - } else { - return true; - } + return ImmutablePair.of(validDrafts, referencedKeys); + } - return false; + private boolean isValidCategoryDraft(@Nullable final CategoryDraft categoryDraft) { + if (categoryDraft == null) { + handleError(CATEGORY_DRAFT_IS_NULL); + } else if (isBlank(categoryDraft.getKey())) { + handleError(format(CATEGORY_DRAFT_KEY_NOT_SET, categoryDraft.getName())); + } else { + return true; } - private void collectReferencedKeys( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final CategoryDraft categoryDraft) { + return false; + } - referencedKeys.categoryKeys.add(categoryDraft.getKey()); - collectReferencedKeyFromResourceIdentifier(categoryDraft.getParent(), - referencedKeys.categoryKeys::add); - collectReferencedKeyFromCustomFieldsDraft(categoryDraft.getCustom(), - referencedKeys.typeKeys::add); - collectReferencedKeysFromAssetDrafts(categoryDraft.getAssets(), - referencedKeys.typeKeys::add); - } + private void collectReferencedKeys( + @Nonnull final ReferencedKeys referencedKeys, @Nonnull final CategoryDraft categoryDraft) { - public static class ReferencedKeys { - private final Set categoryKeys = new HashSet<>(); - private final Set typeKeys = new HashSet<>(); + referencedKeys.categoryKeys.add(categoryDraft.getKey()); + collectReferencedKeyFromResourceIdentifier( + categoryDraft.getParent(), referencedKeys.categoryKeys::add); + collectReferencedKeyFromCustomFieldsDraft( + categoryDraft.getCustom(), referencedKeys.typeKeys::add); + collectReferencedKeysFromAssetDrafts(categoryDraft.getAssets(), referencedKeys.typeKeys::add); + } - public Set getCategoryKeys() { - return categoryKeys; - } + public static class ReferencedKeys { + private final Set categoryKeys = new HashSet<>(); + private final Set typeKeys = new HashSet<>(); + + public Set getCategoryKeys() { + return categoryKeys; + } - public Set getTypeKeys() { - return typeKeys; - } + public Set getTypeKeys() { + return typeKeys; } + } } diff --git a/src/main/java/com/commercetools/sync/categories/helpers/CategoryCustomActionBuilder.java b/src/main/java/com/commercetools/sync/categories/helpers/CategoryCustomActionBuilder.java index 1c657530e7..7402fd0dc9 100644 --- a/src/main/java/com/commercetools/sync/categories/helpers/CategoryCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/categories/helpers/CategoryCustomActionBuilder.java @@ -1,41 +1,40 @@ package com.commercetools.sync.categories.helpers; - import com.commercetools.sync.commons.helpers.GenericCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.commands.updateactions.SetCustomField; import io.sphere.sdk.categories.commands.updateactions.SetCustomType; import io.sphere.sdk.commands.UpdateAction; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; public class CategoryCustomActionBuilder implements GenericCustomActionBuilder { - @Nonnull - @Override - public UpdateAction buildRemoveCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String objectId) { - return SetCustomType.ofRemoveType(); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String objectId, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); + @Nonnull + @Override + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String objectId) { + return SetCustomType.ofRemoveType(); + } - } + @Nonnull + @Override + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); + } - @Nonnull - @Override - public UpdateAction buildSetCustomFieldAction(@Nullable final Integer variantId, - @Nullable final String objectId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - return SetCustomField.ofJson(customFieldName, customFieldValue); - } + @Nonnull + @Override + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + return SetCustomField.ofJson(customFieldName, customFieldValue); + } } diff --git a/src/main/java/com/commercetools/sync/categories/helpers/CategoryReferenceResolver.java b/src/main/java/com/commercetools/sync/categories/helpers/CategoryReferenceResolver.java index d0924073a1..0103c10488 100644 --- a/src/main/java/com/commercetools/sync/categories/helpers/CategoryReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/categories/helpers/CategoryReferenceResolver.java @@ -1,5 +1,11 @@ package com.commercetools.sync.categories.helpers; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; @@ -12,9 +18,6 @@ import io.sphere.sdk.categories.CategoryDraftBuilder; import io.sphere.sdk.models.AssetDraft; import io.sphere.sdk.models.ResourceIdentifier; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -22,174 +25,192 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class CategoryReferenceResolver - extends CustomReferenceResolver { - private final AssetReferenceResolver assetReferenceResolver; - private final CategoryService categoryService; - private final TypeService typeService; - - static final String FAILED_TO_RESOLVE_PARENT = "Failed to resolve parent reference on " - + "CategoryDraft with key:'%s'. Reason: %s"; - static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type reference on " - + "CategoryDraft with key:'%s'."; - static final String PARENT_CATEGORY_DOES_NOT_EXIST = "Parent category with key '%s' doesn't exist."; - - /** - * Takes a {@link CategorySyncOptions} instance, a {@link CategoryService} and {@link TypeService} to instantiate a - * {@link CategoryReferenceResolver} instance that could be used to resolve the category drafts in the - * CTP project specified in the injected {@link CategorySyncOptions} instance. - * - * @param options the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - * @param typeService the service to fetch the custom types for reference resolution. - * @param categoryService the service to fetch the categories for reference resolution. - */ - public CategoryReferenceResolver(@Nonnull final CategorySyncOptions options, - @Nonnull final TypeService typeService, - @Nonnull final CategoryService categoryService) { - super(options, typeService); - this.assetReferenceResolver = new AssetReferenceResolver(options, typeService); - this.categoryService = categoryService; - this.typeService = typeService; + extends CustomReferenceResolver { + private final AssetReferenceResolver assetReferenceResolver; + private final CategoryService categoryService; + private final TypeService typeService; + + static final String FAILED_TO_RESOLVE_PARENT = + "Failed to resolve parent reference on CategoryDraft with key:'%s'. Reason: %s"; + static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = + "Failed to resolve custom type reference on CategoryDraft with key:'%s'."; + static final String PARENT_CATEGORY_DOES_NOT_EXIST = + "Parent category with key '%s' doesn't exist."; + + /** + * Takes a {@link CategorySyncOptions} instance, a {@link CategoryService} and {@link TypeService} + * to instantiate a {@link CategoryReferenceResolver} instance that could be used to resolve the + * category drafts in the CTP project specified in the injected {@link CategorySyncOptions} + * instance. + * + * @param options the container of all the options of the sync process including the CTP project + * client and/or configuration and other sync-specific options. + * @param typeService the service to fetch the custom types for reference resolution. + * @param categoryService the service to fetch the categories for reference resolution. + */ + public CategoryReferenceResolver( + @Nonnull final CategorySyncOptions options, + @Nonnull final TypeService typeService, + @Nonnull final CategoryService categoryService) { + super(options, typeService); + this.assetReferenceResolver = new AssetReferenceResolver(options, typeService); + this.categoryService = categoryService; + this.typeService = typeService; + } + + /** + * Given a {@link CategoryDraft} this method attempts to resolve the custom type and parent + * category references to return a {@link CompletionStage} which contains a new instance of the + * draft with the resolved references. + * + * @param categoryDraft the categoryDraft to resolve its references. + * @return a {@link CompletionStage} that contains as a result a new categoryDraft instance with + * resolved category references or, in case an error occurs during reference resolution, a + * {@link ReferenceResolutionException}. + */ + @Override + @Nonnull + public CompletionStage resolveReferences( + @Nonnull final CategoryDraft categoryDraft) { + return resolveCustomTypeReference(CategoryDraftBuilder.of(categoryDraft)) + .thenCompose(this::resolveParentReference) + .thenCompose(this::resolveAssetsReferences) + .thenApply(CategoryDraftBuilder::build); + } + + @Nonnull + CompletionStage resolveAssetsReferences( + @Nonnull final CategoryDraftBuilder categoryDraftBuilder) { + + final List categoryDraftAssets = categoryDraftBuilder.getAssets(); + if (categoryDraftAssets == null) { + return completedFuture(categoryDraftBuilder); } - /** - * Given a {@link CategoryDraft} this method attempts to resolve the custom type and parent category references to - * return a {@link CompletionStage} which contains a new instance of the draft with the resolved - * references. - * - * @param categoryDraft the categoryDraft to resolve its references. - * @return a {@link CompletionStage} that contains as a result a new categoryDraft instance with resolved - * category references or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Override - @Nonnull - public CompletionStage resolveReferences(@Nonnull final CategoryDraft categoryDraft) { - return resolveCustomTypeReference(CategoryDraftBuilder.of(categoryDraft)) - .thenCompose(this::resolveParentReference) - .thenCompose(this::resolveAssetsReferences) - .thenApply(CategoryDraftBuilder::build); + return mapValuesToFutureOfCompletedValues( + categoryDraftAssets, assetReferenceResolver::resolveReferences, toList()) + .thenApply(categoryDraftBuilder::assets); + } + + @Override + @Nonnull + protected CompletionStage resolveCustomTypeReference( + @Nonnull final CategoryDraftBuilder draftBuilder) { + + return resolveCustomTypeReference( + draftBuilder, + CategoryDraftBuilder::getCustom, + CategoryDraftBuilder::custom, + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getKey())); + } + + @Nonnull + CompletionStage resolveParentReference( + @Nonnull final CategoryDraftBuilder draftBuilder) { + final ResourceIdentifier parent = draftBuilder.getParent(); + if (parent != null && parent.getId() == null) { + try { + return getParentCategoryKey(draftBuilder) + .map( + parentCategoryKey -> + fetchAndResolveParentReference(draftBuilder, parentCategoryKey)) + .orElseGet(() -> completedFuture(draftBuilder)); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture(referenceResolutionException); + } } - - @Nonnull - CompletionStage resolveAssetsReferences( - @Nonnull final CategoryDraftBuilder categoryDraftBuilder) { - - final List categoryDraftAssets = categoryDraftBuilder.getAssets(); - if (categoryDraftAssets == null) { - return completedFuture(categoryDraftBuilder); - } - - return mapValuesToFutureOfCompletedValues(categoryDraftAssets, - assetReferenceResolver::resolveReferences, toList()) - .thenApply(categoryDraftBuilder::assets); + return completedFuture(draftBuilder); + } + + @Nonnull + public static Optional getParentCategoryKey( + @Nonnull final CategoryDraftBuilder draftBuilder) throws ReferenceResolutionException { + return getParentCategoryKey(draftBuilder.getParent(), draftBuilder.getKey()); + } + + @Nonnull + public static Optional getParentCategoryKey(@Nonnull final CategoryDraft draft) + throws ReferenceResolutionException { + return getParentCategoryKey(draft.getParent(), draft.getKey()); + } + + @Nonnull + private static Optional getParentCategoryKey( + @Nullable final ResourceIdentifier parentCategoryResourceIdentifier, + @Nullable final String categoryKey) + throws ReferenceResolutionException { + + if (parentCategoryResourceIdentifier != null + && parentCategoryResourceIdentifier.getId() == null) { + try { + final String parentKey = getKeyFromResourceIdentifier(parentCategoryResourceIdentifier); + return Optional.of(parentKey); + } catch (ReferenceResolutionException referenceResolutionException) { + throw new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_PARENT, categoryKey, referenceResolutionException.getMessage()), + referenceResolutionException); + } } - - @Override - @Nonnull - protected CompletionStage resolveCustomTypeReference( - @Nonnull final CategoryDraftBuilder draftBuilder) { - - return resolveCustomTypeReference(draftBuilder, - CategoryDraftBuilder::getCustom, - CategoryDraftBuilder::custom, - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getKey())); - } - - @Nonnull - CompletionStage resolveParentReference(@Nonnull final CategoryDraftBuilder draftBuilder) { - final ResourceIdentifier parent = draftBuilder.getParent(); - if (parent != null && parent.getId() == null) { - try { - return getParentCategoryKey(draftBuilder) - .map(parentCategoryKey -> fetchAndResolveParentReference(draftBuilder, parentCategoryKey)) - .orElseGet(() -> completedFuture(draftBuilder)); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture(referenceResolutionException); - } - } - return completedFuture(draftBuilder); + return Optional.empty(); + } + + @Nonnull + private CompletionStage fetchAndResolveParentReference( + @Nonnull final CategoryDraftBuilder draftBuilder, @Nonnull final String parentCategoryKey) { + + return categoryService + .fetchCachedCategoryId(parentCategoryKey) + .thenCompose( + resolvedParentIdOptional -> + resolvedParentIdOptional + .map( + resolvedParentId -> + completedFuture( + draftBuilder.parent( + Category.referenceOfId(resolvedParentId) + .toResourceIdentifier()))) + .orElseGet( + () -> { + final String errorMessage = + format( + FAILED_TO_RESOLVE_PARENT, + draftBuilder.getKey(), + format(PARENT_CATEGORY_DOES_NOT_EXIST, parentCategoryKey)); + return exceptionallyCompletedFuture( + new ReferenceResolutionException(errorMessage)); + })); + } + + /** + * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys (category and + * type) from the commercetools to populate caches for the reference resolution. + * + *

Note: This method is meant be only used internally by the library to improve performance. + * + * @param referencedKeys a wrapper for the category references to fetch and cache the id's for. + * @return {@link CompletionStage}<{@link Map}<{@link String}>{@link String}>> in + * which the results of it's completions contains a map of requested references keys -> ids + * of parent category references. + */ + @Nonnull + public CompletableFuture> populateKeyToIdCachesForReferencedKeys( + @Nonnull final CategoryBatchValidator.ReferencedKeys referencedKeys) { + + final List>> futures = new ArrayList<>(); + + futures.add(categoryService.cacheKeysToIds(referencedKeys.getCategoryKeys())); + + final Set typeKeys = referencedKeys.getTypeKeys(); + if (!typeKeys.isEmpty()) { + futures.add(typeService.cacheKeysToIds(typeKeys)); } - @Nonnull - public static Optional getParentCategoryKey(@Nonnull final CategoryDraftBuilder draftBuilder) - throws ReferenceResolutionException { - return getParentCategoryKey(draftBuilder.getParent(), draftBuilder.getKey()); - } - - @Nonnull - public static Optional getParentCategoryKey(@Nonnull final CategoryDraft draft) - throws ReferenceResolutionException { - return getParentCategoryKey(draft.getParent(), draft.getKey()); - } - - @Nonnull - private static Optional getParentCategoryKey( - @Nullable final ResourceIdentifier parentCategoryResourceIdentifier, - @Nullable final String categoryKey) throws ReferenceResolutionException { - - if (parentCategoryResourceIdentifier != null && parentCategoryResourceIdentifier.getId() == null) { - try { - final String parentKey = getKeyFromResourceIdentifier(parentCategoryResourceIdentifier); - return Optional.of(parentKey); - } catch (ReferenceResolutionException referenceResolutionException) { - throw new ReferenceResolutionException(format(FAILED_TO_RESOLVE_PARENT, categoryKey, - referenceResolutionException.getMessage()), referenceResolutionException); - } - } - return Optional.empty(); - } - - @Nonnull - private CompletionStage fetchAndResolveParentReference( - @Nonnull final CategoryDraftBuilder draftBuilder, - @Nonnull final String parentCategoryKey) { - - return categoryService - .fetchCachedCategoryId(parentCategoryKey) - .thenCompose(resolvedParentIdOptional -> resolvedParentIdOptional - .map(resolvedParentId -> - completedFuture( - draftBuilder.parent(Category.referenceOfId(resolvedParentId).toResourceIdentifier()))) - .orElseGet(() -> { - final String errorMessage = format(FAILED_TO_RESOLVE_PARENT, draftBuilder.getKey(), - format(PARENT_CATEGORY_DOES_NOT_EXIST, parentCategoryKey)); - return exceptionallyCompletedFuture(new ReferenceResolutionException(errorMessage)); - })); - } - - /** - * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys (category and type) - * from the commercetools to populate caches for the reference resolution. - * - *

Note: This method is meant be only used internally by the library to improve performance. - * - * @param referencedKeys a wrapper for the category references to fetch and cache the id's for. - * @return {@link CompletionStage}<{@link Map}<{@link String}>{@link String}>> in which the results - * of it's completions contains a map of requested references keys -> ids of parent category references. - */ - @Nonnull - public CompletableFuture> populateKeyToIdCachesForReferencedKeys( - @Nonnull final CategoryBatchValidator.ReferencedKeys referencedKeys) { - - final List>> futures = new ArrayList<>(); - - futures.add(categoryService.cacheKeysToIds(referencedKeys.getCategoryKeys())); - - final Set typeKeys = referencedKeys.getTypeKeys(); - if (!typeKeys.isEmpty()) { - futures.add(typeService.cacheKeysToIds(typeKeys)); - } - - return collectionOfFuturesToFutureOfCollection(futures, toList()).thenApply(maps -> maps.get(0)); - } + return collectionOfFuturesToFutureOfCollection(futures, toList()) + .thenApply(maps -> maps.get(0)); + } } diff --git a/src/main/java/com/commercetools/sync/categories/helpers/CategorySyncStatistics.java b/src/main/java/com/commercetools/sync/categories/helpers/CategorySyncStatistics.java index 58fa863b38..c0fae5c6b8 100644 --- a/src/main/java/com/commercetools/sync/categories/helpers/CategorySyncStatistics.java +++ b/src/main/java/com/commercetools/sync/categories/helpers/CategorySyncStatistics.java @@ -1,11 +1,10 @@ package com.commercetools.sync.categories.helpers; +import static java.lang.String.format; import com.commercetools.sync.commons.helpers.BaseSyncStatistics; import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.CategoryDraft; - -import javax.annotation.Nonnull; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -13,117 +12,123 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; - -import static java.lang.String.format; +import javax.annotation.Nonnull; public class CategorySyncStatistics extends BaseSyncStatistics { - /** - * The following {@link Map} ({@code categoryKeysWithMissingParents}) represents categories with - * missing parents. - * - *

    - *
  • key: key of the missing parent category
  • - *
  • value: a set of the parent's children category keys
  • - *
- * - *

The map is thread-safe (by instantiating it with {@link ConcurrentHashMap}) because it is accessed/modified in - * a concurrent context, specifically when updating products in parallel in - * {@link com.commercetools.sync.categories.CategorySync#updateCategory(Category, CategoryDraft, List)}. - * - */ - private ConcurrentHashMap> categoryKeysWithMissingParents = new ConcurrentHashMap<>(); + /** + * The following {@link Map} ({@code categoryKeysWithMissingParents}) represents categories with + * missing parents. + * + *

    + *
  • key: key of the missing parent category + *
  • value: a set of the parent's children category keys + *
+ * + *

The map is thread-safe (by instantiating it with {@link ConcurrentHashMap}) because it is + * accessed/modified in a concurrent context, specifically when updating products in parallel in + * {@link com.commercetools.sync.categories.CategorySync#updateCategory(Category, CategoryDraft, + * List)}. + */ + private ConcurrentHashMap> categoryKeysWithMissingParents = + new ConcurrentHashMap<>(); - public CategorySyncStatistics() { - super(); - } + public CategorySyncStatistics() { + super(); + } - /** - * Builds a summary of the category sync statistics instance that looks like the following example: - * - *

"Summary: 2 categories were processed in total (0 created, 0 updated and 0 categories failed to sync)." - * - * @return a summary message of the category sync statistics instance. - */ - @Override - public String getReportMessage() { - return format("Summary: %s categories were processed in total " - + "(%s created, %s updated, %s failed to sync and %s categories with a missing parent).", - getProcessed(), getCreated(), getUpdated(), getFailed(), getNumberOfCategoriesWithMissingParents()); - } + /** + * Builds a summary of the category sync statistics instance that looks like the following + * example: + * + *

"Summary: 2 categories were processed in total (0 created, 0 updated and 0 categories failed + * to sync)." + * + * @return a summary message of the category sync statistics instance. + */ + @Override + public String getReportMessage() { + return format( + "Summary: %s categories were processed in total " + + "(%s created, %s updated, %s failed to sync and %s categories with a missing parent).", + getProcessed(), + getCreated(), + getUpdated(), + getFailed(), + getNumberOfCategoriesWithMissingParents()); + } - /** - * Returns the total number of categories with missing parents. - * - * @return the total number of categories with missing parents. - */ - public int getNumberOfCategoriesWithMissingParents() { - return categoryKeysWithMissingParents.values() - .stream() - .mapToInt(Set::size) - .sum(); - } + /** + * Returns the total number of categories with missing parents. + * + * @return the total number of categories with missing parents. + */ + public int getNumberOfCategoriesWithMissingParents() { + return categoryKeysWithMissingParents.values().stream().mapToInt(Set::size).sum(); + } - public Map> getCategoryKeysWithMissingParents() { - return Collections.unmodifiableMap(categoryKeysWithMissingParents); - } + public Map> getCategoryKeysWithMissingParents() { + return Collections.unmodifiableMap(categoryKeysWithMissingParents); + } - public void setCategoryKeysWithMissingParents(@Nonnull final ConcurrentHashMap> - categoryKeysWithMissingParents) { - this.categoryKeysWithMissingParents = categoryKeysWithMissingParents; - } + public void setCategoryKeysWithMissingParents( + @Nonnull final ConcurrentHashMap> categoryKeysWithMissingParents) { + this.categoryKeysWithMissingParents = categoryKeysWithMissingParents; + } - /** - * This method checks if there is an entry with the key of the {@code missingParentCategoryKey} in the - * {@code categoryKeysWithMissingParents}, if there isn't it creates a new entry with this parent key and as a value - * a new set containing the {@code childKey}. Otherwise, if there is already, it just adds the - * {@code categoryKey} to the existing set. - * - * @param missingParentCategoryKey the key of the missing parent. - * @param childKey the key of the category with a missing parent. - */ - public void putMissingParentCategoryChildKey(@Nonnull final String missingParentCategoryKey, - @Nonnull final String childKey) { - final Set missingParentCategoryChildrenKeys = - categoryKeysWithMissingParents.get(missingParentCategoryKey); - if (missingParentCategoryChildrenKeys != null) { - missingParentCategoryChildrenKeys.add(childKey); - } else { - final Set newChildCategoryKeys = new HashSet<>(); - newChildCategoryKeys.add(childKey); - categoryKeysWithMissingParents.put(missingParentCategoryKey, newChildCategoryKeys); - } + /** + * This method checks if there is an entry with the key of the {@code missingParentCategoryKey} in + * the {@code categoryKeysWithMissingParents}, if there isn't it creates a new entry with this + * parent key and as a value a new set containing the {@code childKey}. Otherwise, if there is + * already, it just adds the {@code categoryKey} to the existing set. + * + * @param missingParentCategoryKey the key of the missing parent. + * @param childKey the key of the category with a missing parent. + */ + public void putMissingParentCategoryChildKey( + @Nonnull final String missingParentCategoryKey, @Nonnull final String childKey) { + final Set missingParentCategoryChildrenKeys = + categoryKeysWithMissingParents.get(missingParentCategoryKey); + if (missingParentCategoryChildrenKeys != null) { + missingParentCategoryChildrenKeys.add(childKey); + } else { + final Set newChildCategoryKeys = new HashSet<>(); + newChildCategoryKeys.add(childKey); + categoryKeysWithMissingParents.put(missingParentCategoryKey, newChildCategoryKeys); } + } - /** - * Given a {@code childCategoryKey} this method, checks in the {@code categoryKeysWithMissingParents} - * if it exists as a child to a missing parent, and returns the key of first found (since a category can have only - * one parent) missing parent in an optional. Otherwise, it returns an empty optional. - * - * @param childCategoryKey key of the category to find it's has a missing parent key. - * @return the key of that missing parent in an optional, if it exists. Otherwise, it returns an empty optional. - */ - @Nonnull - public Optional getMissingParentKey(@Nonnull final String childCategoryKey) { - return categoryKeysWithMissingParents.entrySet() - .stream() - .filter(missingParentEntry -> missingParentEntry.getValue().contains( - childCategoryKey)) - .findFirst() - .map(Map.Entry::getKey); - } + /** + * Given a {@code childCategoryKey} this method, checks in the {@code + * categoryKeysWithMissingParents} if it exists as a child to a missing parent, and returns the + * key of first found (since a category can have only one parent) missing parent in an optional. + * Otherwise, it returns an empty optional. + * + * @param childCategoryKey key of the category to find it's has a missing parent key. + * @return the key of that missing parent in an optional, if it exists. Otherwise, it returns an + * empty optional. + */ + @Nonnull + public Optional getMissingParentKey(@Nonnull final String childCategoryKey) { + return categoryKeysWithMissingParents.entrySet().stream() + .filter(missingParentEntry -> missingParentEntry.getValue().contains(childCategoryKey)) + .findFirst() + .map(Map.Entry::getKey); + } - /** - * Given a child {@code categoryKey} this method removes its occurrences from the map - * {@code categoryKeysWithMissingParents}. - * - *

NOTE: When all the children keys of a missing parent are removed, the value of the map entry will be - * an empty list. i.e. the entry itself will not be removed. However, this could be investigated whether removing - * the entry at all when the list is empty will affect the algorithm. TODO: RELATED BUT NOT SAME AS GITHUB ISSUE#77 - * - * @param childCategoryKey the child category key to remove from {@code categoryKeysWithMissingParents} - */ - public void removeChildCategoryKeyFromMissingParentsMap(@Nonnull final String childCategoryKey) { - categoryKeysWithMissingParents.forEach((key, value) -> value.remove(childCategoryKey)); - } + /** + * Given a child {@code categoryKey} this method removes its occurrences from the map {@code + * categoryKeysWithMissingParents}. + * + *

NOTE: When all the children keys of a missing parent are removed, the value of the map entry + * will be an empty list. i.e. the entry itself will not be removed. However, this could be + * investigated whether removing the entry at all when the list is empty will affect the + * algorithm. TODO: RELATED BUT NOT SAME AS GITHUB ISSUE#77 + * + * @param childCategoryKey the child category key to remove from {@code + * categoryKeysWithMissingParents} + */ + public void removeChildCategoryKeyFromMissingParentsMap(@Nonnull final String childCategoryKey) { + categoryKeysWithMissingParents.forEach((key, value) -> value.remove(childCategoryKey)); + } } diff --git a/src/main/java/com/commercetools/sync/categories/utils/CategoryAssetUpdateActionUtils.java b/src/main/java/com/commercetools/sync/categories/utils/CategoryAssetUpdateActionUtils.java index daba94f1dd..c37f9eace4 100644 --- a/src/main/java/com/commercetools/sync/categories/utils/CategoryAssetUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/categories/utils/CategoryAssetUpdateActionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.categories.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.helpers.AssetCustomActionBuilder; import com.commercetools.sync.commons.utils.CustomUpdateActionUtils; @@ -13,159 +16,163 @@ import io.sphere.sdk.models.AssetDraft; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.Resource; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import javax.annotation.Nonnull; public final class CategoryAssetUpdateActionUtils { - /** - * Compares all the fields of an {@link Asset} and an {@link AssetDraft} and returns a list of - * {@link UpdateAction}<{@link Category}> as a result. If both the {@link Asset} and the {@link AssetDraft} - * have identical fields, then no update action is needed and hence an empty {@link List} is returned. - * - * @param Type of the mainresource draft - * @param oldResource mainresource, whose asset should be updated. - * @param newResource new mainresource draft, which contains the asset to update. - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new fields. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the update actions or an empty list if the asset fields are identical. - */ - @Nonnull - public static List> buildActions( - @Nonnull final Resource oldResource, - @Nonnull final D newResource, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset, - @Nonnull final CategorySyncOptions syncOptions) { + /** + * Compares all the fields of an {@link Asset} and an {@link AssetDraft} and returns a list of + * {@link UpdateAction}<{@link Category}> as a result. If both the {@link Asset} and the + * {@link AssetDraft} have identical fields, then no update action is needed and hence an empty + * {@link List} is returned. + * + * @param Type of the mainresource draft + * @param oldResource mainresource, whose asset should be updated. + * @param newResource new mainresource draft, which contains the asset to update. + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new fields. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the update actions or an empty list if the asset fields are identical. + */ + @Nonnull + public static List> buildActions( + @Nonnull final Resource oldResource, + @Nonnull final D newResource, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAsset, + @Nonnull final CategorySyncOptions syncOptions) { - final List> updateActions = filterEmptyOptionals( + final List> updateActions = + filterEmptyOptionals( buildChangeAssetNameUpdateAction(oldAsset, newAsset), buildSetAssetDescriptionUpdateAction(oldAsset, newAsset), buildSetAssetTagsUpdateAction(oldAsset, newAsset), - buildSetAssetSourcesUpdateAction(oldAsset, newAsset) - ); - - updateActions.addAll(buildCustomUpdateActions(oldResource, newResource, oldAsset, newAsset, syncOptions)); - return updateActions; - } + buildSetAssetSourcesUpdateAction(oldAsset, newAsset)); + updateActions.addAll( + buildCustomUpdateActions(oldResource, newResource, oldAsset, newAsset, syncOptions)); + return updateActions; + } - /** - * Compares the {@link LocalizedString} names of an {@link Asset} and an {@link AssetDraft} and returns an - * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link Asset} and - * the {@link AssetDraft} have the same name, then no update action is needed and hence an empty {@link Optional} - * is returned. - * - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new name. - * @return A filled optional with the update action or an empty optional if the names are identical. - */ - @Nonnull - public static Optional> buildChangeAssetNameUpdateAction( - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset) { - return buildUpdateAction(oldAsset.getName(), newAsset.getName(), - () -> ChangeAssetName.ofKey(oldAsset.getKey(), newAsset.getName())); - } + /** + * Compares the {@link LocalizedString} names of an {@link Asset} and an {@link AssetDraft} and + * returns an {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If + * both the {@link Asset} and the {@link AssetDraft} have the same name, then no update action is + * needed and hence an empty {@link Optional} is returned. + * + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new name. + * @return A filled optional with the update action or an empty optional if the names are + * identical. + */ + @Nonnull + public static Optional> buildChangeAssetNameUpdateAction( + @Nonnull final Asset oldAsset, @Nonnull final AssetDraft newAsset) { + return buildUpdateAction( + oldAsset.getName(), + newAsset.getName(), + () -> ChangeAssetName.ofKey(oldAsset.getKey(), newAsset.getName())); + } - /** - * Compares the {@link LocalizedString} descriptions of an {@link Asset} and an {@link AssetDraft} and returns an - * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link Asset} and - * the {@link AssetDraft} have the same description, then no update action is needed and hence an empty - * {@link Optional} is returned. - * - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new description. - * @return A filled optional with the update action or an empty optional if the descriptions are identical. - */ - @Nonnull - public static Optional> buildSetAssetDescriptionUpdateAction( - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset) { - return buildUpdateAction(oldAsset.getDescription(), newAsset.getDescription(), - () -> SetAssetDescription.ofKey(oldAsset.getKey(), newAsset.getDescription())); - } + /** + * Compares the {@link LocalizedString} descriptions of an {@link Asset} and an {@link AssetDraft} + * and returns an {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. + * If both the {@link Asset} and the {@link AssetDraft} have the same description, then no update + * action is needed and hence an empty {@link Optional} is returned. + * + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new description. + * @return A filled optional with the update action or an empty optional if the descriptions are + * identical. + */ + @Nonnull + public static Optional> buildSetAssetDescriptionUpdateAction( + @Nonnull final Asset oldAsset, @Nonnull final AssetDraft newAsset) { + return buildUpdateAction( + oldAsset.getDescription(), + newAsset.getDescription(), + () -> SetAssetDescription.ofKey(oldAsset.getKey(), newAsset.getDescription())); + } - /** - * Compares the tags of an {@link Asset} and an {@link AssetDraft} and returns an - * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link Asset} and - * the {@link AssetDraft} have the same tags, then no update action is needed and hence an empty {@link Optional} is - * returned. - * - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new tags. - * @return A filled optional with the update action or an empty optional if the tags are identical. - */ - @Nonnull - public static Optional> buildSetAssetTagsUpdateAction( - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset) { - return buildUpdateAction(oldAsset.getTags(), newAsset.getTags(), - () -> SetAssetTags.ofKey(oldAsset.getKey(), newAsset.getTags())); - } + /** + * Compares the tags of an {@link Asset} and an {@link AssetDraft} and returns an {@link + * UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link + * Asset} and the {@link AssetDraft} have the same tags, then no update action is needed and hence + * an empty {@link Optional} is returned. + * + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new tags. + * @return A filled optional with the update action or an empty optional if the tags are + * identical. + */ + @Nonnull + public static Optional> buildSetAssetTagsUpdateAction( + @Nonnull final Asset oldAsset, @Nonnull final AssetDraft newAsset) { + return buildUpdateAction( + oldAsset.getTags(), + newAsset.getTags(), + () -> SetAssetTags.ofKey(oldAsset.getKey(), newAsset.getTags())); + } - /** - * Compares the sources of an {@link Asset} and an {@link AssetDraft} and returns an - * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link Asset} and - * the {@link AssetDraft} have the same sources, then no update action is needed and hence an empty {@link Optional} - * is returned. - * - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new sources. - * @return A filled optional with the update action or an empty optional if the sources are identical. - */ - @Nonnull - public static Optional> buildSetAssetSourcesUpdateAction( - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset) { - return buildUpdateAction(oldAsset.getSources(), newAsset.getSources(), - () -> SetAssetSources.ofKey(oldAsset.getKey(), newAsset.getSources())); - } + /** + * Compares the sources of an {@link Asset} and an {@link AssetDraft} and returns an {@link + * UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link + * Asset} and the {@link AssetDraft} have the same sources, then no update action is needed and + * hence an empty {@link Optional} is returned. + * + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new sources. + * @return A filled optional with the update action or an empty optional if the sources are + * identical. + */ + @Nonnull + public static Optional> buildSetAssetSourcesUpdateAction( + @Nonnull final Asset oldAsset, @Nonnull final AssetDraft newAsset) { + return buildUpdateAction( + oldAsset.getSources(), + newAsset.getSources(), + () -> SetAssetSources.ofKey(oldAsset.getKey(), newAsset.getSources())); + } - /** - * Compares the custom fields and custom types of an {@link Asset} and an {@link AssetDraft} and returns a list of - * {@link UpdateAction}<{@link Category}> as a result. If both the {@link Asset} and the {@link AssetDraft} - * have identical custom fields and types, then no update action is needed and hence an empty {@link List} is - * returned. - * - * @param Type of the mainresource draft - * @param oldCategory category in a target project, whose asset should be updated. - * @param newCategory category in a source project, which contains the updated asset. - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new custom fields and types. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the custom field/type update actions or an empty list if the custom fields/types are - * identical. - */ - @Nonnull - public static List> buildCustomUpdateActions( - @Nonnull final Resource oldCategory, - @Nonnull final D newCategory, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset, - @Nonnull final CategorySyncOptions syncOptions) { + /** + * Compares the custom fields and custom types of an {@link Asset} and an {@link AssetDraft} and + * returns a list of {@link UpdateAction}<{@link Category}> as a result. If both the {@link + * Asset} and the {@link AssetDraft} have identical custom fields and types, then no update action + * is needed and hence an empty {@link List} is returned. + * + * @param Type of the mainresource draft + * @param oldCategory category in a target project, whose asset should be updated. + * @param newCategory category in a source project, which contains the updated asset. + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new custom fields and types. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the custom field/type update actions or an empty list if the custom + * fields/types are identical. + */ + @Nonnull + public static List> buildCustomUpdateActions( + @Nonnull final Resource oldCategory, + @Nonnull final D newCategory, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAsset, + @Nonnull final CategorySyncOptions syncOptions) { - return CustomUpdateActionUtils.buildCustomUpdateActions( - oldCategory, - newCategory, - oldAsset, - newAsset, - new AssetCustomActionBuilder(), - -1, - Asset::getId, - asset -> Asset.resourceTypeId(), - Asset::getKey, - syncOptions); - } + return CustomUpdateActionUtils.buildCustomUpdateActions( + oldCategory, + newCategory, + oldAsset, + newAsset, + new AssetCustomActionBuilder(), + -1, + Asset::getId, + asset -> Asset.resourceTypeId(), + Asset::getKey, + syncOptions); + } - private CategoryAssetUpdateActionUtils() { - } + private CategoryAssetUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtils.java index 0310379feb..05202ff9fa 100644 --- a/src/main/java/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtils.java @@ -1,5 +1,9 @@ package com.commercetools.sync.categories.utils; +import static com.commercetools.sync.commons.utils.AssetReferenceResolutionUtils.mapToAssetDrafts; +import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; +import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; + import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.CategoryDraft; import io.sphere.sdk.categories.CategoryDraftBuilder; @@ -10,103 +14,97 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.queries.QueryExecutionUtils; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; import java.util.List; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.AssetReferenceResolutionUtils.mapToAssetDrafts; -import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; -import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; +import javax.annotation.Nonnull; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class CategoryReferenceResolutionUtils { - private CategoryReferenceResolutionUtils() { - } + private CategoryReferenceResolutionUtils() {} - /** - * Returns an {@link List}<{@link CategoryDraft}> consisting of the results of applying the - * mapping from {@link Category} to {@link CategoryDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
parent{@link Reference}<{@link Category}>{@link ResourceIdentifier}<{@link Category}>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
asset.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
- * - *

Note: The {@link Category} and {@link Type} references should be expanded with a key. - * Any reference that is not expanded will have its id in place and not replaced by the key will be - * considered as existing resources on the target commercetools project and - * the library will issues an update/create API request without reference resolution. - * - * @param categories the categories with expanded references. - * @return a {@link List} of {@link CategoryDraft} built from the - * supplied {@link List} of {@link Category}. - */ - @Nonnull - public static List mapToCategoryDrafts(@Nonnull final List categories) { - return categories - .stream() - .map(CategoryReferenceResolutionUtils::mapToCategoryDraft) - .collect(Collectors.toList()); - } + /** + * Returns an {@link List}<{@link CategoryDraft}> consisting of the results of applying the + * mapping from {@link Category} to {@link CategoryDraft} with considering reference resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
parent{@link Reference}<{@link Category}>{@link ResourceIdentifier}<{@link Category}>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
asset.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
+ * + *

Note: The {@link Category} and {@link Type} references should be expanded with a key. + * Any reference that is not expanded will have its id in place and not replaced by the key will + * be considered as existing resources on the target commercetools project and the library will + * issues an update/create API request without reference resolution. + * + * @param categories the categories with expanded references. + * @return a {@link List} of {@link CategoryDraft} built from the supplied {@link List} of {@link + * Category}. + */ + @Nonnull + public static List mapToCategoryDrafts(@Nonnull final List categories) { + return categories.stream() + .map(CategoryReferenceResolutionUtils::mapToCategoryDraft) + .collect(Collectors.toList()); + } - @Nonnull - private static CategoryDraft mapToCategoryDraft(@Nonnull final Category category) { - return CategoryDraftBuilder - .of(category) - .custom(mapToCustomFieldsDraft(category)) - .assets(mapToAssetDrafts(category.getAssets())) - .parent(getResourceIdentifierWithKey(category.getParent())) - .build(); - } + @Nonnull + private static CategoryDraft mapToCategoryDraft(@Nonnull final Category category) { + return CategoryDraftBuilder.of(category) + .custom(mapToCustomFieldsDraft(category)) + .assets(mapToAssetDrafts(category.getAssets())) + .parent(getResourceIdentifierWithKey(category.getParent())) + .build(); + } - /** - * Builds a {@link CategoryQuery} for fetching categories from a source CTP project with all the needed references - * expanded for the sync: - *

    - *
  • Custom Type
  • - *
  • Assets Custom Types
  • - *
  • Parent Category
  • - *
- * - *

Note: Please only use this util if you desire to sync all the aforementioned references from - * a source commercetools project. Otherwise, it is more efficient to build the query without expansions, if they - * are not needed, to avoid unnecessarily bigger payloads fetched from the source project. - * - * @return the query for fetching categories from the source CTP project with all the aforementioned references - * expanded. - */ - public static CategoryQuery buildCategoryQuery() { - return CategoryQuery.of() - .withLimit(QueryExecutionUtils.DEFAULT_PAGE_SIZE) - .withExpansionPaths(ExpansionPath.of("custom.type")) - .plusExpansionPaths(ExpansionPath.of("assets[*].custom.type")) - .plusExpansionPaths(CategoryExpansionModel::parent); - } + /** + * Builds a {@link CategoryQuery} for fetching categories from a source CTP project with all the + * needed references expanded for the sync: + * + *

    + *
  • Custom Type + *
  • Assets Custom Types + *
  • Parent Category + *
+ * + *

Note: Please only use this util if you desire to sync all the aforementioned references from + * a source commercetools project. Otherwise, it is more efficient to build the query without + * expansions, if they are not needed, to avoid unnecessarily bigger payloads fetched from the + * source project. + * + * @return the query for fetching categories from the source CTP project with all the + * aforementioned references expanded. + */ + public static CategoryQuery buildCategoryQuery() { + return CategoryQuery.of() + .withLimit(QueryExecutionUtils.DEFAULT_PAGE_SIZE) + .withExpansionPaths(ExpansionPath.of("custom.type")) + .plusExpansionPaths(ExpansionPath.of("assets[*].custom.type")) + .plusExpansionPaths(CategoryExpansionModel::parent); + } } diff --git a/src/main/java/com/commercetools/sync/categories/utils/CategorySyncUtils.java b/src/main/java/com/commercetools/sync/categories/utils/CategorySyncUtils.java index e8d2c8ce60..9e6632ba0f 100644 --- a/src/main/java/com/commercetools/sync/categories/utils/CategorySyncUtils.java +++ b/src/main/java/com/commercetools/sync/categories/utils/CategorySyncUtils.java @@ -1,15 +1,5 @@ package com.commercetools.sync.categories.utils; -import com.commercetools.sync.categories.CategorySyncOptions; -import com.commercetools.sync.categories.helpers.CategoryCustomActionBuilder; -import com.commercetools.sync.commons.BaseSyncOptions; -import io.sphere.sdk.categories.Category; -import io.sphere.sdk.categories.CategoryDraft; -import io.sphere.sdk.commands.UpdateAction; - -import javax.annotation.Nonnull; -import java.util.List; - import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildAssetsUpdateActions; import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeNameUpdateAction; import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeOrderHintUpdateAction; @@ -23,31 +13,41 @@ import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildPrimaryResourceCustomUpdateActions; import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import com.commercetools.sync.categories.CategorySyncOptions; +import com.commercetools.sync.categories.helpers.CategoryCustomActionBuilder; +import com.commercetools.sync.commons.BaseSyncOptions; +import io.sphere.sdk.categories.Category; +import io.sphere.sdk.categories.CategoryDraft; +import io.sphere.sdk.commands.UpdateAction; +import java.util.List; +import javax.annotation.Nonnull; + public final class CategorySyncUtils { - private static final CategoryCustomActionBuilder categoryCustomActionBuilder = - new CategoryCustomActionBuilder(); + private static final CategoryCustomActionBuilder categoryCustomActionBuilder = + new CategoryCustomActionBuilder(); - /** - * Compares all the fields of a {@link Category} and a {@link CategoryDraft}. It returns a {@link List} of - * {@link UpdateAction}<{@link Category}> as a result. If no update action is needed, for example in - * case where both the {@link Category} and the {@link CategoryDraft} have the same parents, an empty - * {@link List} is returned. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new data. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied - * by the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link BaseSyncOptions} - * for more info. - * @return A list of category-specific update actions. - */ - @Nonnull - public static List> buildActions( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory, - @Nonnull final CategorySyncOptions syncOptions) { + /** + * Compares all the fields of a {@link Category} and a {@link CategoryDraft}. It returns a {@link + * List} of {@link UpdateAction}<{@link Category}> as a result. If no update action is + * needed, for example in case where both the {@link Category} and the {@link CategoryDraft} have + * the same parents, an empty {@link List} is returned. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new data. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * BaseSyncOptions} for more info. + * @return A list of category-specific update actions. + */ + @Nonnull + public static List> buildActions( + @Nonnull final Category oldCategory, + @Nonnull final CategoryDraft newCategory, + @Nonnull final CategorySyncOptions syncOptions) { - final List> updateActions = filterEmptyOptionals( + final List> updateActions = + filterEmptyOptionals( buildChangeNameUpdateAction(oldCategory, newCategory), buildChangeSlugUpdateAction(oldCategory, newCategory), buildSetExternalIdUpdateAction(oldCategory, newCategory), @@ -56,21 +56,20 @@ public static List> buildActions( buildChangeOrderHintUpdateAction(oldCategory, newCategory, syncOptions), buildSetMetaTitleUpdateAction(oldCategory, newCategory), buildSetMetaDescriptionUpdateAction(oldCategory, newCategory), - buildSetMetaKeywordsUpdateAction(oldCategory, newCategory) - ); + buildSetMetaKeywordsUpdateAction(oldCategory, newCategory)); - final List> categoryCustomUpdateActions = - buildPrimaryResourceCustomUpdateActions(oldCategory, newCategory, categoryCustomActionBuilder, syncOptions); + final List> categoryCustomUpdateActions = + buildPrimaryResourceCustomUpdateActions( + oldCategory, newCategory, categoryCustomActionBuilder, syncOptions); - final List> assetsUpdateActions = - buildAssetsUpdateActions(oldCategory, newCategory, syncOptions); + final List> assetsUpdateActions = + buildAssetsUpdateActions(oldCategory, newCategory, syncOptions); - updateActions.addAll(categoryCustomUpdateActions); - updateActions.addAll(assetsUpdateActions); + updateActions.addAll(categoryCustomUpdateActions); + updateActions.addAll(assetsUpdateActions); - return updateActions; - } + return updateActions; + } - private CategorySyncUtils() { - } + private CategorySyncUtils() {} } diff --git a/src/main/java/com/commercetools/sync/categories/utils/CategoryUpdateActionUtils.java b/src/main/java/com/commercetools/sync/categories/utils/CategoryUpdateActionUtils.java index 2c2884ef84..88aedaf543 100644 --- a/src/main/java/com/commercetools/sync/categories/utils/CategoryUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/categories/utils/CategoryUpdateActionUtils.java @@ -1,5 +1,9 @@ package com.commercetools.sync.categories.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; +import static java.lang.String.format; +import static java.util.Collections.emptyList; import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.helpers.CategoryAssetActionFactory; @@ -21,250 +25,271 @@ import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; -import static java.lang.String.format; -import static java.util.Collections.emptyList; +import javax.annotation.Nonnull; public final class CategoryUpdateActionUtils { - private static final String CATEGORY_CHANGE_PARENT_EMPTY_PARENT = "Cannot unset 'parent' field of category with id" - + " '%s'."; - private static final String CATEGORY_CHANGE_ORDER_HINT_EMPTY_ORDERHINT = "Cannot unset 'orderHint' field of " - + "category with id '%s'."; + private static final String CATEGORY_CHANGE_PARENT_EMPTY_PARENT = + "Cannot unset 'parent' field of category with id '%s'."; + private static final String CATEGORY_CHANGE_ORDER_HINT_EMPTY_ORDERHINT = + "Cannot unset 'orderHint' field of category with id '%s'."; - /** - * Compares the {@link LocalizedString} names of a {@link Category} and a {@link CategoryDraft} and returns an - * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link Category} and - * the {@link CategoryDraft} have the same name, then no update action is needed and hence an empty {@link Optional} - * is returned. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new name. - * @return A filled optional with the update action or an empty optional if the names are identical. - */ - @Nonnull - public static Optional> buildChangeNameUpdateAction( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory) { - return buildUpdateAction(oldCategory.getName(), - newCategory.getName(), () -> ChangeName.of(newCategory.getName())); - } + /** + * Compares the {@link LocalizedString} names of a {@link Category} and a {@link CategoryDraft} + * and returns an {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. + * If both the {@link Category} and the {@link CategoryDraft} have the same name, then no update + * action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new name. + * @return A filled optional with the update action or an empty optional if the names are + * identical. + */ + @Nonnull + public static Optional> buildChangeNameUpdateAction( + @Nonnull final Category oldCategory, @Nonnull final CategoryDraft newCategory) { + return buildUpdateAction( + oldCategory.getName(), newCategory.getName(), () -> ChangeName.of(newCategory.getName())); + } - /** - * Compares the {@link LocalizedString} slugs of a {@link Category} and a {@link CategoryDraft} and returns an - * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link Category} and - * the {@link CategoryDraft} have the same slug, then no update action is needed and hence an empty {@link Optional} - * is returned. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new slug. - * @return A filled optional with the update action or an empty optional if the slugs are identical. - */ - @Nonnull - public static Optional> buildChangeSlugUpdateAction( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory) { - return buildUpdateAction(oldCategory.getSlug(), - newCategory.getSlug(), () -> ChangeSlug.of(newCategory.getSlug())); - } + /** + * Compares the {@link LocalizedString} slugs of a {@link Category} and a {@link CategoryDraft} + * and returns an {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. + * If both the {@link Category} and the {@link CategoryDraft} have the same slug, then no update + * action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new slug. + * @return A filled optional with the update action or an empty optional if the slugs are + * identical. + */ + @Nonnull + public static Optional> buildChangeSlugUpdateAction( + @Nonnull final Category oldCategory, @Nonnull final CategoryDraft newCategory) { + return buildUpdateAction( + oldCategory.getSlug(), newCategory.getSlug(), () -> ChangeSlug.of(newCategory.getSlug())); + } - /** - * Compares the {@link LocalizedString} descriptions of a {@link Category} and a {@link CategoryDraft} and - * returns an {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the - * {@link Category} and the {@link CategoryDraft} have the same description, then no update action is needed and - * hence an empty {@link Optional} is returned. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new description. - * @return A filled optional with the update action or an empty optional if the descriptions are identical. - */ - @Nonnull - public static Optional> buildSetDescriptionUpdateAction( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory) { - return buildUpdateAction(oldCategory.getDescription(), - newCategory.getDescription(), () -> SetDescription.of(newCategory.getDescription())); - } + /** + * Compares the {@link LocalizedString} descriptions of a {@link Category} and a {@link + * CategoryDraft} and returns an {@link UpdateAction}<{@link Category}> as a result in an + * {@link Optional}. If both the {@link Category} and the {@link CategoryDraft} have the same + * description, then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new description. + * @return A filled optional with the update action or an empty optional if the descriptions are + * identical. + */ + @Nonnull + public static Optional> buildSetDescriptionUpdateAction( + @Nonnull final Category oldCategory, @Nonnull final CategoryDraft newCategory) { + return buildUpdateAction( + oldCategory.getDescription(), + newCategory.getDescription(), + () -> SetDescription.of(newCategory.getDescription())); + } - /** - * Compares the parents {@link Reference}<{@link Category}> of a {@link Category} and a {@link CategoryDraft} - * and returns an {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the - * {@link Category} and the {@link CategoryDraft} have the same parents, then no update action is needed and hence - * an empty {@link Optional} is returned. - * - *

Note: If the parent {@link Reference}<{@link Category}> of the new {@link CategoryDraft} is null, an - * empty {@link Optional} is returned with no update actions and a custom callback function, if set on the - * supplied {@link CategorySyncOptions}, is called. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new parent. - * @param syncOptions the sync syncOptions with which a custom callback function is called in case the parent - * is null. - * @return A filled optional with the update action or an empty optional if the parent references are identical. - */ - @Nonnull - public static Optional> buildChangeParentUpdateAction( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory, - @Nonnull final CategorySyncOptions syncOptions) { + /** + * Compares the parents {@link Reference}<{@link Category}> of a {@link Category} and a + * {@link CategoryDraft} and returns an {@link UpdateAction}<{@link Category}> as a result + * in an {@link Optional}. If both the {@link Category} and the {@link CategoryDraft} have the + * same parents, then no update action is needed and hence an empty {@link Optional} is returned. + * + *

Note: If the parent {@link Reference}<{@link Category}> of the new {@link + * CategoryDraft} is null, an empty {@link Optional} is returned with no update actions and a + * custom callback function, if set on the supplied {@link CategorySyncOptions}, is called. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new parent. + * @param syncOptions the sync syncOptions with which a custom callback function is called in case + * the parent is null. + * @return A filled optional with the update action or an empty optional if the parent references + * are identical. + */ + @Nonnull + public static Optional> buildChangeParentUpdateAction( + @Nonnull final Category oldCategory, + @Nonnull final CategoryDraft newCategory, + @Nonnull final CategorySyncOptions syncOptions) { - final Reference oldParent = oldCategory.getParent(); - final ResourceIdentifier newParent = newCategory.getParent(); - if (newParent == null && oldParent != null) { - syncOptions.applyWarningCallback( - new SyncException(format(CATEGORY_CHANGE_PARENT_EMPTY_PARENT, oldCategory.getId())), - oldCategory, newCategory); - return Optional.empty(); - } else { - // The newParent.getId() call below can not cause an NPE in this case, since if both newParent and oldParent - // are null, then the supplier will not be called at all. The remaining cases all involve the newParent - // being not null. - return buildUpdateActionForReferences(oldParent, newParent, - () -> ChangeParent.of(ResourceIdentifier.ofId(newParent.getId()))); - } + final Reference oldParent = oldCategory.getParent(); + final ResourceIdentifier newParent = newCategory.getParent(); + if (newParent == null && oldParent != null) { + syncOptions.applyWarningCallback( + new SyncException(format(CATEGORY_CHANGE_PARENT_EMPTY_PARENT, oldCategory.getId())), + oldCategory, + newCategory); + return Optional.empty(); + } else { + // The newParent.getId() call below can not cause an NPE in this case, since if both newParent + // and oldParent + // are null, then the supplier will not be called at all. The remaining cases all involve the + // newParent + // being not null. + return buildUpdateActionForReferences( + oldParent, newParent, () -> ChangeParent.of(ResourceIdentifier.ofId(newParent.getId()))); } + } - /** - * Compares the orderHint values of a {@link Category} and a {@link CategoryDraft} and returns an - * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link Category} and - * the {@link CategoryDraft} have the same orderHint, then no update action is needed and hence an empty - * {@link Optional} is returned. - * - *

Note: If the orderHint of the new {@link CategoryDraft} is null, an empty {@link Optional} is returned with - * no update actions and a custom callback function, if set on the supplied {@link CategorySyncOptions}, is called. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new orderHint. - * @param syncOptions the sync syncOptions with which a custom callback function is called in case the orderHint - * is null. - * @return A filled optional with the update action or an empty optional if the orderHint values are identical. - */ - @Nonnull - public static Optional> buildChangeOrderHintUpdateAction( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory, - @Nonnull final CategorySyncOptions syncOptions) { - if (newCategory.getOrderHint() == null && oldCategory.getOrderHint() != null) { - syncOptions.applyWarningCallback(new SyncException( - format(CATEGORY_CHANGE_ORDER_HINT_EMPTY_ORDERHINT, oldCategory.getId())), oldCategory, newCategory); - return Optional.empty(); - } - return buildUpdateAction(oldCategory.getOrderHint(), - newCategory.getOrderHint(), () -> ChangeOrderHint.of(newCategory.getOrderHint())); + /** + * Compares the orderHint values of a {@link Category} and a {@link CategoryDraft} and returns an + * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the + * {@link Category} and the {@link CategoryDraft} have the same orderHint, then no update action + * is needed and hence an empty {@link Optional} is returned. + * + *

Note: If the orderHint of the new {@link CategoryDraft} is null, an empty {@link Optional} + * is returned with no update actions and a custom callback function, if set on the supplied + * {@link CategorySyncOptions}, is called. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new orderHint. + * @param syncOptions the sync syncOptions with which a custom callback function is called in case + * the orderHint is null. + * @return A filled optional with the update action or an empty optional if the orderHint values + * are identical. + */ + @Nonnull + public static Optional> buildChangeOrderHintUpdateAction( + @Nonnull final Category oldCategory, + @Nonnull final CategoryDraft newCategory, + @Nonnull final CategorySyncOptions syncOptions) { + if (newCategory.getOrderHint() == null && oldCategory.getOrderHint() != null) { + syncOptions.applyWarningCallback( + new SyncException( + format(CATEGORY_CHANGE_ORDER_HINT_EMPTY_ORDERHINT, oldCategory.getId())), + oldCategory, + newCategory); + return Optional.empty(); } + return buildUpdateAction( + oldCategory.getOrderHint(), + newCategory.getOrderHint(), + () -> ChangeOrderHint.of(newCategory.getOrderHint())); + } - /** - * Compares the {@link LocalizedString} meta title of a {@link Category} and a {@link CategoryDraft} and returns an - * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link Category} and - * the {@link CategoryDraft} have the same meta title, then no update action is needed and hence an empty - * {@link Optional} is returned. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new meta title. - * @return A filled optional with the update action or an empty optional if the meta titles values are identical. - */ - @Nonnull - public static Optional> buildSetMetaTitleUpdateAction( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory) { - return buildUpdateAction(oldCategory.getMetaTitle(), - newCategory.getMetaTitle(), () -> SetMetaTitle.of(newCategory.getMetaTitle())); - } + /** + * Compares the {@link LocalizedString} meta title of a {@link Category} and a {@link + * CategoryDraft} and returns an {@link UpdateAction}<{@link Category}> as a result in an + * {@link Optional}. If both the {@link Category} and the {@link CategoryDraft} have the same meta + * title, then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new meta title. + * @return A filled optional with the update action or an empty optional if the meta titles values + * are identical. + */ + @Nonnull + public static Optional> buildSetMetaTitleUpdateAction( + @Nonnull final Category oldCategory, @Nonnull final CategoryDraft newCategory) { + return buildUpdateAction( + oldCategory.getMetaTitle(), + newCategory.getMetaTitle(), + () -> SetMetaTitle.of(newCategory.getMetaTitle())); + } - /** - * Compares the {@link LocalizedString} meta keywords of a {@link Category} and a {@link CategoryDraft} and - * returns an {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the - * {@link Category} and the {@link CategoryDraft} have the same meta keywords, then no update action is needed and - * hence an empty {@link Optional} is returned. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new meta keywords. - * @return A filled optional with the update action or an empty optional if the meta keywords values are identical. - */ - @Nonnull - public static Optional> buildSetMetaKeywordsUpdateAction( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory) { - return buildUpdateAction(oldCategory.getMetaKeywords(), - newCategory.getMetaKeywords(), () -> SetMetaKeywords.of(newCategory.getMetaKeywords())); - } + /** + * Compares the {@link LocalizedString} meta keywords of a {@link Category} and a {@link + * CategoryDraft} and returns an {@link UpdateAction}<{@link Category}> as a result in an + * {@link Optional}. If both the {@link Category} and the {@link CategoryDraft} have the same meta + * keywords, then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new meta keywords. + * @return A filled optional with the update action or an empty optional if the meta keywords + * values are identical. + */ + @Nonnull + public static Optional> buildSetMetaKeywordsUpdateAction( + @Nonnull final Category oldCategory, @Nonnull final CategoryDraft newCategory) { + return buildUpdateAction( + oldCategory.getMetaKeywords(), + newCategory.getMetaKeywords(), + () -> SetMetaKeywords.of(newCategory.getMetaKeywords())); + } - /** - * Compares the {@link LocalizedString} meta description of a {@link Category} and a {@link CategoryDraft} and - * returns an {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the - * {@link Category} and the {@link CategoryDraft} have the same meta description, then no update action is needed - * and hence an empty {@link Optional} is returned. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new meta description. - * @return A filled optional with the update action or an empty optional if the meta description values are - * identical. - */ - @Nonnull - public static Optional> buildSetMetaDescriptionUpdateAction( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory) { - return buildUpdateAction(oldCategory.getMetaDescription(), - newCategory.getMetaDescription(), () -> SetMetaDescription.of(newCategory.getMetaDescription())); - } + /** + * Compares the {@link LocalizedString} meta description of a {@link Category} and a {@link + * CategoryDraft} and returns an {@link UpdateAction}<{@link Category}> as a result in an + * {@link Optional}. If both the {@link Category} and the {@link CategoryDraft} have the same meta + * description, then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new meta description. + * @return A filled optional with the update action or an empty optional if the meta description + * values are identical. + */ + @Nonnull + public static Optional> buildSetMetaDescriptionUpdateAction( + @Nonnull final Category oldCategory, @Nonnull final CategoryDraft newCategory) { + return buildUpdateAction( + oldCategory.getMetaDescription(), + newCategory.getMetaDescription(), + () -> SetMetaDescription.of(newCategory.getMetaDescription())); + } - /** - * Compares the externalId values of a {@link Category} and a {@link CategoryDraft} and returns an - * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the {@link Category} and - * the {@link CategoryDraft} have the same externalId, then no update action is needed and hence an empty - * {@link Optional} is returned. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new externalId. - * @return A filled optional with the update action or an empty optional if the externalId values are identical. - */ - @Nonnull - public static Optional> buildSetExternalIdUpdateAction( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory) { - return buildUpdateAction(oldCategory.getExternalId(), - newCategory.getExternalId(), () -> SetExternalId.of(newCategory.getExternalId())); - } + /** + * Compares the externalId values of a {@link Category} and a {@link CategoryDraft} and returns an + * {@link UpdateAction}<{@link Category}> as a result in an {@link Optional}. If both the + * {@link Category} and the {@link CategoryDraft} have the same externalId, then no update action + * is needed and hence an empty {@link Optional} is returned. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new externalId. + * @return A filled optional with the update action or an empty optional if the externalId values + * are identical. + */ + @Nonnull + public static Optional> buildSetExternalIdUpdateAction( + @Nonnull final Category oldCategory, @Nonnull final CategoryDraft newCategory) { + return buildUpdateAction( + oldCategory.getExternalId(), + newCategory.getExternalId(), + () -> SetExternalId.of(newCategory.getExternalId())); + } - /** - * Compares the assets of a {@link Category} and a {@link CategoryDraft} and returns a list of - * {@link UpdateAction}<{@link Category}> as a result. If both the {@link Category} and - * the {@link CategoryDraft} have the identical assets, then no update action is needed and hence an empty - * {@link List} is returned. In case, the new category draft has a list of assets in which a duplicate key exists, - * the error callback is triggered and an empty list is returned. - * - * @param oldCategory the category which should be updated. - * @param newCategory the category draft where we get the new externalId. - * @param syncOptions the sync options with which a custom callback function is called in case errors exists - * while building assets custom field/type actions. - * @return A list with the update actions or an empty list if the assets are identical. - */ - @Nonnull - public static List> buildAssetsUpdateActions( - @Nonnull final Category oldCategory, - @Nonnull final CategoryDraft newCategory, - @Nonnull final CategorySyncOptions syncOptions) { + /** + * Compares the assets of a {@link Category} and a {@link CategoryDraft} and returns a list of + * {@link UpdateAction}<{@link Category}> as a result. If both the {@link Category} and the + * {@link CategoryDraft} have the identical assets, then no update action is needed and hence an + * empty {@link List} is returned. In case, the new category draft has a list of assets in which a + * duplicate key exists, the error callback is triggered and an empty list is returned. + * + * @param oldCategory the category which should be updated. + * @param newCategory the category draft where we get the new externalId. + * @param syncOptions the sync options with which a custom callback function is called in case + * errors exists while building assets custom field/type actions. + * @return A list with the update actions or an empty list if the assets are identical. + */ + @Nonnull + public static List> buildAssetsUpdateActions( + @Nonnull final Category oldCategory, + @Nonnull final CategoryDraft newCategory, + @Nonnull final CategorySyncOptions syncOptions) { - try { - return AssetsUpdateActionUtils.buildAssetsUpdateActions( - oldCategory, - newCategory, - oldCategory.getAssets(), - newCategory.getAssets(), - new CategoryAssetActionFactory(syncOptions), syncOptions); - } catch (final BuildUpdateActionException exception) { - syncOptions.applyErrorCallback(new SyncException(format("Failed to build update actions for the assets " - + "of the category with the key '%s'.", oldCategory.getKey()), exception), - oldCategory, newCategory, null); - return emptyList(); - } + try { + return AssetsUpdateActionUtils.buildAssetsUpdateActions( + oldCategory, + newCategory, + oldCategory.getAssets(), + newCategory.getAssets(), + new CategoryAssetActionFactory(syncOptions), + syncOptions); + } catch (final BuildUpdateActionException exception) { + syncOptions.applyErrorCallback( + new SyncException( + format( + "Failed to build update actions for the assets " + + "of the category with the key '%s'.", + oldCategory.getKey()), + exception), + oldCategory, + newCategory, + null); + return emptyList(); } + } - private CategoryUpdateActionUtils() { - } + private CategoryUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/channels/helpers/ChannelCustomActionBuilder.java b/src/main/java/com/commercetools/sync/channels/helpers/ChannelCustomActionBuilder.java index e0113eafc1..5259f2ce67 100644 --- a/src/main/java/com/commercetools/sync/channels/helpers/ChannelCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/channels/helpers/ChannelCustomActionBuilder.java @@ -6,37 +6,36 @@ import io.sphere.sdk.channels.commands.updateactions.SetCustomField; import io.sphere.sdk.channels.commands.updateactions.SetCustomType; import io.sphere.sdk.commands.UpdateAction; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; - public class ChannelCustomActionBuilder implements GenericCustomActionBuilder { - @Nonnull - @Override - public UpdateAction buildRemoveCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String objectId) { - return SetCustomType.ofRemoveType(); - } - - @Nonnull - @Override - - public UpdateAction buildSetCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String objectId, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomFieldAction(@Nullable final Integer variantId, - @Nullable final String objectId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - return SetCustomField.ofJson(customFieldName, customFieldValue); - } + @Nonnull + @Override + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String objectId) { + return SetCustomType.ofRemoveType(); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + return SetCustomField.ofJson(customFieldName, customFieldValue); + } } diff --git a/src/main/java/com/commercetools/sync/commons/BaseSync.java b/src/main/java/com/commercetools/sync/commons/BaseSync.java index eaae164b0a..f5a36145bc 100644 --- a/src/main/java/com/commercetools/sync/commons/BaseSync.java +++ b/src/main/java/com/commercetools/sync/commons/BaseSync.java @@ -2,123 +2,124 @@ import com.commercetools.sync.commons.helpers.BaseSyncStatistics; import io.sphere.sdk.client.ConcurrentModificationException; - -import javax.annotation.Nonnull; import java.util.List; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; - +import javax.annotation.Nonnull; public abstract class BaseSync { - protected final U statistics; - protected final V syncOptions; - - protected BaseSync(@Nonnull final U statistics, @Nonnull final V syncOptions) { - this.statistics = statistics; - this.syncOptions = syncOptions; - } + protected final U statistics; + protected final V syncOptions; - /** - * Given a list of resource (e.g. categories, products, etc..) drafts. This method compares each new resource in - * this list with it's corresponding old resource in a given CTP project, and in turn it either issues update - * actions on the existing resource if it exists or create it if it doesn't. - * - * @param resourceDrafts the list of new resources as drafts. - * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an instance of - * {@code U} which is a subclass of {@link BaseSyncStatistics} representing the {@code statistics} instance - * attribute of {@code this} {@link BaseSync}. - */ - protected abstract CompletionStage process(@Nonnull List resourceDrafts); + protected BaseSync(@Nonnull final U statistics, @Nonnull final V syncOptions) { + this.statistics = statistics; + this.syncOptions = syncOptions; + } + /** + * Given a list of resource (e.g. categories, products, etc..) drafts. This method compares each + * new resource in this list with it's corresponding old resource in a given CTP project, and in + * turn it either issues update actions on the existing resource if it exists or create it if it + * doesn't. + * + * @param resourceDrafts the list of new resources as drafts. + * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an + * instance of {@code U} which is a subclass of {@link BaseSyncStatistics} representing the + * {@code statistics} instance attribute of {@code this} {@link BaseSync}. + */ + protected abstract CompletionStage process(@Nonnull List resourceDrafts); - /** - * Given a list of resource (e.g. categories, products, etc..) drafts. This method compares each new resource in - * this list with it's corresponding old resource in a given CTP project, and in turn it either issues update - * actions on the existing resource if it exists or create it if it doesn't. - * - *

The time before and after the actual sync process starts is recorded in the {@link BaseSyncStatistics} - * container so that the total processing time is computed in the statistics. - * - * @param resourceDrafts the list of new resources as drafts. - * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an instance of - * {@code U} which is a subclass of {@link BaseSyncStatistics} representing the {@code statistics} instance - * attribute of {@code this} {@link BaseSync}. - */ - public CompletionStage sync(@Nonnull final List resourceDrafts) { - statistics.startTimer(); - return process(resourceDrafts).thenApply(resultingStatistics -> { - resultingStatistics.calculateProcessingTime(); - return resultingStatistics; - }); - } + /** + * Given a list of resource (e.g. categories, products, etc..) drafts. This method compares each + * new resource in this list with it's corresponding old resource in a given CTP project, and in + * turn it either issues update actions on the existing resource if it exists or create it if it + * doesn't. + * + *

The time before and after the actual sync process starts is recorded in the {@link + * BaseSyncStatistics} container so that the total processing time is computed in the statistics. + * + * @param resourceDrafts the list of new resources as drafts. + * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an + * instance of {@code U} which is a subclass of {@link BaseSyncStatistics} representing the + * {@code statistics} instance attribute of {@code this} {@link BaseSync}. + */ + public CompletionStage sync(@Nonnull final List resourceDrafts) { + statistics.startTimer(); + return process(resourceDrafts) + .thenApply( + resultingStatistics -> { + resultingStatistics.calculateProcessingTime(); + return resultingStatistics; + }); + } + /** + * Returns an instance of type U which is a subclass of {@link BaseSyncStatistics} containing all + * the stats of the sync process; which includes a report message, the total number of update, + * created, failed, processed resources and the processing time of the sync in different time + * units and in a human readable format. + * + * @return a statistics object for the sync process. + */ + @Nonnull + public U getStatistics() { + return statistics; + } - /** - * Returns an instance of type U which is a subclass of {@link BaseSyncStatistics} containing all the stats of the - * sync process; which includes a report message, the total number of update, created, failed, processed resources - * and the processing time of the sync in different time units and in a human readable format. - * - * @return a statistics object for the sync process. - */ - @Nonnull - public U getStatistics() { - return statistics; - } - - public V getSyncOptions() { - return syncOptions; - } + public V getSyncOptions() { + return syncOptions; + } - /** - * Given a list of resource (e.g. categories, products, etc.. batches represented by a - * {@link List}<{@link List}> of resources, this method recursively calls - * {@link #processBatch(List)} on each batch, then removes it, until there are no more batches, in other words, - * all batches have been synced. - * - * @param batches the batches of resources to sync. - * @param result in the first call of this recursive method, this result is normally a completed future, it - * used from within the method to recursively sync each batch once the previous batch has - * finished syncing. - * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an instance of - * {@link BaseSyncStatistics} representing the {@code statistics} of the sync process executed on the - * given list of batches. - */ - protected CompletionStage syncBatches(@Nonnull final List> batches, - @Nonnull final CompletionStage result) { - if (batches.isEmpty()) { - return result; - } - final List firstBatch = batches.remove(0); - return syncBatches(batches, result.thenCompose(subResult -> processBatch(firstBatch))); + /** + * Given a list of resource (e.g. categories, products, etc.. batches represented by a {@link + * List}<{@link List}> of resources, this method recursively calls {@link + * #processBatch(List)} on each batch, then removes it, until there are no more batches, in other + * words, all batches have been synced. + * + * @param batches the batches of resources to sync. + * @param result in the first call of this recursive method, this result is normally a completed + * future, it used from within the method to recursively sync each batch once the previous + * batch has finished syncing. + * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an + * instance of {@link BaseSyncStatistics} representing the {@code statistics} of the sync + * process executed on the given list of batches. + */ + protected CompletionStage syncBatches( + @Nonnull final List> batches, @Nonnull final CompletionStage result) { + if (batches.isEmpty()) { + return result; } + final List firstBatch = batches.remove(0); + return syncBatches(batches, result.thenCompose(subResult -> processBatch(firstBatch))); + } - protected abstract CompletionStage processBatch(@Nonnull List batch); + protected abstract CompletionStage processBatch(@Nonnull List batch); - /** - * This method checks if the supplied {@code sphereException} is an instance of - * {@link ConcurrentModificationException}. If it is, then it executes the supplied - * {@code onConcurrentModificationSupplier} {@link Supplier}. Otherwise, if it is - * not an instance of a {@link ConcurrentModificationException} then it executes - * the other {@code onOtherExceptionSupplier} {@link Supplier}. Regardless, which supplier is executed the results - * of either is the result of this method. - * - * @param sphereException the sphere exception to check if is - * {@link ConcurrentModificationException}. - * @param onConcurrentModificationSupplier the supplier to execute if the {@code sphereException} is a - * {@link ConcurrentModificationException}. - * @param onOtherExceptionSupplier the supplier to execute if the {@code sphereException} is not a - * {@link ConcurrentModificationException}. - * @param the type of the result of the suppliers and this method. - * @return the result of the executed supplier. - */ - protected static S executeSupplierIfConcurrentModificationException( - @Nonnull final Throwable sphereException, - @Nonnull final Supplier onConcurrentModificationSupplier, - @Nonnull final Supplier onOtherExceptionSupplier) { - final Throwable completionExceptionCause = sphereException.getCause(); - if (completionExceptionCause instanceof ConcurrentModificationException) { - return onConcurrentModificationSupplier.get(); - } - return onOtherExceptionSupplier.get(); + /** + * This method checks if the supplied {@code sphereException} is an instance of {@link + * ConcurrentModificationException}. If it is, then it executes the supplied {@code + * onConcurrentModificationSupplier} {@link Supplier}. Otherwise, if it is not an instance of a + * {@link ConcurrentModificationException} then it executes the other {@code + * onOtherExceptionSupplier} {@link Supplier}. Regardless, which supplier is executed the results + * of either is the result of this method. + * + * @param sphereException the sphere exception to check if is {@link + * ConcurrentModificationException}. + * @param onConcurrentModificationSupplier the supplier to execute if the {@code sphereException} + * is a {@link ConcurrentModificationException}. + * @param onOtherExceptionSupplier the supplier to execute if the {@code sphereException} is not a + * {@link ConcurrentModificationException}. + * @param the type of the result of the suppliers and this method. + * @return the result of the executed supplier. + */ + protected static S executeSupplierIfConcurrentModificationException( + @Nonnull final Throwable sphereException, + @Nonnull final Supplier onConcurrentModificationSupplier, + @Nonnull final Supplier onOtherExceptionSupplier) { + final Throwable completionExceptionCause = sphereException.getCause(); + if (completionExceptionCause instanceof ConcurrentModificationException) { + return onConcurrentModificationSupplier.get(); } + return onOtherExceptionSupplier.get(); + } } diff --git a/src/main/java/com/commercetools/sync/commons/BaseSyncOptions.java b/src/main/java/com/commercetools/sync/commons/BaseSyncOptions.java index 5b235194e4..0caae72e6a 100644 --- a/src/main/java/com/commercetools/sync/commons/BaseSyncOptions.java +++ b/src/main/java/com/commercetools/sync/commons/BaseSyncOptions.java @@ -1,251 +1,277 @@ package com.commercetools.sync.commons; +import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; +import static java.util.Optional.ofNullable; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; import com.commercetools.sync.commons.utils.TriFunction; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; -import static java.util.Optional.ofNullable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** - * @param Resource Draft (e.g. {@link io.sphere.sdk.products.ProductDraft}, - * {@link io.sphere.sdk.categories.CategoryDraft}, etc.. - * @param Resource (e.g. {@link io.sphere.sdk.products.Product}, {@link io.sphere.sdk.categories.Category}, etc.. + * @param Resource Draft (e.g. {@link io.sphere.sdk.products.ProductDraft}, {@link + * io.sphere.sdk.categories.CategoryDraft}, etc.. + * @param Resource (e.g. {@link io.sphere.sdk.products.Product}, {@link + * io.sphere.sdk.categories.Category}, etc.. */ public class BaseSyncOptions { - private final SphereClient ctpClient; - private final QuadConsumer, Optional, List>> - errorCallback; - private final TriConsumer, Optional> warningCallback; - private int batchSize; - private final TriFunction>, V, U, List>> beforeUpdateCallback; - private final Function beforeCreateCallback; - private long cacheSize; - - protected BaseSyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, List>> - errorCallback, - @Nullable final TriConsumer, Optional> warningCallback, - final int batchSize, - @Nullable final TriFunction>, V, U, List>> - beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, final long cacheSize) { - this.ctpClient = ctpClient; - this.errorCallback = errorCallback; - this.batchSize = batchSize; - this.warningCallback = warningCallback; - this.beforeUpdateCallback = beforeUpdateCallback; - this.beforeCreateCallback = beforeCreateCallback; - this.cacheSize = cacheSize; - } + private final SphereClient ctpClient; + private final QuadConsumer, Optional, List>> + errorCallback; + private final TriConsumer, Optional> warningCallback; + private int batchSize; + private final TriFunction>, V, U, List>> + beforeUpdateCallback; + private final Function beforeCreateCallback; + private long cacheSize; - /** - * Returns the {@link SphereClient} responsible for interaction with the target CTP project. - * - * @return the {@link SphereClient} responsible for interaction with the target CTP project. - */ - public SphereClient getCtpClient() { - return ctpClient; - } + protected BaseSyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer, Optional, List>> + errorCallback, + @Nullable final TriConsumer, Optional> warningCallback, + final int batchSize, + @Nullable + final TriFunction>, V, U, List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { + this.ctpClient = ctpClient; + this.errorCallback = errorCallback; + this.batchSize = batchSize; + this.warningCallback = warningCallback; + this.beforeUpdateCallback = beforeUpdateCallback; + this.beforeCreateCallback = beforeCreateCallback; + this.cacheSize = cacheSize; + } - /** - * Returns the {@code errorCallback} {@link QuadConsumer}<{@link SyncException}, - * {@link Optional}<{@code V}>, {@link Optional}<{@code U}>, - * {@link List}<{@link UpdateAction}<{@code U}>>> function set to - * {@code this} {@link BaseSyncOptions}. It represents the callback that is called whenever an event occurs during - * the sync process that represents an error. - * - * @return the {@code errorCallback} {@link QuadConsumer}<{@link SyncException}, - * {@link Optional}<{@code V}>, {@link Optional}<{@code U}>, - * {@link List}<{@link UpdateAction}<{@code U}>> function set to - * {@code this} {@link BaseSyncOptions} - */ - @Nullable - public QuadConsumer, Optional, List>> getErrorCallback() { - return errorCallback; - } + /** + * Returns the {@link SphereClient} responsible for interaction with the target CTP project. + * + * @return the {@link SphereClient} responsible for interaction with the target CTP project. + */ + public SphereClient getCtpClient() { + return ctpClient; + } - /** - * Returns the {@code warningCallback} {@link TriConsumer}<{@link SyncException}, - * {@link Optional}<{@code V}>, {@link Optional}<{@code U}>> function set to {@code this} - * {@link BaseSyncOptions}. It represents the callback that is called whenever an event occurs - * during the sync process that represents a warning. - * - * @return the {@code warningCallback} {@link TriConsumer}<{@link SyncException}, - * {@link Optional}<{@code V}>, {@link Optional}<{@code U}>> function set to {@code this} - * {@link BaseSyncOptions} - */ - @Nullable - public TriConsumer, Optional> getWarningCallback() { - return warningCallback; - } + /** + * Returns the {@code errorCallback} {@link QuadConsumer}<{@link SyncException}, {@link + * Optional}<{@code V}>, {@link Optional}<{@code U}>, {@link List}<{@link + * UpdateAction}<{@code U}>>> function set to {@code this} {@link BaseSyncOptions}. It + * represents the callback that is called whenever an event occurs during the sync process that + * represents an error. + * + * @return the {@code errorCallback} {@link QuadConsumer}<{@link SyncException}, {@link + * Optional}<{@code V}>, {@link Optional}<{@code U}>, {@link List}<{@link + * UpdateAction}<{@code U}>> function set to {@code this} {@link BaseSyncOptions} + */ + @Nullable + public QuadConsumer, Optional, List>> + getErrorCallback() { + return errorCallback; + } - /** - * Given an {@code exception}, {@code oldResource} and {@code newResourceDraft} this method calls the - * {@code warningCallback} function which is set to {@code this} instance of the - * {@link BaseSyncOptions}. If there {@code warningCallback} is null, this - * method does nothing. - * - * @param exception the exception to supply to the {@code warningCallback} function. - * @param oldResource the old resource that is being compared to the new draft. - * @param newResourceDraft the new resource draft that is being compared to the old resource. - */ - public void applyWarningCallback(@Nonnull final SyncException exception, @Nullable final U oldResource, - @Nullable final V newResourceDraft) { - if (this.warningCallback != null) { - this.warningCallback.accept(exception, Optional.ofNullable(newResourceDraft), - Optional.ofNullable(oldResource)); - } - } + /** + * Returns the {@code warningCallback} {@link TriConsumer}<{@link SyncException}, {@link + * Optional}<{@code V}>, {@link Optional}<{@code U}>> function set to {@code this} + * {@link BaseSyncOptions}. It represents the callback that is called whenever an event occurs + * during the sync process that represents a warning. + * + * @return the {@code warningCallback} {@link TriConsumer}<{@link SyncException}, {@link + * Optional}<{@code V}>, {@link Optional}<{@code U}>> function set to {@code + * this} {@link BaseSyncOptions} + */ + @Nullable + public TriConsumer, Optional> getWarningCallback() { + return warningCallback; + } - /** - * Given an {@code errorMessage} and an {@code exception}, this method calls the {@code errorCallback} function - * which is set to {@code this} instance of the {@link BaseSyncOptions}. If there {@code errorCallback} is null, - * this method does nothing. - * - * @param exception {@link Throwable} instance to supply as first param to the {@code errorCallback} - * function. - * @param oldResource the old resource that is being compared to the new draft. - * @param newResourceDraft the new resource draft that is being compared to the old resource. - * @param updateActions the list of update actions. - */ - public void applyErrorCallback(@Nonnull final SyncException exception, @Nullable final U oldResource, - @Nullable final V newResourceDraft, - @Nullable final List> updateActions) { - if (this.errorCallback != null) { - this.errorCallback.accept(exception, Optional.ofNullable(newResourceDraft), - Optional.ofNullable(oldResource), updateActions); - } + /** + * Given an {@code exception}, {@code oldResource} and {@code newResourceDraft} this method calls + * the {@code warningCallback} function which is set to {@code this} instance of the {@link + * BaseSyncOptions}. If there {@code warningCallback} is null, this method does nothing. + * + * @param exception the exception to supply to the {@code warningCallback} function. + * @param oldResource the old resource that is being compared to the new draft. + * @param newResourceDraft the new resource draft that is being compared to the old resource. + */ + public void applyWarningCallback( + @Nonnull final SyncException exception, + @Nullable final U oldResource, + @Nullable final V newResourceDraft) { + if (this.warningCallback != null) { + this.warningCallback.accept( + exception, Optional.ofNullable(newResourceDraft), Optional.ofNullable(oldResource)); } + } - /** - * @param syncException {@link Throwable} instance to supply as first param to the {@code errorCallback} function. - * @see #applyErrorCallback(SyncException exception, Object oldResource, Object newResource, List updateActions) - */ - public void applyErrorCallback(@Nonnull final SyncException syncException) { - applyErrorCallback(syncException, null, null, null); + /** + * Given an {@code errorMessage} and an {@code exception}, this method calls the {@code + * errorCallback} function which is set to {@code this} instance of the {@link BaseSyncOptions}. + * If there {@code errorCallback} is null, this method does nothing. + * + * @param exception {@link Throwable} instance to supply as first param to the {@code + * errorCallback} function. + * @param oldResource the old resource that is being compared to the new draft. + * @param newResourceDraft the new resource draft that is being compared to the old resource. + * @param updateActions the list of update actions. + */ + public void applyErrorCallback( + @Nonnull final SyncException exception, + @Nullable final U oldResource, + @Nullable final V newResourceDraft, + @Nullable final List> updateActions) { + if (this.errorCallback != null) { + this.errorCallback.accept( + exception, + Optional.ofNullable(newResourceDraft), + Optional.ofNullable(oldResource), + updateActions); } + } - /** - * @param errorMessage the error message to supply as part of first param to the {@code errorCallback} function. - * @see #applyErrorCallback(SyncException exception, Object oldResource, Object newResource, List updateActions) - */ - public void applyErrorCallback(@Nonnull final String errorMessage) { - applyErrorCallback(new SyncException(errorMessage), null, null, null); - } + /** + * @param syncException {@link Throwable} instance to supply as first param to the {@code + * errorCallback} function. + * @see #applyErrorCallback(SyncException exception, Object oldResource, Object newResource, List + * updateActions) + */ + public void applyErrorCallback(@Nonnull final SyncException syncException) { + applyErrorCallback(syncException, null, null, null); + } - /** - * Gets the batch size used in the sync process. During the sync there is a need for fetching existing - * resources so that they can be compared with the new resource drafts. That's why the input is sliced into - * batches and then processed. It allows to reduce the query size for fetching all resources processed in one - * batch. - * E.g. value of 30 means that 30 entries from input list would be accumulated and one API call will be performed - * for fetching entries responding to them. Then comparison and sync are performed. - * - *

This batch size is set to 30 by default. - * - * @return option that indicates capacity of batch of resources to process. - */ - public int getBatchSize() { - return batchSize; - } + /** + * @param errorMessage the error message to supply as part of first param to the {@code + * errorCallback} function. + * @see #applyErrorCallback(SyncException exception, Object oldResource, Object newResource, List + * updateActions) + */ + public void applyErrorCallback(@Nonnull final String errorMessage) { + applyErrorCallback(new SyncException(errorMessage), null, null, null); + } - /** - * Gets the cache size used in the sync process. To increase performance during the sync some resource keys / ids - * are cached which are required for resolving references. To keep the cache performant old entries are evicted - * when a certain size is reached. - * - *

This cache size is set to 100.000 by default. - * - * @return option that indicates capacity of cache of resource keys - */ - public long getCacheSize() { - return cacheSize; - } + /** + * Gets the batch size used in the sync process. During the sync there is a need for fetching + * existing resources so that they can be compared with the new resource drafts. That's why the + * input is sliced into batches and then processed. It allows to reduce the query size for + * fetching all resources processed in one batch. E.g. value of 30 means that 30 entries from + * input list would be accumulated and one API call will be performed for fetching entries + * responding to them. Then comparison and sync are performed. + * + *

This batch size is set to 30 by default. + * + * @return option that indicates capacity of batch of resources to process. + */ + public int getBatchSize() { + return batchSize; + } - /** - * Returns the {@code beforeUpdateCallback} {@link TriFunction}<{@link List}<{@link UpdateAction}< - * {@code U}>>, {@code V}, {@code U}, {@link List}<{@link UpdateAction}<{@code U}>>> function - * set to {@code this} {@link BaseSyncOptions}. It represents a callback function which is applied (if set) on the - * generated list of update actions to produce a resultant list after the filter function has been applied. - * - * @return the {@code beforeUpdateCallback} {@link TriFunction}<{@link List}<{@link UpdateAction}< - * {@code U}>>, {@code V}, {@code U}, {@link List}<{@link UpdateAction}<{@code U}>>> - * function set to {@code this} {@link BaseSyncOptions}. - */ - @Nullable - public TriFunction>, V, U, List>> getBeforeUpdateCallback() { - return beforeUpdateCallback; - } + /** + * Gets the cache size used in the sync process. To increase performance during the sync some + * resource keys / ids are cached which are required for resolving references. To keep the cache + * performant old entries are evicted when a certain size is reached. + * + *

This cache size is set to 100.000 by default. + * + * @return option that indicates capacity of cache of resource keys + */ + public long getCacheSize() { + return cacheSize; + } - /** - * Returns the {@code beforeCreateCallback} {@link Function}<{@code V}, {@link Optional}<{@code V}>> - * function set to {@code this} {@link BaseSyncOptions}. It represents a callback function which is applied (if set) - * on the resource draft of type {@code V} before it is created to produce a resultant draft after the filter - * function has been applied. - * - * @return the {@code beforeUpdateCallback} {@link Function}<{@code V}, {@link Optional}<{@code V}>> - * function set to {@code this} {@link BaseSyncOptions}. - */ - @Nullable - public Function getBeforeCreateCallback() { - return beforeCreateCallback; - } + /** + * Returns the {@code beforeUpdateCallback} {@link TriFunction}<{@link List}<{@link + * UpdateAction}< {@code U}>>, {@code V}, {@code U}, {@link List}<{@link + * UpdateAction}<{@code U}>>> function set to {@code this} {@link BaseSyncOptions}. It + * represents a callback function which is applied (if set) on the generated list of update + * actions to produce a resultant list after the filter function has been applied. + * + * @return the {@code beforeUpdateCallback} {@link TriFunction}<{@link List}<{@link + * UpdateAction}< {@code U}>>, {@code V}, {@code U}, {@link List}<{@link + * UpdateAction}<{@code U}>>> function set to {@code this} {@link + * BaseSyncOptions}. + */ + @Nullable + public TriFunction>, V, U, List>> getBeforeUpdateCallback() { + return beforeUpdateCallback; + } - /** - * Given a {@link List} of {@link UpdateAction}, a new resource draft of type {@code V} and the old existing - * resource of the type {@code U}, this method applies the {@code beforeUpdateCallback} function which is set to - * {@code this} instance of the {@link BaseSyncOptions} and returns the result. If the {@code beforeUpdateCallback} - * is null or {@code updateActions} is empty, this method does nothing to the supplied list of {@code updateActions} - * and returns the same list. If the result of the callback is null, an empty list is returned. - * - * @param updateActions the list of update actions to apply the {@code beforeUpdateCallback} function on. - * @param newResourceDraft the new resource draft that is being compared to the old resource. - * @param oldResource the old resource that is being compared to the new draft. - * @return a list of update actions after applying the {@code beforeUpdateCallback} function on. If the - * {@code beforeUpdateCallback} function is null or {@code updateActions} is empty, the supplied list of - * {@code updateActions} is returned as is. If the return of the callback is null, an empty list is - * returned. - */ - @Nonnull - public List> applyBeforeUpdateCallback(@Nonnull final List> updateActions, - @Nonnull final V newResourceDraft, - @Nonnull final U oldResource) { - - return ofNullable(beforeUpdateCallback) - .filter(callback -> !updateActions.isEmpty()) - .map(filteredCallback -> emptyIfNull(filteredCallback.apply(updateActions, newResourceDraft, oldResource))) - .orElse(updateActions); - } + /** + * Returns the {@code beforeCreateCallback} {@link Function}<{@code V}, {@link + * Optional}<{@code V}>> function set to {@code this} {@link BaseSyncOptions}. It + * represents a callback function which is applied (if set) on the resource draft of type {@code + * V} before it is created to produce a resultant draft after the filter function has been + * applied. + * + * @return the {@code beforeUpdateCallback} {@link Function}<{@code V}, {@link + * Optional}<{@code V}>> function set to {@code this} {@link BaseSyncOptions}. + */ + @Nullable + public Function getBeforeCreateCallback() { + return beforeCreateCallback; + } - /** - * Given a new resource draft of type {@code V} this method applies the {@code beforeCreateCallback} function - * which is set to {@code this} instance of the {@link BaseSyncOptions} and returns the result. - *

    - *
  • If the {@code beforeCreateCallback} is null, this method does nothing to the supplied resource draft and - * returns it as is, wrapped in an optional.
  • - *
  • If the return of {@code beforeCreateCallback} is null, an empty optional is returned.
  • - *
  • Otherwise, the result of the {@code beforeCreateCallback} is returned in the optional.
  • - *
- * - * @param newResourceDraft the new resource draft that should be created. - * @return an optional containing the resultant resource draft after applying the {@code beforeCreateCallback} - * function on. If the {@code beforeCreateCallback} function is null, the supplied resource draft is - * returned as is, wrapped in an optional. - */ - @Nonnull - public Optional applyBeforeCreateCallback(@Nonnull final V newResourceDraft) { - return ofNullable( - beforeCreateCallback != null ? beforeCreateCallback.apply(newResourceDraft) : newResourceDraft); - } + /** + * Given a {@link List} of {@link UpdateAction}, a new resource draft of type {@code V} and the + * old existing resource of the type {@code U}, this method applies the {@code + * beforeUpdateCallback} function which is set to {@code this} instance of the {@link + * BaseSyncOptions} and returns the result. If the {@code beforeUpdateCallback} is null or {@code + * updateActions} is empty, this method does nothing to the supplied list of {@code updateActions} + * and returns the same list. If the result of the callback is null, an empty list is returned. + * + * @param updateActions the list of update actions to apply the {@code beforeUpdateCallback} + * function on. + * @param newResourceDraft the new resource draft that is being compared to the old resource. + * @param oldResource the old resource that is being compared to the new draft. + * @return a list of update actions after applying the {@code beforeUpdateCallback} function on. + * If the {@code beforeUpdateCallback} function is null or {@code updateActions} is empty, the + * supplied list of {@code updateActions} is returned as is. If the return of the callback is + * null, an empty list is returned. + */ + @Nonnull + public List> applyBeforeUpdateCallback( + @Nonnull final List> updateActions, + @Nonnull final V newResourceDraft, + @Nonnull final U oldResource) { + + return ofNullable(beforeUpdateCallback) + .filter(callback -> !updateActions.isEmpty()) + .map( + filteredCallback -> + emptyIfNull(filteredCallback.apply(updateActions, newResourceDraft, oldResource))) + .orElse(updateActions); + } + + /** + * Given a new resource draft of type {@code V} this method applies the {@code + * beforeCreateCallback} function which is set to {@code this} instance of the {@link + * BaseSyncOptions} and returns the result. + * + *
    + *
  • If the {@code beforeCreateCallback} is null, this method does nothing to the supplied + * resource draft and returns it as is, wrapped in an optional. + *
  • If the return of {@code beforeCreateCallback} is null, an empty optional is returned. + *
  • Otherwise, the result of the {@code beforeCreateCallback} is returned in the optional. + *
+ * + * @param newResourceDraft the new resource draft that should be created. + * @return an optional containing the resultant resource draft after applying the {@code + * beforeCreateCallback} function on. If the {@code beforeCreateCallback} function is null, + * the supplied resource draft is returned as is, wrapped in an optional. + */ + @Nonnull + public Optional applyBeforeCreateCallback(@Nonnull final V newResourceDraft) { + return ofNullable( + beforeCreateCallback != null + ? beforeCreateCallback.apply(newResourceDraft) + : newResourceDraft); + } } diff --git a/src/main/java/com/commercetools/sync/commons/BaseSyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/commons/BaseSyncOptionsBuilder.java index dd52eb81eb..4134c48f9f 100644 --- a/src/main/java/com/commercetools/sync/commons/BaseSyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/commons/BaseSyncOptionsBuilder.java @@ -6,137 +6,146 @@ import com.commercetools.sync.commons.utils.TriFunction; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; -public abstract class BaseSyncOptionsBuilder, - S extends BaseSyncOptions, U, V> { +public abstract class BaseSyncOptionsBuilder< + T extends BaseSyncOptionsBuilder, S extends BaseSyncOptions, U, V> { - protected SphereClient ctpClient; - protected QuadConsumer, Optional, List>> errorCallback; - protected TriConsumer, Optional> warningCallback; - protected int batchSize = 30; - protected TriFunction>, V, U, List>> beforeUpdateCallback; - protected Function beforeCreateCallback; - protected long cacheSize = 10_000; + protected SphereClient ctpClient; + protected QuadConsumer, Optional, List>> + errorCallback; + protected TriConsumer, Optional> warningCallback; + protected int batchSize = 30; + protected TriFunction>, V, U, List>> beforeUpdateCallback; + protected Function beforeCreateCallback; + protected long cacheSize = 10_000; - /** - * Sets the {@code errorCallback} function of the sync module. This callback will be called whenever an event occurs - * that leads to an error alert from the sync process. - * - * @param errorCallback the new value to set to the error callback. - * @return {@code this} instance of {@link BaseSyncOptionsBuilder} - */ - public T errorCallback(@Nonnull final QuadConsumer, Optional, - List>> errorCallback) { - this.errorCallback = errorCallback; - return getThis(); - } + /** + * Sets the {@code errorCallback} function of the sync module. This callback will be called + * whenever an event occurs that leads to an error alert from the sync process. + * + * @param errorCallback the new value to set to the error callback. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public T errorCallback( + @Nonnull + final QuadConsumer, Optional, List>> + errorCallback) { + this.errorCallback = errorCallback; + return getThis(); + } - /** - * Sets the {@code warningCallback} function of the sync module. This callback will be called whenever an event - * occurs that leads to a warning alert from the sync process. - * - * @param warningCallback the new value to set to the warning callback. - * @return {@code this} instance of {@link BaseSyncOptionsBuilder} - */ - public T warningCallback(@Nonnull final TriConsumer, Optional> warningCallback) { - this.warningCallback = warningCallback; - return getThis(); - } + /** + * Sets the {@code warningCallback} function of the sync module. This callback will be called + * whenever an event occurs that leads to a warning alert from the sync process. + * + * @param warningCallback the new value to set to the warning callback. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public T warningCallback( + @Nonnull final TriConsumer, Optional> warningCallback) { + this.warningCallback = warningCallback; + return getThis(); + } - /** - * Set option that indicates batch size for sync process. During the sync there is a need for fetching existing - * resources so that they can be compared with the new resource drafts. That's why the input is sliced into - * batches and then processed. It allows to reduce the query size for fetching all resources processed in one - * batch. - * E.g. value of 30 means that 30 entries from input list would be accumulated and one API call will be performed - * for fetching entries responding to them. Then comparision and sync are performed. - * - *

This batch size is set to 30 by default. - * - * @param batchSize int that indicates batch size of resources to process. Has to be positive - * or else will be ignored and default value of 30 would be used. - * @return {@code this} instance of {@link BaseSyncOptionsBuilder} - */ - public T batchSize(final int batchSize) { - if (batchSize > 0) { - this.batchSize = batchSize; - } - return getThis(); + /** + * Set option that indicates batch size for sync process. During the sync there is a need for + * fetching existing resources so that they can be compared with the new resource drafts. That's + * why the input is sliced into batches and then processed. It allows to reduce the query size for + * fetching all resources processed in one batch. E.g. value of 30 means that 30 entries from + * input list would be accumulated and one API call will be performed for fetching entries + * responding to them. Then comparision and sync are performed. + * + *

This batch size is set to 30 by default. + * + * @param batchSize int that indicates batch size of resources to process. Has to be positive or + * else will be ignored and default value of 30 would be used. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public T batchSize(final int batchSize) { + if (batchSize > 0) { + this.batchSize = batchSize; } + return getThis(); + } - /** - * Sets the cache size that indicates the key to id cache size of the sync process. To increase performance - * during the sync some resource keys mapped to ids are cached which are required for resolving references. To keep - * the cache performant outdated entries are evicted when a certain size is reached. - * - *

Note: This cache size is set to 10.000 by default. - * - * @param cacheSize a long number value that indicates cache size of the key to id cache used for reference - * resolution. Has to be positive or else will be ignored and default value of 10.000 would be - * used. - * @return {@code this} instance of {@link BaseSyncOptionsBuilder} - */ - public T cacheSize(final long cacheSize) { - if (cacheSize > 0) { - this.cacheSize = cacheSize; - } - return getThis(); + /** + * Sets the cache size that indicates the key to id cache size of the sync process. To increase + * performance during the sync some resource keys mapped to ids are cached which are required for + * resolving references. To keep the cache performant outdated entries are evicted when a certain + * size is reached. + * + *

Note: This cache size is set to 10.000 by default. + * + * @param cacheSize a long number value that indicates cache size of the key to id cache used for + * reference resolution. Has to be positive or else will be ignored and default value of + * 10.000 would be used. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public T cacheSize(final long cacheSize) { + if (cacheSize > 0) { + this.cacheSize = cacheSize; } + return getThis(); + } - /** - * Sets the beforeUpdateCallback {@link TriFunction} which can be applied on the supplied list of update actions - * generated from comparing an old resource of type {@code U} (e.g. {@link io.sphere.sdk.products.Product}) to a new - * draft of type {@code V} (e.g. {@link io.sphere.sdk.products.ProductDraft}). It results in a resultant list after - * the specified {@link TriFunction} {@code beforeUpdateCallback} function has been applied. This can be used to - * intercept the sync process before issuing an update request and to be able to manipulate the update actions. - * Note: Specifying a callback that returns a {@code null} value or empty list will skip issuing the update - * request. - * - * @param beforeUpdateCallback function which can be applied on generated list of update actions. - * @return {@code this} instance of {@link BaseSyncOptionsBuilder} - */ - public T beforeUpdateCallback(@Nonnull final TriFunction>, V, U, List>> - beforeUpdateCallback) { - this.beforeUpdateCallback = beforeUpdateCallback; - return getThis(); - } + /** + * Sets the beforeUpdateCallback {@link TriFunction} which can be applied on the supplied list of + * update actions generated from comparing an old resource of type {@code U} (e.g. {@link + * io.sphere.sdk.products.Product}) to a new draft of type {@code V} (e.g. {@link + * io.sphere.sdk.products.ProductDraft}). It results in a resultant list after the specified + * {@link TriFunction} {@code beforeUpdateCallback} function has been applied. This can be used to + * intercept the sync process before issuing an update request and to be able to manipulate the + * update actions. Note: Specifying a callback that returns a {@code null} value or empty + * list will skip issuing the update request. + * + * @param beforeUpdateCallback function which can be applied on generated list of update actions. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public T beforeUpdateCallback( + @Nonnull + final TriFunction>, V, U, List>> + beforeUpdateCallback) { + this.beforeUpdateCallback = beforeUpdateCallback; + return getThis(); + } - /** - * Sets the beforeCreateCallback {@link Function} which can be applied on a new resource draft of type {@code V} - * (e.g. {@link io.sphere.sdk.products.ProductDraft}) before it's created by the sync. It results in a resource - * draft of the same type which is the result of the application of the specified {@link Function} - * {@code beforeCreateCallback} function. This can be used to intercept the sync process before creating the - * resource draft and to be able to manipulate it. Note: Specifying a callback that returns a {@code null} - * value will skip draft creation. - * - * @param beforeCreateCallback function which can be applied on a new draft before it's created by the sync. - * @return {@code this} instance of {@link BaseSyncOptionsBuilder} - */ - public T beforeCreateCallback(@Nonnull final Function beforeCreateCallback) { - this.beforeCreateCallback = beforeCreateCallback; - return getThis(); - } + /** + * Sets the beforeCreateCallback {@link Function} which can be applied on a new resource draft of + * type {@code V} (e.g. {@link io.sphere.sdk.products.ProductDraft}) before it's created by the + * sync. It results in a resource draft of the same type which is the result of the application of + * the specified {@link Function} {@code beforeCreateCallback} function. This can be used to + * intercept the sync process before creating the resource draft and to be able to manipulate it. + * Note: Specifying a callback that returns a {@code null} value will skip draft creation. + * + * @param beforeCreateCallback function which can be applied on a new draft before it's created by + * the sync. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public T beforeCreateCallback(@Nonnull final Function beforeCreateCallback) { + this.beforeCreateCallback = beforeCreateCallback; + return getThis(); + } - /** - * Creates new instance of {@code S} which extends {@link BaseSyncOptions} enriched with all attributes provided to - * {@code this} builder. - * - * @return new instance of S which extends {@link BaseSyncOptions} - */ - protected abstract S build(); + /** + * Creates new instance of {@code S} which extends {@link BaseSyncOptions} enriched with all + * attributes provided to {@code this} builder. + * + * @return new instance of S which extends {@link BaseSyncOptions} + */ + protected abstract S build(); - /** - * Returns {@code this} instance of {@code T}, which extends {@link BaseSyncOptionsBuilder}. The purpose of this - * method is to make sure that {@code this} is an instance of a class which extends {@link BaseSyncOptionsBuilder} - * in order to be used in the generic methods of the class. Otherwise, without this method, the methods above would - * need to cast {@code this to T} which could lead to a runtime error of the class was extended in a wrong way. - * - * @return an instance of the class that overrides this method. - */ - protected abstract T getThis(); + /** + * Returns {@code this} instance of {@code T}, which extends {@link BaseSyncOptionsBuilder}. The + * purpose of this method is to make sure that {@code this} is an instance of a class which + * extends {@link BaseSyncOptionsBuilder} in order to be used in the generic methods of the class. + * Otherwise, without this method, the methods above would need to cast {@code this to T} which + * could lead to a runtime error of the class was extended in a wrong way. + * + * @return an instance of the class that overrides this method. + */ + protected abstract T getThis(); } diff --git a/src/main/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjects.java b/src/main/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjects.java index 2bfcfbbac0..492c999db1 100644 --- a/src/main/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjects.java +++ b/src/main/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjects.java @@ -1,5 +1,8 @@ package com.commercetools.sync.commons; +import static com.commercetools.sync.commons.utils.CtpQueryUtils.queryAll; +import static java.lang.String.format; + import com.commercetools.sync.commons.models.FetchCustomObjectsGraphQlRequest; import com.commercetools.sync.commons.models.ResourceKeyId; import com.commercetools.sync.services.impl.UnresolvedReferencesServiceImpl; @@ -9,9 +12,6 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.commands.CustomObjectDeleteCommand; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.List; @@ -21,196 +21,206 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; - -import static com.commercetools.sync.commons.utils.CtpQueryUtils.queryAll; -import static java.lang.String.format; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class CleanupUnresolvedReferenceCustomObjects { - private final SphereClient sphereClient; - private final Statistics statistics; - private int pageSize = 500; - private Consumer errorCallback; - - private CleanupUnresolvedReferenceCustomObjects(@Nonnull final SphereClient sphereClient) { - this.sphereClient = sphereClient; - this.statistics = new Statistics(); - } - - /** - * Creates new instance of {@link CleanupUnresolvedReferenceCustomObjects} which has the functionality to run - * cleanup helpers. - * - * @param sphereClient the client object. - * @return new instance of {@link CleanupUnresolvedReferenceCustomObjects} - */ - public static CleanupUnresolvedReferenceCustomObjects of(@Nonnull final SphereClient sphereClient) { - return new CleanupUnresolvedReferenceCustomObjects(sphereClient); - } - - /** - * Sets the {@code errorCallback} function of the cleanup. This callback will be called whenever an event occurs - * that leads to an error alert from the cleanup process. - * - * @param errorCallback the new value to set to the error callback. - * @return {@code this} instance of {@link BaseSyncOptionsBuilder} - */ - public CleanupUnresolvedReferenceCustomObjects errorCallback(@Nonnull final Consumer errorCallback) { - this.errorCallback = errorCallback; - return this; - } - - /** - * Given an {@code exception}, this method calls the {@code errorCallback} function. - * If {@code errorCallback} is null, this method does nothing. - * - * @param exception {@link Throwable} instance to supply as first param to the {@code errorCallback} - * function. - */ - private void applyErrorCallback(@Nonnull final Throwable exception) { - if (this.errorCallback != null) { - this.errorCallback.accept(exception); - } - } - - /** - * Configures the pageSize (limit), the maximum number of results to return from the grapqhl query, - * which can be set using the limit query parameter. The default page size is 500. - * - *

NOTE: Changing this value might negatively impact the performance of the cleanup and must be tested properly. - * - * @param pageSize int that indicates batch size of resources to process. - * @return {@code this} instance of {@link BaseSyncOptionsBuilder} - */ - public CleanupUnresolvedReferenceCustomObjects pageSize(final int pageSize) { - this.pageSize = pageSize; - return this; + private final SphereClient sphereClient; + private final Statistics statistics; + private int pageSize = 500; + private Consumer errorCallback; + + private CleanupUnresolvedReferenceCustomObjects(@Nonnull final SphereClient sphereClient) { + this.sphereClient = sphereClient; + this.statistics = new Statistics(); + } + + /** + * Creates new instance of {@link CleanupUnresolvedReferenceCustomObjects} which has the + * functionality to run cleanup helpers. + * + * @param sphereClient the client object. + * @return new instance of {@link CleanupUnresolvedReferenceCustomObjects} + */ + public static CleanupUnresolvedReferenceCustomObjects of( + @Nonnull final SphereClient sphereClient) { + return new CleanupUnresolvedReferenceCustomObjects(sphereClient); + } + + /** + * Sets the {@code errorCallback} function of the cleanup. This callback will be called whenever + * an event occurs that leads to an error alert from the cleanup process. + * + * @param errorCallback the new value to set to the error callback. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public CleanupUnresolvedReferenceCustomObjects errorCallback( + @Nonnull final Consumer errorCallback) { + this.errorCallback = errorCallback; + return this; + } + + /** + * Given an {@code exception}, this method calls the {@code errorCallback} function. If {@code + * errorCallback} is null, this method does nothing. + * + * @param exception {@link Throwable} instance to supply as first param to the {@code + * errorCallback} function. + */ + private void applyErrorCallback(@Nonnull final Throwable exception) { + if (this.errorCallback != null) { + this.errorCallback.accept(exception); } + } + + /** + * Configures the pageSize (limit), the maximum number of results to return from the grapqhl + * query, which can be set using the limit query parameter. The default page size is 500. + * + *

NOTE: Changing this value might negatively impact the performance of the cleanup and must be + * tested properly. + * + * @param pageSize int that indicates batch size of resources to process. + * @return {@code this} instance of {@link BaseSyncOptionsBuilder} + */ + public CleanupUnresolvedReferenceCustomObjects pageSize(final int pageSize) { + this.pageSize = pageSize; + return this; + } + + /** + * Deletes the unresolved reference custom objects persisted by commercetools-sync-java library to + * handle reference resolution. The custom objects will be deleted if it hasn't been modified for + * the specified amount of days as given {@code deleteDaysAfterLastModification}. + * + *

Note: Keeping the unresolved references forever can negatively influence the performance of + * your project, so deleting unused data ensures the best performance for your project. + * + * @param deleteDaysAfterLastModification Days to query. The custom objects will be deleted if it + * hasn't been modified for the specified amount of days. + * @return an instance of {@link CompletableFuture}<{@link + * CleanupUnresolvedReferenceCustomObjects.Statistics} > which contains the processing + * time, the total number of custom objects that were deleted and failed to delete, and a + * proper summary message of the statistics. + */ + public CompletableFuture cleanup(final int deleteDaysAfterLastModification) { + + return CompletableFuture.allOf( + cleanupUnresolvedProductReferences(deleteDaysAfterLastModification), + cleanupUnresolvedStateReferences(deleteDaysAfterLastModification)) + .thenApply(ignoredResult -> statistics); + } + + private CompletableFuture cleanup( + @Nonnull final String containerName, final int deleteDaysAfterLastModification) { + final Consumer> pageConsumer = + resourceKeyIds -> deleteCustomObjects(containerName, resourceKeyIds); + + return queryAll( + sphereClient, + getRequest(containerName, deleteDaysAfterLastModification), + pageConsumer, + pageSize) + .toCompletableFuture(); + } + + private CompletableFuture cleanupUnresolvedProductReferences( + final int deleteDaysAfterLastModification) { + return cleanup( + UnresolvedReferencesServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY, + deleteDaysAfterLastModification); + } + + private CompletableFuture cleanupUnresolvedStateReferences( + final int deleteDaysAfterLastModification) { + return cleanup( + UnresolvedTransitionsServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY, + deleteDaysAfterLastModification); + } + + /** + * Prepares a graphql request to fetch the unresolved reference custom objects based on the given + * {@code containerName} and {@code deleteDaysAfterLastModification}. + * + * @param containerName container name (i.e + * "commercetools-sync-java.UnresolvedReferencesService.productDrafts") + * @param deleteDaysAfterLastModification Days to query lastModifiedAt. The custom objects will be + * deleted if it hasn't been modified for the specified amount of days. + */ + private FetchCustomObjectsGraphQlRequest getRequest( + @Nonnull final String containerName, final int deleteDaysAfterLastModification) { + + final Instant lastModifiedAt = + Instant.now().minus(deleteDaysAfterLastModification, ChronoUnit.DAYS); + return new FetchCustomObjectsGraphQlRequest(containerName, lastModifiedAt); + } + + /** + * Deletes all custom objects in the given {@link List} representing a page of custom object's key + * and ids. + * + *

Note: The deletion is done concurrently but it's blocked by page consumer to avoid race + * conditions like fetching and removing same custom objects in same time. + * + * @param resourceKeyIdSet a page of custom object's key and ids. + */ + private void deleteCustomObjects( + @Nonnull final String containerName, @Nonnull final Set resourceKeyIdSet) { + + CompletableFuture.allOf( + resourceKeyIdSet.stream() + .map(resourceKeyId -> executeDeletion(containerName, resourceKeyId)) + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)) + .join(); + } - /** - * Deletes the unresolved reference custom objects persisted by commercetools-sync-java library to - * handle reference resolution. The custom objects will be deleted if it hasn't been modified for the specified - * amount of days as given {@code deleteDaysAfterLastModification}. - * - *

Note: Keeping the unresolved references forever can negatively influence the performance of your project, - * so deleting unused data ensures the best performance for your project. - * - * @param deleteDaysAfterLastModification Days to query. The custom objects will be deleted if it hasn't been - * modified for the specified amount of days. - * @return an instance of {@link CompletableFuture}<{@link CleanupUnresolvedReferenceCustomObjects.Statistics} - * > which contains the processing time, the total number of custom objects that were deleted and failed - * to delete, and a proper summary message of the statistics. - */ - public CompletableFuture cleanup(final int deleteDaysAfterLastModification) { - - return CompletableFuture - .allOf(cleanupUnresolvedProductReferences(deleteDaysAfterLastModification), - cleanupUnresolvedStateReferences(deleteDaysAfterLastModification)) - .thenApply(ignoredResult -> statistics); - } + private CompletionStage>> executeDeletion( + @Nonnull final String containerName, @Nonnull final ResourceKeyId resourceKeyId) { - private CompletableFuture cleanup( - @Nonnull final String containerName, - final int deleteDaysAfterLastModification) { - final Consumer> pageConsumer = resourceKeyIds -> - deleteCustomObjects(containerName, resourceKeyIds); + return sphereClient + .execute( + CustomObjectDeleteCommand.of(containerName, resourceKeyId.getKey(), JsonNode.class)) + .handle(this::handleDeleteCallback); + } - return queryAll(sphereClient, - getRequest(containerName, deleteDaysAfterLastModification), pageConsumer, pageSize) - .toCompletableFuture(); - } + private Optional> handleDeleteCallback( + @Nonnull final CustomObject resource, @Nullable final Throwable throwable) { - private CompletableFuture cleanupUnresolvedProductReferences(final int deleteDaysAfterLastModification) { - return cleanup(UnresolvedReferencesServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY, - deleteDaysAfterLastModification); + if (throwable == null) { + statistics.totalDeleted.incrementAndGet(); + return Optional.of(resource); + } else if (throwable instanceof NotFoundException) { + return Optional.empty(); } - private CompletableFuture cleanupUnresolvedStateReferences(final int deleteDaysAfterLastModification) { - return cleanup(UnresolvedTransitionsServiceImpl.CUSTOM_OBJECT_CONTAINER_KEY, - deleteDaysAfterLastModification); - } + applyErrorCallback(throwable); + statistics.totalFailed.incrementAndGet(); + return Optional.empty(); + } - /** - * Prepares a graphql request to fetch the unresolved reference custom objects based on the given - * {@code containerName} and {@code deleteDaysAfterLastModification}. - * - * @param containerName container name (i.e "commercetools-sync-java.UnresolvedReferencesService.productDrafts") - * @param deleteDaysAfterLastModification Days to query lastModifiedAt. The custom objects will be deleted if - * it hasn't been modified for the specified amount of days. - */ - private FetchCustomObjectsGraphQlRequest getRequest( - @Nonnull final String containerName, - final int deleteDaysAfterLastModification) { - - final Instant lastModifiedAt = Instant.now().minus(deleteDaysAfterLastModification, ChronoUnit.DAYS); - return new FetchCustomObjectsGraphQlRequest(containerName, lastModifiedAt); - } + public static class Statistics { + final AtomicInteger totalDeleted; + final AtomicInteger totalFailed; - /** - * Deletes all custom objects in the given {@link List} representing a page of custom object's key and ids. - * - *

Note: The deletion is done concurrently but it's blocked by page consumer to avoid race conditions like - * fetching and removing same custom objects in same time.

- * - * @param resourceKeyIdSet a page of custom object's key and ids. - */ - private void deleteCustomObjects( - @Nonnull final String containerName, - @Nonnull final Set resourceKeyIdSet) { - - CompletableFuture.allOf( - resourceKeyIdSet - .stream() - .map(resourceKeyId -> executeDeletion(containerName, resourceKeyId)) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)).join(); + private Statistics() { + this.totalDeleted = new AtomicInteger(); + this.totalFailed = new AtomicInteger(); } - private CompletionStage>> executeDeletion( - @Nonnull final String containerName, - @Nonnull final ResourceKeyId resourceKeyId) { - - return sphereClient.execute( - CustomObjectDeleteCommand.of(containerName, resourceKeyId.getKey(), JsonNode.class)) - .handle(this::handleDeleteCallback); + public int getTotalDeleted() { + return totalDeleted.get(); } - private Optional> handleDeleteCallback( - @Nonnull final CustomObject resource, - @Nullable final Throwable throwable) { - - if (throwable == null) { - statistics.totalDeleted.incrementAndGet(); - return Optional.of(resource); - } else if (throwable instanceof NotFoundException) { - return Optional.empty(); - } - - applyErrorCallback(throwable); - statistics.totalFailed.incrementAndGet(); - return Optional.empty(); + public int getTotalFailed() { + return totalFailed.get(); } - public static class Statistics { - final AtomicInteger totalDeleted; - final AtomicInteger totalFailed; - - private Statistics() { - this.totalDeleted = new AtomicInteger(); - this.totalFailed = new AtomicInteger(); - } - - public int getTotalDeleted() { - return totalDeleted.get(); - } - - public int getTotalFailed() { - return totalFailed.get(); - } - - public String getReportMessage() { - return format("Summary: %s custom objects were deleted in total (%s failed to delete).", - getTotalDeleted(), getTotalFailed()); - } + public String getReportMessage() { + return format( + "Summary: %s custom objects were deleted in total (%s failed to delete).", + getTotalDeleted(), getTotalFailed()); } + } } diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/BuildUpdateActionException.java b/src/main/java/com/commercetools/sync/commons/exceptions/BuildUpdateActionException.java index 0d05cc7f0b..25b12fa9e0 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/BuildUpdateActionException.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/BuildUpdateActionException.java @@ -4,15 +4,15 @@ public class BuildUpdateActionException extends Exception { - public BuildUpdateActionException(@Nonnull final String message) { - super(message); - } + public BuildUpdateActionException(@Nonnull final String message) { + super(message); + } - public BuildUpdateActionException(@Nonnull final Throwable cause) { - super(cause); - } + public BuildUpdateActionException(@Nonnull final Throwable cause) { + super(cause); + } - public BuildUpdateActionException(@Nonnull final String message, @Nonnull final Throwable cause) { - super(message, cause); - } + public BuildUpdateActionException(@Nonnull final String message, @Nonnull final Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/DuplicateKeyException.java b/src/main/java/com/commercetools/sync/commons/exceptions/DuplicateKeyException.java index 045296098c..6b553ce90f 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/DuplicateKeyException.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/DuplicateKeyException.java @@ -3,15 +3,15 @@ import javax.annotation.Nonnull; public class DuplicateKeyException extends RuntimeException { - public DuplicateKeyException(@Nonnull final String message) { - super(message); - } + public DuplicateKeyException(@Nonnull final String message) { + super(message); + } - public DuplicateKeyException(@Nonnull final Throwable cause) { - super(cause); - } + public DuplicateKeyException(@Nonnull final Throwable cause) { + super(cause); + } - public DuplicateKeyException(@Nonnull final String message, @Nonnull final Throwable cause) { - super(message, cause); - } + public DuplicateKeyException(@Nonnull final String message, @Nonnull final Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/DuplicateNameException.java b/src/main/java/com/commercetools/sync/commons/exceptions/DuplicateNameException.java index d63f99eb80..05c00d4312 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/DuplicateNameException.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/DuplicateNameException.java @@ -3,15 +3,15 @@ import javax.annotation.Nonnull; public class DuplicateNameException extends RuntimeException { - public DuplicateNameException(@Nonnull final String message) { - super(message); - } + public DuplicateNameException(@Nonnull final String message) { + super(message); + } - public DuplicateNameException(@Nonnull final Throwable cause) { - super(cause); - } + public DuplicateNameException(@Nonnull final Throwable cause) { + super(cause); + } - public DuplicateNameException(@Nonnull final String message, @Nonnull final Throwable cause) { - super(message, cause); - } + public DuplicateNameException(@Nonnull final String message, @Nonnull final Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/ExceptionUtils.java b/src/main/java/com/commercetools/sync/commons/exceptions/ExceptionUtils.java index bc0de175bc..02e805bf35 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/ExceptionUtils.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/ExceptionUtils.java @@ -1,52 +1,52 @@ package com.commercetools.sync.commons.exceptions; -import org.apache.commons.lang3.StringUtils; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isNotBlank; -import javax.annotation.Nonnull; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.StringUtils; final class ExceptionUtils { - @Nonnull - static String buildMessage(@Nonnull final String message, @Nonnull final Set causes, - final int causesTabbingDepth) { - final String causesString = asMessage(causes, causesTabbingDepth); - return isNotBlank(causesString) ? format("%s %s", message, causesString) : message; - } - - @Nonnull - private static String asMessage(@Nonnull final Set causes, final int tabbingDepth) { - final String tabs = buildTabs(tabbingDepth); - final List causesMessages = causes - .stream() + @Nonnull + static String buildMessage( + @Nonnull final String message, + @Nonnull final Set causes, + final int causesTabbingDepth) { + final String causesString = asMessage(causes, causesTabbingDepth); + return isNotBlank(causesString) ? format("%s %s", message, causesString) : message; + } + + @Nonnull + private static String asMessage(@Nonnull final Set causes, final int tabbingDepth) { + final String tabs = buildTabs(tabbingDepth); + final List causesMessages = + causes.stream() .map(Throwable::getMessage) .filter(StringUtils::isNotBlank) .collect(Collectors.toList()); - return !causesMessages.isEmpty() ? buildCausesMessage(tabs, causesMessages) : ""; - } - - @Nonnull - private static String buildCausesMessage(@Nonnull final String tabs, @Nonnull final List causesMessages) { - return format("Causes:%n%s%s", tabs, joinCauses(tabs, causesMessages)); - } - - @Nonnull - private static String joinCauses(@Nonnull final String tabs, @Nonnull final List causesMessages) { - return String.join(format("%n%s", tabs), causesMessages); - } - - @Nonnull - private static String buildTabs(final int numberOfTabs) { - return IntStream.range(0, numberOfTabs) - .mapToObj(index -> "\t") - .collect(Collectors.joining()); - } - - private ExceptionUtils() { - } + return !causesMessages.isEmpty() ? buildCausesMessage(tabs, causesMessages) : ""; + } + + @Nonnull + private static String buildCausesMessage( + @Nonnull final String tabs, @Nonnull final List causesMessages) { + return format("Causes:%n%s%s", tabs, joinCauses(tabs, causesMessages)); + } + + @Nonnull + private static String joinCauses( + @Nonnull final String tabs, @Nonnull final List causesMessages) { + return String.join(format("%n%s", tabs), causesMessages); + } + + @Nonnull + private static String buildTabs(final int numberOfTabs) { + return IntStream.range(0, numberOfTabs).mapToObj(index -> "\t").collect(Collectors.joining()); + } + + private ExceptionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/InvalidAttributeDefinitionException.java b/src/main/java/com/commercetools/sync/commons/exceptions/InvalidAttributeDefinitionException.java index d9e96416f3..785c45ef30 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/InvalidAttributeDefinitionException.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/InvalidAttributeDefinitionException.java @@ -4,7 +4,8 @@ public class InvalidAttributeDefinitionException extends Exception { - public InvalidAttributeDefinitionException(@Nonnull final String message, @Nonnull final Throwable cause) { - super(message, cause); - } -} \ No newline at end of file + public InvalidAttributeDefinitionException( + @Nonnull final String message, @Nonnull final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/InvalidProductTypeException.java b/src/main/java/com/commercetools/sync/commons/exceptions/InvalidProductTypeException.java index f446d1d87d..9dbc778c4a 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/InvalidProductTypeException.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/InvalidProductTypeException.java @@ -1,19 +1,20 @@ package com.commercetools.sync.commons.exceptions; -import javax.annotation.Nonnull; -import java.util.Set; - import static com.commercetools.sync.commons.exceptions.ExceptionUtils.buildMessage; +import java.util.Set; +import javax.annotation.Nonnull; + public class InvalidProductTypeException extends Exception { - private final Set causes; + private final Set causes; - public InvalidProductTypeException(@Nonnull final String message, @Nonnull final Set causes) { - super(buildMessage(message, causes, 2)); - this.causes = causes; - } + public InvalidProductTypeException( + @Nonnull final String message, @Nonnull final Set causes) { + super(buildMessage(message, causes, 2)); + this.causes = causes; + } - public Set getCauses() { - return causes; - } -} \ No newline at end of file + public Set getCauses() { + return causes; + } +} diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/InvalidReferenceException.java b/src/main/java/com/commercetools/sync/commons/exceptions/InvalidReferenceException.java index c445a91a94..ef76a4974b 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/InvalidReferenceException.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/InvalidReferenceException.java @@ -4,7 +4,7 @@ public class InvalidReferenceException extends Exception { - public InvalidReferenceException(@Nonnull final String message) { - super(message); - } -} \ No newline at end of file + public InvalidReferenceException(@Nonnull final String message) { + super(message); + } +} diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/ReferenceReplacementException.java b/src/main/java/com/commercetools/sync/commons/exceptions/ReferenceReplacementException.java index a5c6f96476..6a921d4e46 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/ReferenceReplacementException.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/ReferenceReplacementException.java @@ -1,19 +1,20 @@ package com.commercetools.sync.commons.exceptions; -import javax.annotation.Nonnull; -import java.util.Set; - import static com.commercetools.sync.commons.exceptions.ExceptionUtils.buildMessage; +import java.util.Set; +import javax.annotation.Nonnull; + public class ReferenceReplacementException extends RuntimeException { - private final Set causes; + private final Set causes; - public ReferenceReplacementException(@Nonnull final String message, @Nonnull final Set causes) { - super(buildMessage(message, causes, 1)); - this.causes = causes; - } + public ReferenceReplacementException( + @Nonnull final String message, @Nonnull final Set causes) { + super(buildMessage(message, causes, 1)); + this.causes = causes; + } - public Set getCauses() { - return causes; - } + public Set getCauses() { + return causes; + } } diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/ReferenceResolutionException.java b/src/main/java/com/commercetools/sync/commons/exceptions/ReferenceResolutionException.java index 3698e985e3..f610c3db28 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/ReferenceResolutionException.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/ReferenceResolutionException.java @@ -3,15 +3,16 @@ import javax.annotation.Nonnull; public class ReferenceResolutionException extends Exception { - public ReferenceResolutionException(@Nonnull final String message) { - super(message); - } + public ReferenceResolutionException(@Nonnull final String message) { + super(message); + } - public ReferenceResolutionException(@Nonnull final Throwable cause) { - super(cause); - } + public ReferenceResolutionException(@Nonnull final Throwable cause) { + super(cause); + } - public ReferenceResolutionException(@Nonnull final String message, @Nonnull final Throwable cause) { - super(message, cause); - } + public ReferenceResolutionException( + @Nonnull final String message, @Nonnull final Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/com/commercetools/sync/commons/exceptions/SyncException.java b/src/main/java/com/commercetools/sync/commons/exceptions/SyncException.java index c8a7a74cd2..187f028a2f 100644 --- a/src/main/java/com/commercetools/sync/commons/exceptions/SyncException.java +++ b/src/main/java/com/commercetools/sync/commons/exceptions/SyncException.java @@ -5,16 +5,15 @@ public class SyncException extends Exception { - public SyncException(@Nonnull final String message) { - super(message); - } + public SyncException(@Nonnull final String message) { + super(message); + } - public SyncException(@Nonnull final Throwable cause) { - super(cause); - } - - public SyncException(@Nonnull final String message, @Nullable final Throwable cause) { - super(message, cause); - } + public SyncException(@Nonnull final Throwable cause) { + super(cause); + } + public SyncException(@Nonnull final String message, @Nullable final Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/AssetActionFactory.java b/src/main/java/com/commercetools/sync/commons/helpers/AssetActionFactory.java index 465a3873c0..f16e0ef11c 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/AssetActionFactory.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/AssetActionFactory.java @@ -5,57 +5,58 @@ import io.sphere.sdk.models.Asset; import io.sphere.sdk.models.AssetDraft; import io.sphere.sdk.models.Resource; - -import javax.annotation.Nonnull; import java.util.List; - +import javax.annotation.Nonnull; /** - * Helper class for building update actions for assets that are contained in the resource of type {@code T}. + * Helper class for building update actions for assets that are contained in the resource of type + * {@code T}. * * @param the type of the resource the asset update actions are built for. - * @param the type of the draft, which contains the changes the asset update actions are built for. + * @param the type of the draft, which contains the changes the asset update actions are built + * for. */ public abstract class AssetActionFactory { - public BaseSyncOptions syncOptions = null; - - /** - * Takes a matching old asset and a new asset and computes the update actions needed to sync them. - * - * @param oldResource mainresource, whose asset should be updated. - * @param newResource new mainresource draft, which contains the asset to update. - * @param oldAsset the old asset to compare. - * @param newAssetDraft the matching new asset draft. - * @return update actions needed to sync the two assets. - */ - public abstract List> buildAssetActions(@Nonnull final T oldResource, - @Nonnull final D newResource, - @Nonnull Asset oldAsset, - @Nonnull AssetDraft newAssetDraft); - - /** - * Takes an asset key to build a RemoveAsset action of the type T. - * - * @param assetKey the key of the asset used un building the update action. - * @return the built remove asset update action. - */ - public abstract UpdateAction buildRemoveAssetAction(@Nonnull String assetKey); - - /** - * Takes a list of asset ids to build a ChangeAssetOrder action of the type T. - * - * @param newAssetOrder the new asset order needed to build the action. - * @return the built update action. - */ - public abstract UpdateAction buildChangeAssetOrderAction(@Nonnull List newAssetOrder); - - /** - * Takes an asset draft and an asset position to build an AddAsset action of the type T. - * - * @param newAssetDraft the new asset draft to create an Add asset action for. - * @param position the position to add the new asset to. - * @return the built update action. - */ - public abstract UpdateAction buildAddAssetAction(@Nonnull AssetDraft newAssetDraft, - @Nonnull Integer position); + public BaseSyncOptions syncOptions = null; + + /** + * Takes a matching old asset and a new asset and computes the update actions needed to sync them. + * + * @param oldResource mainresource, whose asset should be updated. + * @param newResource new mainresource draft, which contains the asset to update. + * @param oldAsset the old asset to compare. + * @param newAssetDraft the matching new asset draft. + * @return update actions needed to sync the two assets. + */ + public abstract List> buildAssetActions( + @Nonnull final T oldResource, + @Nonnull final D newResource, + @Nonnull Asset oldAsset, + @Nonnull AssetDraft newAssetDraft); + + /** + * Takes an asset key to build a RemoveAsset action of the type T. + * + * @param assetKey the key of the asset used un building the update action. + * @return the built remove asset update action. + */ + public abstract UpdateAction buildRemoveAssetAction(@Nonnull String assetKey); + + /** + * Takes a list of asset ids to build a ChangeAssetOrder action of the type T. + * + * @param newAssetOrder the new asset order needed to build the action. + * @return the built update action. + */ + public abstract UpdateAction buildChangeAssetOrderAction(@Nonnull List newAssetOrder); + + /** + * Takes an asset draft and an asset position to build an AddAsset action of the type T. + * + * @param newAssetDraft the new asset draft to create an Add asset action for. + * @param position the position to add the new asset to. + * @return the built update action. + */ + public abstract UpdateAction buildAddAssetAction( + @Nonnull AssetDraft newAssetDraft, @Nonnull Integer position); } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/AssetReferenceResolver.java b/src/main/java/com/commercetools/sync/commons/helpers/AssetReferenceResolver.java index 64b66f886e..492650ed6f 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/AssetReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/AssetReferenceResolver.java @@ -1,52 +1,50 @@ package com.commercetools.sync.commons.helpers; +import static java.lang.String.format; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.services.TypeService; import io.sphere.sdk.models.AssetDraft; import io.sphere.sdk.models.AssetDraftBuilder; - -import javax.annotation.Nonnull; import java.util.concurrent.CompletionStage; - -import static java.lang.String.format; +import javax.annotation.Nonnull; public final class AssetReferenceResolver extends CustomReferenceResolver { - static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type reference on " - + "AssetDraft with key:'%s'."; - - /** - * Takes a {@link BaseSyncOptions} instance and a {@link TypeService} to instantiate a - * {@link AssetReferenceResolver} instance that could be used to resolve the asset drafts in the CTP project - * specified in the injected {@link BaseSyncOptions} instance. - * - * @param options the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - * @param typeService the service to fetch the custom types for reference resolution. - */ - public AssetReferenceResolver(@Nonnull final BaseSyncOptions options, @Nonnull final TypeService typeService) { - super(options, typeService); - } - - @Override - @Nonnull - public CompletionStage resolveReferences(@Nonnull final AssetDraft assetDraft) { - return resolveCustomTypeReference(AssetDraftBuilder.of(assetDraft)) - .thenApply(AssetDraftBuilder::build); - } - - @Override - @Nonnull - protected CompletionStage resolveCustomTypeReference( - @Nonnull final AssetDraftBuilder assetDraftBuilder) { - - return resolveCustomTypeReference( - assetDraftBuilder, - AssetDraftBuilder::getCustom, - AssetDraftBuilder::custom, - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, assetDraftBuilder.getKey())); - } - - + static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = + "Failed to resolve custom type reference on AssetDraft with key:'%s'."; + + /** + * Takes a {@link BaseSyncOptions} instance and a {@link TypeService} to instantiate a {@link + * AssetReferenceResolver} instance that could be used to resolve the asset drafts in the CTP + * project specified in the injected {@link BaseSyncOptions} instance. + * + * @param options the container of all the options of the sync process including the CTP project + * client and/or configuration and other sync-specific options. + * @param typeService the service to fetch the custom types for reference resolution. + */ + public AssetReferenceResolver( + @Nonnull final BaseSyncOptions options, @Nonnull final TypeService typeService) { + super(options, typeService); + } + + @Override + @Nonnull + public CompletionStage resolveReferences(@Nonnull final AssetDraft assetDraft) { + return resolveCustomTypeReference(AssetDraftBuilder.of(assetDraft)) + .thenApply(AssetDraftBuilder::build); + } + + @Override + @Nonnull + protected CompletionStage resolveCustomTypeReference( + @Nonnull final AssetDraftBuilder assetDraftBuilder) { + + return resolveCustomTypeReference( + assetDraftBuilder, + AssetDraftBuilder::getCustom, + AssetDraftBuilder::custom, + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, assetDraftBuilder.getKey())); + } } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/BaseBatchValidator.java b/src/main/java/com/commercetools/sync/commons/helpers/BaseBatchValidator.java index 0e0ea6c179..49e36cb27e 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/BaseBatchValidator.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/BaseBatchValidator.java @@ -1,5 +1,7 @@ package com.commercetools.sync.commons.helpers; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.exceptions.SyncException; import io.sphere.sdk.categories.CategoryDraft; @@ -7,82 +9,80 @@ import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.types.CustomFieldsDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Set; import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; -import static org.apache.commons.lang3.StringUtils.isBlank; - -public abstract class BaseBatchValidator { - - private final O syncOptions; - private final S syncStatistics; - - public BaseBatchValidator(@Nonnull final O syncOptions, - @Nonnull final S syncStatistics) { - this.syncOptions = syncOptions; - this.syncStatistics = syncStatistics; - } - - /** - * Given the {@link List}<{@code D}> (e.g.{@link CategoryDraft}) of drafts this method attempts to validate - * drafts and collect referenced keys from the draft - * and return an {@link ImmutablePair}<{@link Set}<{@code D}>, ?> which contains - * the {@link Set} of valid drafts and referenced keys. - * - * @param drafts the drafts to validate. - * @return {@link ImmutablePair}<{@link Set}<{@code D}>, ?> which contains - * the {@link Set} of valid drafts and referenced keys. - */ - public abstract ImmutablePair, ?> validateAndCollectReferencedKeys(@Nonnull final List drafts); - - protected void collectReferencedKeyFromResourceIdentifier( - @Nullable final ResourceIdentifier resourceIdentifier, - @Nonnull final Consumer keyConsumer) { - - if (resourceIdentifier != null && !isBlank(resourceIdentifier.getKey())) { - keyConsumer.accept(resourceIdentifier.getKey()); - } +public abstract class BaseBatchValidator< + D, O extends BaseSyncOptions, S extends BaseSyncStatistics> { + + private final O syncOptions; + private final S syncStatistics; + + public BaseBatchValidator(@Nonnull final O syncOptions, @Nonnull final S syncStatistics) { + this.syncOptions = syncOptions; + this.syncStatistics = syncStatistics; + } + + /** + * Given the {@link List}<{@code D}> (e.g.{@link CategoryDraft}) of drafts this method + * attempts to validate drafts and collect referenced keys from the draft and return an {@link + * ImmutablePair}<{@link Set}<{@code D}>, ?> which contains the {@link Set} of valid + * drafts and referenced keys. + * + * @param drafts the drafts to validate. + * @return {@link ImmutablePair}<{@link Set}<{@code D}>, ?> which contains the {@link + * Set} of valid drafts and referenced keys. + */ + public abstract ImmutablePair, ?> validateAndCollectReferencedKeys( + @Nonnull final List drafts); + + protected void collectReferencedKeyFromResourceIdentifier( + @Nullable final ResourceIdentifier resourceIdentifier, + @Nonnull final Consumer keyConsumer) { + + if (resourceIdentifier != null && !isBlank(resourceIdentifier.getKey())) { + keyConsumer.accept(resourceIdentifier.getKey()); } + } - protected void collectReferencedKeyFromCustomFieldsDraft( - @Nullable final CustomFieldsDraft customFieldsDraft, - @Nonnull final Consumer keyConsumer) { + protected void collectReferencedKeyFromCustomFieldsDraft( + @Nullable final CustomFieldsDraft customFieldsDraft, + @Nonnull final Consumer keyConsumer) { - if (customFieldsDraft != null) { - collectReferencedKeyFromResourceIdentifier(customFieldsDraft.getType(), keyConsumer); - } + if (customFieldsDraft != null) { + collectReferencedKeyFromResourceIdentifier(customFieldsDraft.getType(), keyConsumer); } + } - protected void collectReferencedKeysFromAssetDrafts( - @Nullable final List assetDrafts, - @Nonnull final Consumer keyConsumer) { + protected void collectReferencedKeysFromAssetDrafts( + @Nullable final List assetDrafts, @Nonnull final Consumer keyConsumer) { - if (assetDrafts != null) { - assetDrafts.forEach(assetDraft -> - collectReferencedKeyFromCustomFieldsDraft(assetDraft.getCustom(), keyConsumer)); - } + if (assetDrafts != null) { + assetDrafts.forEach( + assetDraft -> + collectReferencedKeyFromCustomFieldsDraft(assetDraft.getCustom(), keyConsumer)); } + } - protected void collectReferencedKeyFromReference( - @Nullable final Reference reference, - @Nonnull final Consumer keyInReferenceSupplier) { + protected void collectReferencedKeyFromReference( + @Nullable final Reference reference, + @Nonnull final Consumer keyInReferenceSupplier) { - if (reference != null && !isBlank(reference.getId())) { - keyInReferenceSupplier.accept(reference.getId()); - } + if (reference != null && !isBlank(reference.getId())) { + keyInReferenceSupplier.accept(reference.getId()); } + } - protected void handleError(@Nonnull final SyncException syncException) { - this.syncOptions.applyErrorCallback(syncException); - this.syncStatistics.incrementFailed(); - } + protected void handleError(@Nonnull final SyncException syncException) { + this.syncOptions.applyErrorCallback(syncException); + this.syncStatistics.incrementFailed(); + } - protected void handleError(@Nonnull final String errorMessage) { - handleError(new SyncException(errorMessage)); - } + protected void handleError(@Nonnull final String errorMessage) { + handleError(new SyncException(errorMessage)); + } } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/BaseReferenceResolver.java b/src/main/java/com/commercetools/sync/commons/helpers/BaseReferenceResolver.java index 39538e3634..4456da1806 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/BaseReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/BaseReferenceResolver.java @@ -1,88 +1,98 @@ package com.commercetools.sync.commons.helpers; +import static org.apache.commons.lang3.StringUtils.isBlank; import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; - -import javax.annotation.Nonnull; import java.util.concurrent.CompletionStage; - -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; /** - * This class is responsible for providing an abstract implementation of reference resolution on different CTP - * resources. For concrete reference resolution implementation of the CTP resources, this class should be extended. + * This class is responsible for providing an abstract implementation of reference resolution on + * different CTP resources. For concrete reference resolution implementation of the CTP resources, + * this class should be extended. * - * @param a subclass implementation of {@link BaseSyncOptions} that is used to allow/deny some specific options, - * specified by the user, on reference resolution. + * @param a subclass implementation of {@link BaseSyncOptions} that is used to allow/deny some + * specific options, specified by the user, on reference resolution. */ public abstract class BaseReferenceResolver { - public static final String BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER = "The value of the 'key' field of the " - + "Resource Identifier is blank (null/empty). Expecting the key of the referenced resource."; - public static final String BLANK_ID_VALUE_ON_REFERENCE = "The value of the 'id' field of the Reference" - + " is blank (null/empty). Expecting the key of the referenced resource."; - protected S options; + public static final String BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER = + "The value of the 'key' field of the " + + "Resource Identifier is blank (null/empty). Expecting the key of the referenced resource."; + public static final String BLANK_ID_VALUE_ON_REFERENCE = + "The value of the 'id' field of the Reference" + + " is blank (null/empty). Expecting the key of the referenced resource."; + protected S options; - protected BaseReferenceResolver(@Nonnull final S options) { - this.options = options; - } + protected BaseReferenceResolver(@Nonnull final S options) { + this.options = options; + } - /** - * Given a draft this method attempts to resolve the all the references on the draft to - * return a {@link CompletionStage} which contains a new instance of the draft with the resolved - * references. - * - * @param draft the productDraft to resolve it's references. - * @return a {@link CompletionStage} that contains as a result a new draft instance with resolved references - * or, in case an error occurs during reference resolution, a {@link ReferenceResolutionException}. - */ - public abstract CompletionStage resolveReferences(@Nonnull T draft); + /** + * Given a draft this method attempts to resolve the all the references on the draft to return a + * {@link CompletionStage} which contains a new instance of the draft with the resolved + * references. + * + * @param draft the productDraft to resolve it's references. + * @return a {@link CompletionStage} that contains as a result a new draft instance with resolved + * references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + public abstract CompletionStage resolveReferences(@Nonnull T draft); - /** - * This method gets the key value on the passed {@link Reference} from the id field, if valid. If it is not valid, a - * {@link ReferenceResolutionException} will be thrown. The validity checks are: - *
    - *
  • Checks if the id value is not null or not empty.
  • - *
- * If the above checks pass, the key value is returned. Otherwise a {@link ReferenceResolutionException} is thrown. - * - * @param reference the reference from which the key value is validated and returned. - * @return the Id value on the {@link ResourceIdentifier} - * @throws ReferenceResolutionException if any of the validation checks fail. - */ - @Nonnull - protected static String getIdFromReference(@Nonnull final Reference reference) - throws ReferenceResolutionException { + /** + * This method gets the key value on the passed {@link Reference} from the id field, if valid. If + * it is not valid, a {@link ReferenceResolutionException} will be thrown. The validity checks + * are: + * + *
    + *
  • Checks if the id value is not null or not empty. + *
+ * + * If the above checks pass, the key value is returned. Otherwise a {@link + * ReferenceResolutionException} is thrown. + * + * @param reference the reference from which the key value is validated and returned. + * @return the Id value on the {@link ResourceIdentifier} + * @throws ReferenceResolutionException if any of the validation checks fail. + */ + @Nonnull + protected static String getIdFromReference(@Nonnull final Reference reference) + throws ReferenceResolutionException { - final String id = reference.getId(); - if (isBlank(id)) { - throw new ReferenceResolutionException(BLANK_ID_VALUE_ON_REFERENCE); - } - return id; + final String id = reference.getId(); + if (isBlank(id)) { + throw new ReferenceResolutionException(BLANK_ID_VALUE_ON_REFERENCE); } + return id; + } - /** - * This method gets the key value on the passed {@link ResourceIdentifier}, if valid. If it is not valid, a - * {@link ReferenceResolutionException} will be thrown. The validity checks are: - *
    - *
  • Checks if the key value is not null or not empty.
  • - *
- * If the above checks pass, the key value is returned. Otherwise a {@link ReferenceResolutionException} is thrown. - * - * @param resourceIdentifier the resource identifier from which the key value is validated and returned. - * @return the key value on the {@link ResourceIdentifier} - * @throws ReferenceResolutionException if any of the validation checks fail. - */ - @Nonnull - protected static String getKeyFromResourceIdentifier(@Nonnull final ResourceIdentifier resourceIdentifier) - throws ReferenceResolutionException { + /** + * This method gets the key value on the passed {@link ResourceIdentifier}, if valid. If it is not + * valid, a {@link ReferenceResolutionException} will be thrown. The validity checks are: + * + *
    + *
  • Checks if the key value is not null or not empty. + *
+ * + * If the above checks pass, the key value is returned. Otherwise a {@link + * ReferenceResolutionException} is thrown. + * + * @param resourceIdentifier the resource identifier from which the key value is validated and + * returned. + * @return the key value on the {@link ResourceIdentifier} + * @throws ReferenceResolutionException if any of the validation checks fail. + */ + @Nonnull + protected static String getKeyFromResourceIdentifier( + @Nonnull final ResourceIdentifier resourceIdentifier) throws ReferenceResolutionException { - final String key = resourceIdentifier.getKey(); - if (isBlank(key)) { - throw new ReferenceResolutionException(BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER); - } - return key; + final String key = resourceIdentifier.getKey(); + if (isBlank(key)) { + throw new ReferenceResolutionException(BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER); } + return key; + } } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/BaseSyncStatistics.java b/src/main/java/com/commercetools/sync/commons/helpers/BaseSyncStatistics.java index 94828371aa..01eee22162 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/BaseSyncStatistics.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/BaseSyncStatistics.java @@ -1,301 +1,298 @@ package com.commercetools.sync.commons.helpers; -import io.netty.util.internal.StringUtil; +import static java.lang.String.format; -import javax.annotation.Nonnull; +import io.netty.util.internal.StringUtil; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; - -import static java.lang.String.format; +import javax.annotation.Nonnull; public abstract class BaseSyncStatistics { - private AtomicInteger updated; - private AtomicInteger created; - private AtomicInteger failed; - private AtomicInteger processed; - private long latestBatchStartTime; - private long latestBatchProcessingTimeInDays; - private long latestBatchProcessingTimeInHours; - private long latestBatchProcessingTimeInMinutes; - private long latestBatchProcessingTimeInSeconds; - private long latestBatchProcessingTimeInMillis; - private String latestBatchHumanReadableProcessingTime; - - - /** - * Creates a new {@link BaseSyncStatistics} with initial values {@code 0} of created, updated, failed and processed - * counters, an empty reportMessage and latestBatchHumanReadableProcessingTime. - */ - public BaseSyncStatistics() { - updated = new AtomicInteger(); - created = new AtomicInteger(); - failed = new AtomicInteger(); - processed = new AtomicInteger(); - latestBatchHumanReadableProcessingTime = StringUtil.EMPTY_STRING; - } - - /** - * Stores the current time of instantiation in the {@code latestBatchStartTime} instance variable that will be used - * later when {@link BaseSyncStatistics#calculateProcessingTime()} is called to calculate the total time of - * processing. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - */ - public void startTimer() { - latestBatchStartTime = System.currentTimeMillis(); - } - - /** - * Gets the total number of resources that were updated. - * - * @return total number of resources that were updated. - */ - public AtomicInteger getUpdated() { - return updated; - } - - /** - * Increments the total number of resource that were updated. - */ - public void incrementUpdated() { - updated.incrementAndGet(); - } - - /** - * Increments the total number of resources that were updated by the supplied times. - * - * @param times the total number of times to increment. - */ - public void incrementUpdated(final int times) { - updated.addAndGet(times); - } - - /** - * Gets the total number of resources that were created. - * - * @return total number of resources that were created. - */ - public AtomicInteger getCreated() { - return created; - } - - /** - * Increments the total number of resource that were created. - */ - public void incrementCreated() { - created.incrementAndGet(); - } - - /** - * Increments the total number of resources that were created by the supplied times. - * - * @param times the total number of times to increment. - */ - public void incrementCreated(final int times) { - created.addAndGet(times); - } - - /** - * Gets the total number of resources that were processed/synced. - * - * @return total number of resources that were processed/synced. - */ - public AtomicInteger getProcessed() { - return processed; - } - - /** - * Increments the total number of resources that were processed/synced. - */ - public void incrementProcessed() { - processed.incrementAndGet(); - } - - /** - * Increments the total number of resources that were processed/synced by the supplied times. - * - * @param times the total number of times to increment. - */ - public void incrementProcessed(final int times) { - processed.addAndGet(times); - } - - /** - * Gets the total number of resources that failed to sync. - * - * @return total number of resources that failed to sync. - */ - public AtomicInteger getFailed() { - return failed; - } - - /** - * Increments the total number of resources that failed to sync. - * - */ - public void incrementFailed() { - failed.incrementAndGet(); - } - - /** - * Increments the total number of resources that failed to sync by the supplied times. - * - * @param times the total number of times to increment. - */ - public void incrementFailed(final int times) { - failed.addAndGet(times); - } - - /** - * Calculates the processing time taken by the subtracting the time, when the - * {@link BaseSyncStatistics#startTimer()} method of this instance was called, from the current time in - * Milliseconds. It also sets the processing time in all the units {@code latestBatchProcessingTimeInDays}, - * {@code latestBatchProcessingTimeInHours}, {@code latestBatchProcessingTimeInMinutes}, - * {@code latestBatchProcessingTimeInSeconds} and - * {@code latestBatchProcessingTimeInMillis}. It also builds a human readable processing time, as string, in the - * following format @{code "0d, 0h, 0m, 2s, 545ms"} and stores it in the publicly exposed - * variable {@code latestBatchHumanReadableProcessingTime}. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - */ - public void calculateProcessingTime() { - setProcessingTimeInAllUnits(); - setHumanReadableProcessingTime(); - } - - /** - * Calculates the processing time taken by the subtracting the time when this {@link BaseSyncStatistics} instance - * was instantiated from the current time in Milliseconds. It sets the processing time in all the units - * {@code latestBatchProcessingTimeInDays}, {@code latestBatchProcessingTimeInHours}, - * {@code latestBatchProcessingTimeInMinutes}, - * {@code latestBatchProcessingTimeInSeconds} and {@code latestBatchProcessingTimeInMillis}. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - */ - private void setProcessingTimeInAllUnits() { - latestBatchProcessingTimeInMillis = System.currentTimeMillis() - this.latestBatchStartTime; - latestBatchProcessingTimeInDays = TimeUnit.MILLISECONDS.toDays(latestBatchProcessingTimeInMillis); - latestBatchProcessingTimeInHours = TimeUnit.MILLISECONDS.toHours(latestBatchProcessingTimeInMillis); - latestBatchProcessingTimeInMinutes = TimeUnit.MILLISECONDS.toMinutes(latestBatchProcessingTimeInMillis); - latestBatchProcessingTimeInSeconds = TimeUnit.MILLISECONDS.toSeconds(latestBatchProcessingTimeInMillis); - } - - /** - * Builds a human readable processing time, as string, in the following format @{code "0d, 0h, 0m, 2s, 545ms"} - * and stores it in the publicly exposed variable {@code latestBatchHumanReadableProcessingTime}. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - */ - private void setHumanReadableProcessingTime() { - final long completeDaysInHours = TimeUnit.DAYS.toHours(latestBatchProcessingTimeInDays); - final long completeHoursInMinutes = TimeUnit.HOURS.toMinutes(latestBatchProcessingTimeInHours); - final long completeMinutesInSeconds = TimeUnit.MINUTES.toSeconds(latestBatchProcessingTimeInMinutes); - final long completeSecondsInMillis = TimeUnit.SECONDS.toMillis(latestBatchProcessingTimeInSeconds); - - final long remainingHours = latestBatchProcessingTimeInHours - completeDaysInHours; - final long remainingMinutes = latestBatchProcessingTimeInMinutes - completeHoursInMinutes; - final long remainingSeconds = latestBatchProcessingTimeInSeconds - completeMinutesInSeconds; - final long remainingMillis = latestBatchProcessingTimeInMillis - completeSecondsInMillis; - - latestBatchHumanReadableProcessingTime = format("%dd, %dh, %dm, %ds, %dms", + private AtomicInteger updated; + private AtomicInteger created; + private AtomicInteger failed; + private AtomicInteger processed; + private long latestBatchStartTime; + private long latestBatchProcessingTimeInDays; + private long latestBatchProcessingTimeInHours; + private long latestBatchProcessingTimeInMinutes; + private long latestBatchProcessingTimeInSeconds; + private long latestBatchProcessingTimeInMillis; + private String latestBatchHumanReadableProcessingTime; + + /** + * Creates a new {@link BaseSyncStatistics} with initial values {@code 0} of created, updated, + * failed and processed counters, an empty reportMessage and + * latestBatchHumanReadableProcessingTime. + */ + public BaseSyncStatistics() { + updated = new AtomicInteger(); + created = new AtomicInteger(); + failed = new AtomicInteger(); + processed = new AtomicInteger(); + latestBatchHumanReadableProcessingTime = StringUtil.EMPTY_STRING; + } + + /** + * Stores the current time of instantiation in the {@code latestBatchStartTime} instance variable + * that will be used later when {@link BaseSyncStatistics#calculateProcessingTime()} is called to + * calculate the total time of processing. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + */ + public void startTimer() { + latestBatchStartTime = System.currentTimeMillis(); + } + + /** + * Gets the total number of resources that were updated. + * + * @return total number of resources that were updated. + */ + public AtomicInteger getUpdated() { + return updated; + } + + /** Increments the total number of resource that were updated. */ + public void incrementUpdated() { + updated.incrementAndGet(); + } + + /** + * Increments the total number of resources that were updated by the supplied times. + * + * @param times the total number of times to increment. + */ + public void incrementUpdated(final int times) { + updated.addAndGet(times); + } + + /** + * Gets the total number of resources that were created. + * + * @return total number of resources that were created. + */ + public AtomicInteger getCreated() { + return created; + } + + /** Increments the total number of resource that were created. */ + public void incrementCreated() { + created.incrementAndGet(); + } + + /** + * Increments the total number of resources that were created by the supplied times. + * + * @param times the total number of times to increment. + */ + public void incrementCreated(final int times) { + created.addAndGet(times); + } + + /** + * Gets the total number of resources that were processed/synced. + * + * @return total number of resources that were processed/synced. + */ + public AtomicInteger getProcessed() { + return processed; + } + + /** Increments the total number of resources that were processed/synced. */ + public void incrementProcessed() { + processed.incrementAndGet(); + } + + /** + * Increments the total number of resources that were processed/synced by the supplied times. + * + * @param times the total number of times to increment. + */ + public void incrementProcessed(final int times) { + processed.addAndGet(times); + } + + /** + * Gets the total number of resources that failed to sync. + * + * @return total number of resources that failed to sync. + */ + public AtomicInteger getFailed() { + return failed; + } + + /** Increments the total number of resources that failed to sync. */ + public void incrementFailed() { + failed.incrementAndGet(); + } + + /** + * Increments the total number of resources that failed to sync by the supplied times. + * + * @param times the total number of times to increment. + */ + public void incrementFailed(final int times) { + failed.addAndGet(times); + } + + /** + * Calculates the processing time taken by the subtracting the time, when the {@link + * BaseSyncStatistics#startTimer()} method of this instance was called, from the current time in + * Milliseconds. It also sets the processing time in all the units {@code + * latestBatchProcessingTimeInDays}, {@code latestBatchProcessingTimeInHours}, {@code + * latestBatchProcessingTimeInMinutes}, {@code latestBatchProcessingTimeInSeconds} and {@code + * latestBatchProcessingTimeInMillis}. It also builds a human readable processing time, as string, + * in the following format @{code "0d, 0h, 0m, 2s, 545ms"} and stores it in the publicly exposed + * variable {@code latestBatchHumanReadableProcessingTime}. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + */ + public void calculateProcessingTime() { + setProcessingTimeInAllUnits(); + setHumanReadableProcessingTime(); + } + + /** + * Calculates the processing time taken by the subtracting the time when this {@link + * BaseSyncStatistics} instance was instantiated from the current time in Milliseconds. It sets + * the processing time in all the units {@code latestBatchProcessingTimeInDays}, {@code + * latestBatchProcessingTimeInHours}, {@code latestBatchProcessingTimeInMinutes}, {@code + * latestBatchProcessingTimeInSeconds} and {@code latestBatchProcessingTimeInMillis}. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + */ + private void setProcessingTimeInAllUnits() { + latestBatchProcessingTimeInMillis = System.currentTimeMillis() - this.latestBatchStartTime; + latestBatchProcessingTimeInDays = + TimeUnit.MILLISECONDS.toDays(latestBatchProcessingTimeInMillis); + latestBatchProcessingTimeInHours = + TimeUnit.MILLISECONDS.toHours(latestBatchProcessingTimeInMillis); + latestBatchProcessingTimeInMinutes = + TimeUnit.MILLISECONDS.toMinutes(latestBatchProcessingTimeInMillis); + latestBatchProcessingTimeInSeconds = + TimeUnit.MILLISECONDS.toSeconds(latestBatchProcessingTimeInMillis); + } + + /** + * Builds a human readable processing time, as string, in the following format @{code "0d, 0h, 0m, + * 2s, 545ms"} and stores it in the publicly exposed variable {@code + * latestBatchHumanReadableProcessingTime}. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + */ + private void setHumanReadableProcessingTime() { + final long completeDaysInHours = TimeUnit.DAYS.toHours(latestBatchProcessingTimeInDays); + final long completeHoursInMinutes = TimeUnit.HOURS.toMinutes(latestBatchProcessingTimeInHours); + final long completeMinutesInSeconds = + TimeUnit.MINUTES.toSeconds(latestBatchProcessingTimeInMinutes); + final long completeSecondsInMillis = + TimeUnit.SECONDS.toMillis(latestBatchProcessingTimeInSeconds); + + final long remainingHours = latestBatchProcessingTimeInHours - completeDaysInHours; + final long remainingMinutes = latestBatchProcessingTimeInMinutes - completeHoursInMinutes; + final long remainingSeconds = latestBatchProcessingTimeInSeconds - completeMinutesInSeconds; + final long remainingMillis = latestBatchProcessingTimeInMillis - completeSecondsInMillis; + + latestBatchHumanReadableProcessingTime = + format( + "%dd, %dh, %dm, %ds, %dms", latestBatchProcessingTimeInDays, - remainingHours, - remainingMinutes, - remainingSeconds, - remainingMillis - ); - } - - /** - * Gets the human readable processing time in the following format @{code "0d, 0h, 0m, 2s, 545ms"}. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - * @return the human readable processing time in the following format @{code "0d, 0h, 0m, 2s, 545ms"} - */ - public String getLatestBatchHumanReadableProcessingTime() { - return latestBatchHumanReadableProcessingTime; - } - - /** - * Gets the number of days it took to process. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - * @return number of days taken to process. - */ - public long getLatestBatchProcessingTimeInDays() { - return latestBatchProcessingTimeInDays; - } - - /** - * Gets the number of hours it took to process. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - * @return number of hours taken to process. - */ - public long getLatestBatchProcessingTimeInHours() { - return latestBatchProcessingTimeInHours; - } - - /** - * Gets the number of minutes it took to process. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - * @return number of minutes taken to process. - */ - public long getLatestBatchProcessingTimeInMinutes() { - return latestBatchProcessingTimeInMinutes; - } - - /** - * Gets the number of seconds it took to process. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - * @return number of seconds taken to process. - */ - public long getLatestBatchProcessingTimeInSeconds() { - return latestBatchProcessingTimeInSeconds; - } - - /** - * Gets the number of milliseconds it took to process. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - * @return number of milliseconds taken to process. - */ - public long getLatestBatchProcessingTimeInMillis() { - return latestBatchProcessingTimeInMillis; - } - - /** - * Gets a summary message of the statistics report. - * - *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. - * - * @return a summary message of the statistics report. - */ - public abstract String getReportMessage(); - - /** - * Builds a proper summary message of the statistics report of a given {@code resourceString} in following format: - * - *

"Summary: 2 resources were processed in total (2 created, 2 updated and 0 failed to sync)." - * - * @param resourceString a string representing the resource - * @return a summary message of the statistics report - */ - protected String getDefaultReportMessageForResource(@Nonnull final String resourceString) { - return format( - "Summary: %s %s were processed in total (%s created, %s updated and %s failed to sync).", - getProcessed(), resourceString, getCreated(), getUpdated(), getFailed()); - } + remainingHours, + remainingMinutes, + remainingSeconds, + remainingMillis); + } + + /** + * Gets the human readable processing time in the following format @{code "0d, 0h, 0m, 2s, + * 545ms"}. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + * + * @return the human readable processing time in the following format @{code "0d, 0h, 0m, 2s, + * 545ms"} + */ + public String getLatestBatchHumanReadableProcessingTime() { + return latestBatchHumanReadableProcessingTime; + } + + /** + * Gets the number of days it took to process. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + * + * @return number of days taken to process. + */ + public long getLatestBatchProcessingTimeInDays() { + return latestBatchProcessingTimeInDays; + } + + /** + * Gets the number of hours it took to process. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + * + * @return number of hours taken to process. + */ + public long getLatestBatchProcessingTimeInHours() { + return latestBatchProcessingTimeInHours; + } + + /** + * Gets the number of minutes it took to process. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + * + * @return number of minutes taken to process. + */ + public long getLatestBatchProcessingTimeInMinutes() { + return latestBatchProcessingTimeInMinutes; + } + + /** + * Gets the number of seconds it took to process. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + * + * @return number of seconds taken to process. + */ + public long getLatestBatchProcessingTimeInSeconds() { + return latestBatchProcessingTimeInSeconds; + } + + /** + * Gets the number of milliseconds it took to process. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + * + * @return number of milliseconds taken to process. + */ + public long getLatestBatchProcessingTimeInMillis() { + return latestBatchProcessingTimeInMillis; + } + + /** + * Gets a summary message of the statistics report. + * + *

Note: This method isn't thread-safe and shouldn't be used in a concurrent context. + * + * @return a summary message of the statistics report. + */ + public abstract String getReportMessage(); + + /** + * Builds a proper summary message of the statistics report of a given {@code resourceString} in + * following format: + * + *

"Summary: 2 resources were processed in total (2 created, 2 updated and 0 failed to sync)." + * + * @param resourceString a string representing the resource + * @return a summary message of the statistics report + */ + protected String getDefaultReportMessageForResource(@Nonnull final String resourceString) { + return format( + "Summary: %s %s were processed in total (%s created, %s updated and %s failed to sync).", + getProcessed(), resourceString, getCreated(), getUpdated(), getFailed()); + } } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/CategoryReferencePair.java b/src/main/java/com/commercetools/sync/commons/helpers/CategoryReferencePair.java index 118b1439ba..8f03e2910c 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/CategoryReferencePair.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/CategoryReferencePair.java @@ -4,36 +4,37 @@ import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.CategoryOrderHints; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** - * Container for a {@link List} of {@link Category} {@link Reference}s and a {@link CategoryOrderHints}. + * Container for a {@link List} of {@link Category} {@link Reference}s and a {@link + * CategoryOrderHints}. */ public final class CategoryReferencePair { - private Set> categoryResourceIdentifiers; - private CategoryOrderHints categoryOrderHints; - - private CategoryReferencePair(@Nonnull final Set> categoryResourceIdentifiers, - @Nullable final CategoryOrderHints categoryOrderHints) { - this.categoryResourceIdentifiers = categoryResourceIdentifiers; - this.categoryOrderHints = categoryOrderHints; - } - - public static CategoryReferencePair of(@Nonnull final Set> categoryReferences, - @Nullable final CategoryOrderHints categoryOrderHints) { - return new CategoryReferencePair(categoryReferences, categoryOrderHints); - } - - - public Set> getCategoryResourceIdentifiers() { - return categoryResourceIdentifiers; - } - - public CategoryOrderHints getCategoryOrderHints() { - return categoryOrderHints; - } + private Set> categoryResourceIdentifiers; + private CategoryOrderHints categoryOrderHints; + + private CategoryReferencePair( + @Nonnull final Set> categoryResourceIdentifiers, + @Nullable final CategoryOrderHints categoryOrderHints) { + this.categoryResourceIdentifiers = categoryResourceIdentifiers; + this.categoryOrderHints = categoryOrderHints; + } + + public static CategoryReferencePair of( + @Nonnull final Set> categoryReferences, + @Nullable final CategoryOrderHints categoryOrderHints) { + return new CategoryReferencePair(categoryReferences, categoryOrderHints); + } + + public Set> getCategoryResourceIdentifiers() { + return categoryResourceIdentifiers; + } + + public CategoryOrderHints getCategoryOrderHints() { + return categoryOrderHints; + } } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/CustomReferenceResolver.java b/src/main/java/com/commercetools/sync/commons/helpers/CustomReferenceResolver.java index 05bac2d6ef..69858f4e24 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/CustomReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/CustomReferenceResolver.java @@ -1,5 +1,10 @@ package com.commercetools.sync.commons.helpers; +import static io.sphere.sdk.types.CustomFieldsDraft.ofTypeIdAndJson; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.services.TypeService; @@ -9,145 +14,153 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import java.util.function.Function; - -import static io.sphere.sdk.types.CustomFieldsDraft.ofTypeIdAndJson; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** - * This class is responsible for providing an abstract implementation of reference resolution on Custom CTP - * resources for example (Categories, inventories and resource with a custom field). - * For concrete reference resolution implementation of the CTP resources, this class should be extended. + * This class is responsible for providing an abstract implementation of reference resolution on + * Custom CTP resources for example (Categories, inventories and resource with a custom field). For + * concrete reference resolution implementation of the CTP resources, this class should be extended. * * @param the resource draft type for which reference resolution has to be done on. - * @param the resource draft builder where resolved values should be set. The builder type should correspond - * the {@code D} type. - * @param a subclass implementation of {@link BaseSyncOptions} that is used to allow/deny some specific options, - * specified by the user, on reference resolution. + * @param the resource draft builder where resolved values should be set. The builder type + * should correspond the {@code D} type. + * @param a subclass implementation of {@link BaseSyncOptions} that is used to allow/deny some + * specific options, specified by the user, on reference resolution. */ -public abstract class CustomReferenceResolver - , S extends BaseSyncOptions> +public abstract class CustomReferenceResolver< + D, B extends Builder, S extends BaseSyncOptions> extends BaseReferenceResolver { - public static final String TYPE_DOES_NOT_EXIST = "Type with key '%s' doesn't exist."; - private final TypeService typeService; - - protected CustomReferenceResolver(@Nonnull final S options, @Nonnull final TypeService typeService) { - super(options); - this.typeService = typeService; - } - - /** - * Given a draft of {@code D} (e.g. {@link CategoryDraft}) this method attempts to resolve it's custom type - * reference to return {@link CompletionStage} which contains a new instance of the draft with the resolved - * custom type reference. - * - *

The method then tries to fetch the key of the custom type, optimistically from a - * cache. If the key is is not found, the resultant draft would remain exactly the same as the passed - * draft (without a custom type reference resolution). - * - * @param draftBuilder the draft builder to resolve it's references. - * @return a {@link CompletionStage} that contains as a result a new draft instance with resolved custom - * type references or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - protected abstract CompletionStage resolveCustomTypeReference(@Nonnull B draftBuilder); - - /** - * Given a draft of {@code D} (e.g. {@link CategoryDraft}) this method attempts to resolve it's custom type - * reference to return {@link CompletionStage} which contains a new instance of the draft with the resolved - * custom type reference. - * - *

The method then tries to fetch the key of the custom type, optimistically from a - * cache. If the key is is not found, the resultant draft would remain exactly the same as the passed - * draft (without a custom type reference resolution). - * - *

Note: If the id field is set, then it is an evidence of resource existence on commercetools, - * so we can issue an update/create API request right away without reference resolution. - * - * @param draftBuilder the draft builder to resolve it's references. - * @param customGetter a function to return the CustomFieldsDraft instance of the draft builder. - * @param customSetter a function to set the CustomFieldsDraft instance of the builder and return this builder. - * @param errorMessage the error message to inject in the {@link ReferenceResolutionException} if it occurs. - * @return a {@link CompletionStage} that contains as a result a new draft instance with resolved custom - * type references or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Nonnull - protected CompletionStage resolveCustomTypeReference( - @Nonnull final B draftBuilder, - @Nonnull final Function customGetter, - @Nonnull final BiFunction customSetter, - @Nonnull final String errorMessage) { - - final CustomFieldsDraft custom = customGetter.apply(draftBuilder); - - if (custom != null) { - final ResourceIdentifier customType = custom.getType(); - - if (customType.getId() == null) { - String customTypeKey; - - try { - customTypeKey = getCustomTypeKey(customType, errorMessage); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture(referenceResolutionException); - } - - return fetchAndResolveTypeReference(draftBuilder, customSetter, custom.getFields(), customTypeKey, - errorMessage); - } - } - return completedFuture(draftBuilder); - } - - /** - * Given a custom fields object this method fetches the custom type reference id. - * - * @param customType the custom fields' Type. - * @param referenceResolutionErrorMessage the message containing the information about the draft to attach to the - * {@link ReferenceResolutionException} in case it occurs. - * @return a {@link CompletionStage} that contains as a result an optional which either contains the custom type id - * if it exists or empty if it doesn't. - */ - private String getCustomTypeKey( - @Nonnull final ResourceIdentifier customType, - @Nonnull final String referenceResolutionErrorMessage) throws ReferenceResolutionException { + public static final String TYPE_DOES_NOT_EXIST = "Type with key '%s' doesn't exist."; + private final TypeService typeService; + + protected CustomReferenceResolver( + @Nonnull final S options, @Nonnull final TypeService typeService) { + super(options); + this.typeService = typeService; + } + + /** + * Given a draft of {@code D} (e.g. {@link CategoryDraft}) this method attempts to resolve it's + * custom type reference to return {@link CompletionStage} which contains a new instance of the + * draft with the resolved custom type reference. + * + *

The method then tries to fetch the key of the custom type, optimistically from a cache. If + * the key is is not found, the resultant draft would remain exactly the same as the passed draft + * (without a custom type reference resolution). + * + * @param draftBuilder the draft builder to resolve it's references. + * @return a {@link CompletionStage} that contains as a result a new draft instance with resolved + * custom type references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + protected abstract CompletionStage resolveCustomTypeReference(@Nonnull B draftBuilder); + + /** + * Given a draft of {@code D} (e.g. {@link CategoryDraft}) this method attempts to resolve it's + * custom type reference to return {@link CompletionStage} which contains a new instance of the + * draft with the resolved custom type reference. + * + *

The method then tries to fetch the key of the custom type, optimistically from a cache. If + * the key is is not found, the resultant draft would remain exactly the same as the passed draft + * (without a custom type reference resolution). + * + *

Note: If the id field is set, then it is an evidence of resource existence on commercetools, + * so we can issue an update/create API request right away without reference resolution. + * + * @param draftBuilder the draft builder to resolve it's references. + * @param customGetter a function to return the CustomFieldsDraft instance of the draft builder. + * @param customSetter a function to set the CustomFieldsDraft instance of the builder and return + * this builder. + * @param errorMessage the error message to inject in the {@link ReferenceResolutionException} if + * it occurs. + * @return a {@link CompletionStage} that contains as a result a new draft instance with resolved + * custom type references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Nonnull + protected CompletionStage resolveCustomTypeReference( + @Nonnull final B draftBuilder, + @Nonnull final Function customGetter, + @Nonnull final BiFunction customSetter, + @Nonnull final String errorMessage) { + + final CustomFieldsDraft custom = customGetter.apply(draftBuilder); + + if (custom != null) { + final ResourceIdentifier customType = custom.getType(); + + if (customType.getId() == null) { + String customTypeKey; try { - return getKeyFromResourceIdentifier(customType); - } catch (ReferenceResolutionException exception) { - final String errorMessage = - format("%s Reason: %s", referenceResolutionErrorMessage, exception.getMessage()); - throw new ReferenceResolutionException(errorMessage, exception); + customTypeKey = getCustomTypeKey(customType, errorMessage); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture(referenceResolutionException); } - } - @Nonnull - private CompletionStage fetchAndResolveTypeReference( - @Nonnull final B draftBuilder, - @Nonnull final BiFunction customSetter, - @Nullable final Map customFields, - @Nonnull final String typeKey, - @Nonnull final String referenceResolutionErrorMessage) { - - return typeService - .fetchCachedTypeId(typeKey) - .thenCompose(resolvedTypeIdOptional -> resolvedTypeIdOptional - .map(resolvedTypeId -> - completedFuture(customSetter.apply(draftBuilder, ofTypeIdAndJson(resolvedTypeId, customFields)))) - .orElseGet(() -> { - final String errorMessage = - format("%s Reason: %s", referenceResolutionErrorMessage, format(TYPE_DOES_NOT_EXIST, typeKey)); - return exceptionallyCompletedFuture(new ReferenceResolutionException(errorMessage)); - })); + return fetchAndResolveTypeReference( + draftBuilder, customSetter, custom.getFields(), customTypeKey, errorMessage); + } + } + return completedFuture(draftBuilder); + } + + /** + * Given a custom fields object this method fetches the custom type reference id. + * + * @param customType the custom fields' Type. + * @param referenceResolutionErrorMessage the message containing the information about the draft + * to attach to the {@link ReferenceResolutionException} in case it occurs. + * @return a {@link CompletionStage} that contains as a result an optional which either contains + * the custom type id if it exists or empty if it doesn't. + */ + private String getCustomTypeKey( + @Nonnull final ResourceIdentifier customType, + @Nonnull final String referenceResolutionErrorMessage) + throws ReferenceResolutionException { + + try { + return getKeyFromResourceIdentifier(customType); + } catch (ReferenceResolutionException exception) { + final String errorMessage = + format("%s Reason: %s", referenceResolutionErrorMessage, exception.getMessage()); + throw new ReferenceResolutionException(errorMessage, exception); } + } + + @Nonnull + private CompletionStage fetchAndResolveTypeReference( + @Nonnull final B draftBuilder, + @Nonnull final BiFunction customSetter, + @Nullable final Map customFields, + @Nonnull final String typeKey, + @Nonnull final String referenceResolutionErrorMessage) { + + return typeService + .fetchCachedTypeId(typeKey) + .thenCompose( + resolvedTypeIdOptional -> + resolvedTypeIdOptional + .map( + resolvedTypeId -> + completedFuture( + customSetter.apply( + draftBuilder, ofTypeIdAndJson(resolvedTypeId, customFields)))) + .orElseGet( + () -> { + final String errorMessage = + format( + "%s Reason: %s", + referenceResolutionErrorMessage, + format(TYPE_DOES_NOT_EXIST, typeKey)); + return exceptionallyCompletedFuture( + new ReferenceResolutionException(errorMessage)); + })); + } } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/GenericCustomActionBuilder.java b/src/main/java/com/commercetools/sync/commons/helpers/GenericCustomActionBuilder.java index 36878cfc26..fcb11aa022 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/GenericCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/GenericCustomActionBuilder.java @@ -1,76 +1,81 @@ package com.commercetools.sync.commons.helpers; - import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.Resource; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; /** - * A generic custom update action builder that creates update actions that are of the same type as the Generic type T - * provided by the subclass of this abstract class. For example, if T is a {@link io.sphere.sdk.categories.Category} - * then all the methods would build custom update actions of the type {@link io.sphere.sdk.categories.Category} + * A generic custom update action builder that creates update actions that are of the same type as + * the Generic type T provided by the subclass of this abstract class. For example, if T is a {@link + * io.sphere.sdk.categories.Category} then all the methods would build custom update actions of the + * type {@link io.sphere.sdk.categories.Category} * * @param the type of the resource to create update actions for. */ public interface GenericCustomActionBuilder> { - /** - * Creates a CTP "setCustomType" update action on the given resource {@code T} that removes the custom type set on - * the given resource {@code T}. If the resource that has the custom fields is a secondary resource (e.g. price or - * asset) and not a primary resource (e.g Category, Product, Channel, etc..), the {@code variantId} and the - * {@code objectId} will be used to identify this secondary resource within its container. - * - * @param variantId an optional field which could be used to identify the variant that holds the a resource - * (e.g. asset) which has the custom fields. - * @param objectId an optional field which could be used to identify the id of the resource - * (e.g. asset, price, etc..) which has the custom fields. - * @return a setCustomType update action that removes the custom type from the resource it's requested on. - */ - @Nonnull - UpdateAction buildRemoveCustomTypeAction(@Nullable Integer variantId, @Nullable String objectId); + /** + * Creates a CTP "setCustomType" update action on the given resource {@code T} that removes the + * custom type set on the given resource {@code T}. If the resource that has the custom fields is + * a secondary resource (e.g. price or asset) and not a primary resource (e.g Category, Product, + * Channel, etc..), the {@code variantId} and the {@code objectId} will be used to identify this + * secondary resource within its container. + * + * @param variantId an optional field which could be used to identify the variant that holds the a + * resource (e.g. asset) which has the custom fields. + * @param objectId an optional field which could be used to identify the id of the resource (e.g. + * asset, price, etc..) which has the custom fields. + * @return a setCustomType update action that removes the custom type from the resource it's + * requested on. + */ + @Nonnull + UpdateAction buildRemoveCustomTypeAction( + @Nullable Integer variantId, @Nullable String objectId); - /** - * Creates a CTP "setCustomType" update action on the given resource {@code T}. If the resource that has the custom - * fields is a secondary resource (e.g. Price or asset) and not a primary resource (e.g Category, Product, Channel, - * etc..), the {@code variantId} and the {@code objectId} will be used to identify this secondary resource within - * its container. - * - * @param variantId an optional field which could be used to identify the variant that holds the a - * resource (e.g. asset) which has the custom fields. - * @param objectId an optional field which could be used to identify the id of the resource - * (e.g. asset, price, etc..) which has the custom fields. - * @param customTypeId the id of the new custom type. - * @param customFieldsJsonMap the custom fields map of JSON values. - * @return a setCustomType update action of the type of the resource it's requested on. - */ - @Nonnull - UpdateAction buildSetCustomTypeAction(@Nullable Integer variantId, - @Nullable String objectId, - @Nonnull String customTypeId, - @Nullable Map customFieldsJsonMap); + /** + * Creates a CTP "setCustomType" update action on the given resource {@code T}. If the resource + * that has the custom fields is a secondary resource (e.g. Price or asset) and not a primary + * resource (e.g Category, Product, Channel, etc..), the {@code variantId} and the {@code + * objectId} will be used to identify this secondary resource within its container. + * + * @param variantId an optional field which could be used to identify the variant that holds the a + * resource (e.g. asset) which has the custom fields. + * @param objectId an optional field which could be used to identify the id of the resource (e.g. + * asset, price, etc..) which has the custom fields. + * @param customTypeId the id of the new custom type. + * @param customFieldsJsonMap the custom fields map of JSON values. + * @return a setCustomType update action of the type of the resource it's requested on. + */ + @Nonnull + UpdateAction buildSetCustomTypeAction( + @Nullable Integer variantId, + @Nullable String objectId, + @Nonnull String customTypeId, + @Nullable Map customFieldsJsonMap); - /** - * Creates a CTP "setCustomField" update action on the given resource {@code T} that updates a custom field with - * {@code customFieldName} and a {@code customFieldValue} on the given resource {@code T}. If the resource that has - * the custom fields is a secondary resource (e.g. Price or asset) and not a primary resource (e.g Category, - * Product, Channel, etc..), the {@code variantId} and the {@code objectId} will be used to identify this secondary - * resource within its container. - * - * @param variantId an optional field which could be used to identify the variant that holds the a resource - * (e.g. asset) which has the custom fields. - * @param objectId an optional field which could be used to identify the id of the resource - * (e.g. asset, price, etc..) which has the custom fields. - * @param customFieldName the name of the custom field to update. - * @param customFieldValue the new JSON value of the custom field. - * @return a setCustomField update action on the provided field name, with the provided value - * on the resource it's requested on. - */ - @Nonnull - UpdateAction buildSetCustomFieldAction(@Nullable Integer variantId, - @Nullable String objectId, - @Nullable String customFieldName, - @Nullable JsonNode customFieldValue); + /** + * Creates a CTP "setCustomField" update action on the given resource {@code T} that updates a + * custom field with {@code customFieldName} and a {@code customFieldValue} on the given resource + * {@code T}. If the resource that has the custom fields is a secondary resource (e.g. Price or + * asset) and not a primary resource (e.g Category, Product, Channel, etc..), the {@code + * variantId} and the {@code objectId} will be used to identify this secondary resource within its + * container. + * + * @param variantId an optional field which could be used to identify the variant that holds the a + * resource (e.g. asset) which has the custom fields. + * @param objectId an optional field which could be used to identify the id of the resource (e.g. + * asset, price, etc..) which has the custom fields. + * @param customFieldName the name of the custom field to update. + * @param customFieldValue the new JSON value of the custom field. + * @return a setCustomField update action on the provided field name, with the provided value on + * the resource it's requested on. + */ + @Nonnull + UpdateAction buildSetCustomFieldAction( + @Nullable Integer variantId, + @Nullable String objectId, + @Nullable String customFieldName, + @Nullable JsonNode customFieldValue); } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/GraphQlBaseRequestImpl.java b/src/main/java/com/commercetools/sync/commons/helpers/GraphQlBaseRequestImpl.java index a61f099ab3..93639bf476 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/GraphQlBaseRequestImpl.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/GraphQlBaseRequestImpl.java @@ -1,5 +1,7 @@ package com.commercetools.sync.commons.helpers; +import static java.lang.String.format; + import com.commercetools.sync.commons.models.GraphQlBaseRequest; import com.commercetools.sync.commons.models.GraphQlBaseResource; import com.commercetools.sync.commons.models.GraphQlBaseResult; @@ -8,79 +10,77 @@ import io.sphere.sdk.http.HttpMethod; import io.sphere.sdk.http.HttpResponse; import io.sphere.sdk.json.SphereJsonUtils; - import javax.annotation.Nonnull; import javax.annotation.Nullable; -import static java.lang.String.format; - -public abstract class GraphQlBaseRequestImpl> +public abstract class GraphQlBaseRequestImpl< + T extends GraphQlBaseResult> implements GraphQlBaseRequest { - protected String queryPredicate = null; - protected long limit = 500; + protected String queryPredicate = null; + protected long limit = 500; - /** - * This method adds a predicate string to the request. - * - * @param predicate - a string representing a query predicate. - * @return - an instance of this class. - */ - @Nonnull - @Override - public GraphQlBaseRequest withPredicate(final String predicate) { + /** + * This method adds a predicate string to the request. + * + * @param predicate - a string representing a query predicate. + * @return - an instance of this class. + */ + @Nonnull + @Override + public GraphQlBaseRequest withPredicate(final String predicate) { - this.queryPredicate = predicate; - return this; - } + this.queryPredicate = predicate; + return this; + } - /** - * This method adds a limit to the request. - * - * @param limit - a number representing the query limit parameter - * @return - an instance of this class - */ - @Nonnull - @Override - public GraphQlBaseRequest withLimit(final long limit) { + /** + * This method adds a limit to the request. + * + * @param limit - a number representing the query limit parameter + * @return - an instance of this class + */ + @Nonnull + @Override + public GraphQlBaseRequest withLimit(final long limit) { - this.limit = limit; - return this; - } + this.limit = limit; + return this; + } - @Override - public HttpRequestIntent httpRequestIntent() { + @Override + public HttpRequestIntent httpRequestIntent() { - final String body = format("{\"query\": \"{%s}\"}", buildQueryString()); - return HttpRequestIntent.of(HttpMethod.POST, "/graphql", body); - } + final String body = format("{\"query\": \"{%s}\"}", buildQueryString()); + return HttpRequestIntent.of(HttpMethod.POST, "/graphql", body); + } - /** - * Deserialize the body of {@param httpResponse}. - * - * @param httpResponse httpResponse of the request. - * @param resourceName resource type in the query (i.e "customers", "products", "customObjects") - * @param clazz the type of the class to deserialize. - * @return the deserialized body of the graphql request. - */ - @Nullable - public T deserializeWithResourceName( - @Nonnull final HttpResponse httpResponse, - @Nonnull final String resourceName, - @Nonnull final Class clazz) { + /** + * Deserialize the body of {@param httpResponse}. + * + * @param httpResponse httpResponse of the request. + * @param resourceName resource type in the query (i.e "customers", "products", "customObjects") + * @param clazz the type of the class to deserialize. + * @return the deserialized body of the graphql request. + */ + @Nullable + public T deserializeWithResourceName( + @Nonnull final HttpResponse httpResponse, + @Nonnull final String resourceName, + @Nonnull final Class clazz) { - final JsonNode rootJsonNode = SphereJsonUtils.parse(httpResponse.getResponseBody()); - if (rootJsonNode.isNull()) { - return null; - } - JsonNode result = rootJsonNode.get("data").get(resourceName); - return SphereJsonUtils.readObject(result, clazz); + final JsonNode rootJsonNode = SphereJsonUtils.parse(httpResponse.getResponseBody()); + if (rootJsonNode.isNull()) { + return null; } + JsonNode result = rootJsonNode.get("data").get(resourceName); + return SphereJsonUtils.readObject(result, clazz); + } - /** - * This method builds a string matching the required format needed in the CTP graphql API. - * - * @return a string representing a graphql query - */ - protected abstract String buildQueryString(); + /** + * This method builds a string matching the required format needed in the CTP graphql API. + * + * @return a string representing a graphql query + */ + protected abstract String buildQueryString(); } diff --git a/src/main/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequest.java b/src/main/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequest.java index 791309fe69..6c4953c061 100644 --- a/src/main/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequest.java +++ b/src/main/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequest.java @@ -1,92 +1,95 @@ package com.commercetools.sync.commons.helpers; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.models.GraphQlQueryResources; import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.client.SphereRequest; import io.sphere.sdk.http.HttpResponse; - +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Set; - -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.joining; -import static org.apache.commons.lang3.StringUtils.isBlank; /** - * A SphereRequest implementation to allow {@link SphereClient} to execute graphQL queries on CTP. It provides a - * POST request to the CTP graphql API containing body to fetch a set of ids matching given keys of a resource - * defined in endpoint parameter. + * A SphereRequest implementation to allow {@link SphereClient} to execute graphQL queries on CTP. + * It provides a POST request to the CTP graphql API containing body to fetch a set of ids matching + * given keys of a resource defined in endpoint parameter. */ -public class ResourceKeyIdGraphQlRequest extends GraphQlBaseRequestImpl { - protected final Set keysToSearch; - protected final GraphQlQueryResources resource; - - /** - * Takes {@code keysToSearch} and query resource name {@link GraphQlQueryResources} to instantiate a new - * {@link ResourceKeyIdGraphQlRequest} instance, which is an implementation of the {@link SphereRequest}. - * - * @param keysToSearch - a set of keys to fetch matching ids for. - * @param resource - a string representing the name of the resource endpoint. - */ - public ResourceKeyIdGraphQlRequest(@Nonnull final Set keysToSearch, - @Nonnull final GraphQlQueryResources resource) { +public class ResourceKeyIdGraphQlRequest + extends GraphQlBaseRequestImpl { + protected final Set keysToSearch; + protected final GraphQlQueryResources resource; - this.keysToSearch = requireNonNull(keysToSearch); - this.resource = resource; - } + /** + * Takes {@code keysToSearch} and query resource name {@link GraphQlQueryResources} to instantiate + * a new {@link ResourceKeyIdGraphQlRequest} instance, which is an implementation of the {@link + * SphereRequest}. + * + * @param keysToSearch - a set of keys to fetch matching ids for. + * @param resource - a string representing the name of the resource endpoint. + */ + public ResourceKeyIdGraphQlRequest( + @Nonnull final Set keysToSearch, @Nonnull final GraphQlQueryResources resource) { - @Nullable - @Override - public ResourceKeyIdGraphQlResult deserialize(final HttpResponse httpResponse) { - return deserializeWithResourceName(httpResponse, resource.getName(), ResourceKeyIdGraphQlResult.class); - } + this.keysToSearch = requireNonNull(keysToSearch); + this.resource = resource; + } - /** - * This method builds a string matching the required format to query a set of ids matching given keys of a - * resource using the CTP graphql API - * - * @return a string representing a graphql query - */ - @Nonnull - @Override - protected String buildQueryString() { + @Nullable + @Override + public ResourceKeyIdGraphQlResult deserialize(final HttpResponse httpResponse) { + return deserializeWithResourceName( + httpResponse, resource.getName(), ResourceKeyIdGraphQlResult.class); + } - return format( - "%s(limit: %d, where: \\\"%s\\\", sort: [\\\"id asc\\\"]) { results { id key } }", - this.resource.getName(), this.limit, createWhereQuery(keysToSearch)); - } + /** + * This method builds a string matching the required format to query a set of ids matching given + * keys of a resource using the CTP graphql API + * + * @return a string representing a graphql query + */ + @Nonnull + @Override + protected String buildQueryString() { - @Nonnull - private String createWhereQuery(@Nonnull final Set keys) { - // The where in the graphql query should look like this in the end => `where: "key in (\"key1\", - // \"key2\")"` - // So we need an escaping backslash before the quote. So to add this: - // We need 1 backslash (2 in java) to escape the quote in the graphql query. - // We need 2 backslashes (4 in java) to escape the backslash in the JSON payload string. - // We need 1 extra backslash to escape the quote in the java string - // hence: 7 backslashes: - final String backslashQuote = "\\\\\\\""; - final String commaSeparatedKeys = - keys.stream() - .filter(key -> !isBlank(key)) - .collect( - joining( - format("%s, %s", backslashQuote, backslashQuote), - backslashQuote, - backslashQuote)); + return format( + "%s(limit: %d, where: \\\"%s\\\", sort: [\\\"id asc\\\"]) { results { id key } }", + this.resource.getName(), this.limit, createWhereQuery(keysToSearch)); + } - String whereQuery = createWhereQuery(commaSeparatedKeys); - return isBlank(this.queryPredicate) ? whereQuery : format("%s AND %s", whereQuery, queryPredicate); - } + @Nonnull + private String createWhereQuery(@Nonnull final Set keys) { + // The where in the graphql query should look like this in the end => `where: "key in + // (\"key1\", + // \"key2\")"` + // So we need an escaping backslash before the quote. So to add this: + // We need 1 backslash (2 in java) to escape the quote in the graphql query. + // We need 2 backslashes (4 in java) to escape the backslash in the JSON payload string. + // We need 1 extra backslash to escape the quote in the java string + // hence: 7 backslashes: + final String backslashQuote = "\\\\\\\""; + final String commaSeparatedKeys = + keys.stream() + .filter(key -> !isBlank(key)) + .collect( + joining( + format("%s, %s", backslashQuote, backslashQuote), + backslashQuote, + backslashQuote)); - @Nonnull - private static String createWhereQuery(@Nonnull final String commaSeparatedKeys) { + String whereQuery = createWhereQuery(commaSeparatedKeys); + return isBlank(this.queryPredicate) + ? whereQuery + : format("%s AND %s", whereQuery, queryPredicate); + } - return format("key in (%s)", commaSeparatedKeys); - } + @Nonnull + private static String createWhereQuery(@Nonnull final String commaSeparatedKeys) { + return format("key in (%s)", commaSeparatedKeys); + } } - diff --git a/src/main/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequest.java b/src/main/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequest.java index c344ae6976..188d8a9d6d 100644 --- a/src/main/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequest.java +++ b/src/main/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequest.java @@ -1,63 +1,65 @@ package com.commercetools.sync.commons.models; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.helpers.GraphQlBaseRequestImpl; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.client.SphereRequest; import io.sphere.sdk.http.HttpResponse; - +import java.time.Instant; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.time.Instant; - -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.isBlank; /** - * A SphereRequest implementation to allow {@link SphereClient} to execute graphQL queries on CTP. It provides a - * POST request to the CTP graphql API containing body to fetch custom object resource with the given container name - * defined in endpoint parameter. + * A SphereRequest implementation to allow {@link SphereClient} to execute graphQL queries on CTP. + * It provides a POST request to the CTP graphql API containing body to fetch custom object resource + * with the given container name defined in endpoint parameter. */ -public class FetchCustomObjectsGraphQlRequest extends GraphQlBaseRequestImpl { - - private final String container; - private final Instant lastModifiedAt; - private final String resourceName = GraphQlQueryResources.CUSTOM_OBJECTS.getName(); - - /** - * Takes {@code container} and {@code lastModifiedAt} to instantiate a new {@link FetchCustomObjectsGraphQlRequest} - * instance, which is an implementation of the {@link SphereRequest}. - * - * @param container - A namespace to group custom objects. - * @param lastModifiedAt - lastModifiedAt will be used in where param. - */ - public FetchCustomObjectsGraphQlRequest( - @Nonnull final String container, - @Nonnull final Instant lastModifiedAt) { - - this.container = container; - this.lastModifiedAt = lastModifiedAt; - } - - @Nullable - @Override - public ResourceKeyIdGraphQlResult deserialize(final HttpResponse httpResponse) { - - return deserializeWithResourceName(httpResponse, resourceName, ResourceKeyIdGraphQlResult.class); - } - - @Nonnull - @Override - protected String buildQueryString() { - - return format( - "%s(container: \\\"%s\\\", limit: %d, where: \\\"%s\\\", sort: [\\\"id asc\\\"]) { results { id key } }", - this.resourceName, this.container, this.limit, createWhereQuery()); - } - - @Nonnull - private String createWhereQuery() { - final String whereQuery = format("lastModifiedAt < \\\\\\\"%s\\\\\\\"", lastModifiedAt); - return isBlank(this.queryPredicate) ? whereQuery : format("%s AND %s", whereQuery, queryPredicate); - } -} +public class FetchCustomObjectsGraphQlRequest + extends GraphQlBaseRequestImpl { + + private final String container; + private final Instant lastModifiedAt; + private final String resourceName = GraphQlQueryResources.CUSTOM_OBJECTS.getName(); + + /** + * Takes {@code container} and {@code lastModifiedAt} to instantiate a new {@link + * FetchCustomObjectsGraphQlRequest} instance, which is an implementation of the {@link + * SphereRequest}. + * + * @param container - A namespace to group custom objects. + * @param lastModifiedAt - lastModifiedAt will be used in where param. + */ + public FetchCustomObjectsGraphQlRequest( + @Nonnull final String container, @Nonnull final Instant lastModifiedAt) { + + this.container = container; + this.lastModifiedAt = lastModifiedAt; + } + + @Nullable + @Override + public ResourceKeyIdGraphQlResult deserialize(final HttpResponse httpResponse) { + return deserializeWithResourceName( + httpResponse, resourceName, ResourceKeyIdGraphQlResult.class); + } + + @Nonnull + @Override + protected String buildQueryString() { + + return format( + "%s(container: \\\"%s\\\", limit: %d, where: \\\"%s\\\", sort: [\\\"id asc\\\"]) { results { id key } }", + this.resourceName, this.container, this.limit, createWhereQuery()); + } + + @Nonnull + private String createWhereQuery() { + final String whereQuery = format("lastModifiedAt < \\\\\\\"%s\\\\\\\"", lastModifiedAt); + return isBlank(this.queryPredicate) + ? whereQuery + : format("%s AND %s", whereQuery, queryPredicate); + } +} diff --git a/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseRequest.java b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseRequest.java index ceb6324ecb..3d4e5c0176 100644 --- a/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseRequest.java +++ b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseRequest.java @@ -2,21 +2,21 @@ import io.sphere.sdk.client.SphereRequest; -public interface GraphQlBaseRequest> extends SphereRequest -{ - /** - * This method adds a predicate string to the request. - * - * @param predicate - a string representing a query predicate. - * @return - an instance of this class. - */ - GraphQlBaseRequest withPredicate(final String predicate); +public interface GraphQlBaseRequest> + extends SphereRequest { + /** + * This method adds a predicate string to the request. + * + * @param predicate - a string representing a query predicate. + * @return - an instance of this class. + */ + GraphQlBaseRequest withPredicate(final String predicate); - /** - * This method adds a limit to the request. - * - * @param limit - a number representing the query limit parameter - * @return - an instance of this class - */ - GraphQlBaseRequest withLimit(final long limit); + /** + * This method adds a limit to the request. + * + * @param limit - a number representing the query limit parameter + * @return - an instance of this class + */ + GraphQlBaseRequest withLimit(final long limit); } diff --git a/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResource.java b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResource.java index e5c4216010..91df567f94 100644 --- a/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResource.java +++ b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResource.java @@ -1,5 +1,5 @@ package com.commercetools.sync.commons.models; public interface GraphQlBaseResource { - String getId(); + String getId(); } diff --git a/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResult.java b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResult.java index 08d2c04f1e..354e7280ac 100644 --- a/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResult.java +++ b/src/main/java/com/commercetools/sync/commons/models/GraphQlBaseResult.java @@ -3,5 +3,5 @@ import java.util.Set; public interface GraphQlBaseResult { - Set getResults(); + Set getResults(); } diff --git a/src/main/java/com/commercetools/sync/commons/models/GraphQlQueryResources.java b/src/main/java/com/commercetools/sync/commons/models/GraphQlQueryResources.java index ca5a136420..5e9e2899ea 100644 --- a/src/main/java/com/commercetools/sync/commons/models/GraphQlQueryResources.java +++ b/src/main/java/com/commercetools/sync/commons/models/GraphQlQueryResources.java @@ -1,25 +1,25 @@ package com.commercetools.sync.commons.models; public enum GraphQlQueryResources { - CATEGORIES("categories"), - CHANNELS("channels"), - CUSTOMER_GROUPS("customerGroups"), - CUSTOMERS("customers"), - PRODUCTS("products"), - PRODUCT_TYPES("productTypes"), - STATES("states"), - TAX_CATEGORIES("taxCategories"), - TYPES("typeDefinitions"), - SHOPPING_LISTS("shoppingLists"), - CUSTOM_OBJECTS("customObjects"); + CATEGORIES("categories"), + CHANNELS("channels"), + CUSTOMER_GROUPS("customerGroups"), + CUSTOMERS("customers"), + PRODUCTS("products"), + PRODUCT_TYPES("productTypes"), + STATES("states"), + TAX_CATEGORIES("taxCategories"), + TYPES("typeDefinitions"), + SHOPPING_LISTS("shoppingLists"), + CUSTOM_OBJECTS("customObjects"); - private final String name; + private final String name; - GraphQlQueryResources(final String name) { - this.name = name; - } + GraphQlQueryResources(final String name) { + this.name = name; + } - public String getName() { - return name; - } + public String getName() { + return name; + } } diff --git a/src/main/java/com/commercetools/sync/commons/models/ResourceKeyId.java b/src/main/java/com/commercetools/sync/commons/models/ResourceKeyId.java index ec1fe9af2e..e466e811fa 100644 --- a/src/main/java/com/commercetools/sync/commons/models/ResourceKeyId.java +++ b/src/main/java/com/commercetools/sync/commons/models/ResourceKeyId.java @@ -2,25 +2,26 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; - import javax.annotation.Nonnull; public final class ResourceKeyId implements GraphQlBaseResource { - private final String key; - private final String id; + private final String key; + private final String id; - @JsonCreator - public ResourceKeyId(@JsonProperty("key") @Nonnull final String key, @JsonProperty("id") @Nonnull final String id) { - this.key = key; - this.id = id; - } + @JsonCreator + public ResourceKeyId( + @JsonProperty("key") @Nonnull final String key, + @JsonProperty("id") @Nonnull final String id) { + this.key = key; + this.id = id; + } - public String getKey() { - return key; - } + public String getKey() { + return key; + } - @Override - public String getId() { - return id; - } + @Override + public String getId() { + return id; + } } diff --git a/src/main/java/com/commercetools/sync/commons/models/ResourceKeyIdGraphQlResult.java b/src/main/java/com/commercetools/sync/commons/models/ResourceKeyIdGraphQlResult.java index 031ed15dae..744a738e62 100644 --- a/src/main/java/com/commercetools/sync/commons/models/ResourceKeyIdGraphQlResult.java +++ b/src/main/java/com/commercetools/sync/commons/models/ResourceKeyIdGraphQlResult.java @@ -2,19 +2,17 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.Set; public class ResourceKeyIdGraphQlResult implements GraphQlBaseResult { - private final Set results; - - @JsonCreator - protected ResourceKeyIdGraphQlResult(@JsonProperty("results") final Set results) { - this.results = results; - } + private final Set results; - public Set getResults() { - return results; - } + @JsonCreator + protected ResourceKeyIdGraphQlResult(@JsonProperty("results") final Set results) { + this.results = results; + } + public Set getResults() { + return results; + } } diff --git a/src/main/java/com/commercetools/sync/commons/models/WaitingToBeResolved.java b/src/main/java/com/commercetools/sync/commons/models/WaitingToBeResolved.java index 9e0a803044..d40fa290b0 100644 --- a/src/main/java/com/commercetools/sync/commons/models/WaitingToBeResolved.java +++ b/src/main/java/com/commercetools/sync/commons/models/WaitingToBeResolved.java @@ -1,67 +1,66 @@ package com.commercetools.sync.commons.models; import io.sphere.sdk.products.ProductDraft; - -import javax.annotation.Nonnull; import java.util.Objects; import java.util.Set; +import javax.annotation.Nonnull; public final class WaitingToBeResolved { - private ProductDraft productDraft; - private Set missingReferencedProductKeys; + private ProductDraft productDraft; + private Set missingReferencedProductKeys; - /** - * Represents a productDraft that is waiting for some product references, which are on this productDraft as - * attributes, to be resolved. - * - * @param productDraft product draft which has irresolvable references as attributes. - * @param missingReferencedProductKeys product keys of irresolvable references. - */ - public WaitingToBeResolved( - @Nonnull final ProductDraft productDraft, - @Nonnull final Set missingReferencedProductKeys) { - this.productDraft = productDraft; - this.missingReferencedProductKeys = missingReferencedProductKeys; - } + /** + * Represents a productDraft that is waiting for some product references, which are on this + * productDraft as attributes, to be resolved. + * + * @param productDraft product draft which has irresolvable references as attributes. + * @param missingReferencedProductKeys product keys of irresolvable references. + */ + public WaitingToBeResolved( + @Nonnull final ProductDraft productDraft, + @Nonnull final Set missingReferencedProductKeys) { + this.productDraft = productDraft; + this.missingReferencedProductKeys = missingReferencedProductKeys; + } - // Needed for the 'com.fasterxml.jackson' deserialization, for example, when fetching - // from CTP custom objects. - public WaitingToBeResolved() { - } + // Needed for the 'com.fasterxml.jackson' deserialization, for example, when fetching + // from CTP custom objects. + public WaitingToBeResolved() {} - @Nonnull - public ProductDraft getProductDraft() { - return productDraft; - } + @Nonnull + public ProductDraft getProductDraft() { + return productDraft; + } - @Nonnull - public Set getMissingReferencedProductKeys() { - return missingReferencedProductKeys; - } + @Nonnull + public Set getMissingReferencedProductKeys() { + return missingReferencedProductKeys; + } - public void setProductDraft(@Nonnull final ProductDraft productDraft) { - this.productDraft = productDraft; - } + public void setProductDraft(@Nonnull final ProductDraft productDraft) { + this.productDraft = productDraft; + } - public void setMissingReferencedProductKeys(@Nonnull final Set missingReferencedProductKeys) { - this.missingReferencedProductKeys = missingReferencedProductKeys; - } + public void setMissingReferencedProductKeys( + @Nonnull final Set missingReferencedProductKeys) { + this.missingReferencedProductKeys = missingReferencedProductKeys; + } - @Override - public boolean equals(final Object other) { - if (this == other) { - return true; - } - if (!(other instanceof WaitingToBeResolved)) { - return false; - } - final WaitingToBeResolved that = (WaitingToBeResolved) other; - return Objects.equals(getProductDraft().getKey(), that.getProductDraft().getKey()) - && getMissingReferencedProductKeys().equals(that.getMissingReferencedProductKeys()); + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; } - - @Override - public int hashCode() { - return Objects.hash(getProductDraft().getKey(), getMissingReferencedProductKeys()); + if (!(other instanceof WaitingToBeResolved)) { + return false; } + final WaitingToBeResolved that = (WaitingToBeResolved) other; + return Objects.equals(getProductDraft().getKey(), that.getProductDraft().getKey()) + && getMissingReferencedProductKeys().equals(that.getMissingReferencedProductKeys()); + } + + @Override + public int hashCode() { + return Objects.hash(getProductDraft().getKey(), getMissingReferencedProductKeys()); + } } diff --git a/src/main/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTransitions.java b/src/main/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTransitions.java index ddc214f978..739273b3b8 100644 --- a/src/main/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTransitions.java +++ b/src/main/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTransitions.java @@ -1,67 +1,64 @@ package com.commercetools.sync.commons.models; import io.sphere.sdk.states.StateDraft; - -import javax.annotation.Nonnull; import java.util.Objects; import java.util.Set; +import javax.annotation.Nonnull; public final class WaitingToBeResolvedTransitions { - private StateDraft stateDraft; - private Set missingTransitionStateKeys; + private StateDraft stateDraft; + private Set missingTransitionStateKeys; - /** - * Represents a statedraft that is waiting for some state references, which are on this stateDraft as - * transitions, to be resolved. - * - * @param stateDraft state draft which has irresolvable references as transitions. - * @param missingTransitionStateKeys state keys of irresolvable transition states. - */ - public WaitingToBeResolvedTransitions( - @Nonnull final StateDraft stateDraft, - @Nonnull final Set missingTransitionStateKeys) { - this.stateDraft = stateDraft; - this.missingTransitionStateKeys = missingTransitionStateKeys; - } + /** + * Represents a statedraft that is waiting for some state references, which are on this stateDraft + * as transitions, to be resolved. + * + * @param stateDraft state draft which has irresolvable references as transitions. + * @param missingTransitionStateKeys state keys of irresolvable transition states. + */ + public WaitingToBeResolvedTransitions( + @Nonnull final StateDraft stateDraft, @Nonnull final Set missingTransitionStateKeys) { + this.stateDraft = stateDraft; + this.missingTransitionStateKeys = missingTransitionStateKeys; + } - // Needed for the 'com.fasterxml.jackson' deserialization, for example, when fetching - // from CTP custom objects. - public WaitingToBeResolvedTransitions() { - } + // Needed for the 'com.fasterxml.jackson' deserialization, for example, when fetching + // from CTP custom objects. + public WaitingToBeResolvedTransitions() {} - @Nonnull - public StateDraft getStateDraft() { - return stateDraft; - } + @Nonnull + public StateDraft getStateDraft() { + return stateDraft; + } - @Nonnull - public Set getMissingTransitionStateKeys() { - return missingTransitionStateKeys; - } + @Nonnull + public Set getMissingTransitionStateKeys() { + return missingTransitionStateKeys; + } - public void setStateDraft(@Nonnull final StateDraft stateDraft) { - this.stateDraft = stateDraft; - } + public void setStateDraft(@Nonnull final StateDraft stateDraft) { + this.stateDraft = stateDraft; + } - public void setMissingTransitionStateKeys(@Nonnull final Set missingTransitionStateKeys) { - this.missingTransitionStateKeys = missingTransitionStateKeys; - } + public void setMissingTransitionStateKeys(@Nonnull final Set missingTransitionStateKeys) { + this.missingTransitionStateKeys = missingTransitionStateKeys; + } - @Override - public boolean equals(final Object other) { - if (this == other) { - return true; - } - if (!(other instanceof WaitingToBeResolvedTransitions)) { - return false; - } - final WaitingToBeResolvedTransitions that = (WaitingToBeResolvedTransitions) other; - return Objects.equals(getStateDraft().getKey(), that.getStateDraft().getKey()) - && getMissingTransitionStateKeys().equals(that.getMissingTransitionStateKeys()); + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; } - - @Override - public int hashCode() { - return Objects.hash(getStateDraft().getKey(), getMissingTransitionStateKeys()); + if (!(other instanceof WaitingToBeResolvedTransitions)) { + return false; } + final WaitingToBeResolvedTransitions that = (WaitingToBeResolvedTransitions) other; + return Objects.equals(getStateDraft().getKey(), that.getStateDraft().getKey()) + && getMissingTransitionStateKeys().equals(that.getMissingTransitionStateKeys()); + } + + @Override + public int hashCode() { + return Objects.hash(getStateDraft().getKey(), getMissingTransitionStateKeys()); + } } diff --git a/src/main/java/com/commercetools/sync/commons/utils/AssetReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/commons/utils/AssetReferenceResolutionUtils.java index 1fc49e462d..4cc3fd46ca 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/AssetReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/AssetReferenceResolutionUtils.java @@ -1,39 +1,37 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; +import static java.util.stream.Collectors.toList; + import io.sphere.sdk.models.Asset; import io.sphere.sdk.models.AssetDraft; import io.sphere.sdk.models.AssetDraftBuilder; - -import javax.annotation.Nonnull; import java.util.List; - -import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class AssetReferenceResolutionUtils { - /** - * Takes an asset list that is supposed to have all its assets' custom references expanded in order to be able to - * fetch the keys for the custom references. This method returns as a result a {@link List} of {@link AssetDraft} - * that has all custom references with keys. - * - *

Any custom reference that is not expanded will have its id in place and not replaced by the key. - * - * @param assets the list of assets to replace their custom ids with keys. - * @return a {@link List} of {@link AssetDraft} that has all channel references with keys. - */ - @Nonnull - public static List mapToAssetDrafts(@Nonnull final List assets) { - return assets.stream().map(asset -> - AssetDraftBuilder.of(asset) - .custom(mapToCustomFieldsDraft(asset)).build()) - .collect(toList()); - } + /** + * Takes an asset list that is supposed to have all its assets' custom references expanded in + * order to be able to fetch the keys for the custom references. This method returns as a result a + * {@link List} of {@link AssetDraft} that has all custom references with keys. + * + *

Any custom reference that is not expanded will have its id in place and not replaced by the + * key. + * + * @param assets the list of assets to replace their custom ids with keys. + * @return a {@link List} of {@link AssetDraft} that has all channel references with keys. + */ + @Nonnull + public static List mapToAssetDrafts(@Nonnull final List assets) { + return assets.stream() + .map(asset -> AssetDraftBuilder.of(asset).custom(mapToCustomFieldsDraft(asset)).build()) + .collect(toList()); + } - private AssetReferenceResolutionUtils() { - } + private AssetReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/AssetsUpdateActionUtils.java b/src/main/java/com/commercetools/sync/commons/utils/AssetsUpdateActionUtils.java index bb59e8a1a8..2361718a26 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/AssetsUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/AssetsUpdateActionUtils.java @@ -1,5 +1,15 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.DuplicateKeyException; @@ -9,9 +19,6 @@ import io.sphere.sdk.models.Asset; import io.sphere.sdk.models.AssetDraft; import io.sphere.sdk.models.Resource; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -22,267 +29,294 @@ import java.util.Optional; import java.util.Set; import java.util.stream.IntStream; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class AssetsUpdateActionUtils { - public static final String ASSET_KEY_NOT_SET = "Asset with %s has no defined key. Keys are required for " - + "asset matching."; + public static final String ASSET_KEY_NOT_SET = + "Asset with %s has no defined key. Keys are required for " + "asset matching."; + /** + * Compares a list of {@link Asset}s with a list of {@link AssetDraft}s. The method serves as a + * generic implementation for assets syncing. The method takes in functions for building the + * required update actions ( AddAsset, RemoveAsset, ChangeAssetOrder and 1-1 update actions on + * assets (e.g. changeAssetName, setAssetDescription, etc..) for the required resource. + * + *

If the list of new {@link AssetDraft}s is {@code null}, then remove actions are built for + * every existing asset in the {@code oldAssets} list. + * + * @param the type of the resource the asset update actions are built for. + * @param the type of the draft, which contains the changes the asset update actions are built + * for. + * @param oldResource resource from a target project, whose asset should be updated. + * @param newResource resource draft from a source project, which contains the asset to update. + * @param oldAssets the old list of assets. + * @param newAssetDrafts the new list of asset drafts. + * @param assetActionFactory factory responsible for building asset update actions. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the warn callback within the utility + * @return a list of asset update actions on the resource of type T if the list of assets is not + * identical. Otherwise, if the assets are identical, an empty list is returned. + * @throws BuildUpdateActionException in case there are asset drafts with duplicate keys. + */ + @Nonnull + public static List> buildAssetsUpdateActions( + @Nonnull final T oldResource, + @Nonnull final D newResource, + @Nonnull final List oldAssets, + @Nullable final List newAssetDrafts, + @Nonnull final AssetActionFactory assetActionFactory, + @Nonnull final BaseSyncOptions syncOptions) + throws BuildUpdateActionException { - /** - * Compares a list of {@link Asset}s with a list of {@link AssetDraft}s. The method serves as a generic - * implementation for assets syncing. The method takes in functions for building the required update actions ( - * AddAsset, RemoveAsset, ChangeAssetOrder and 1-1 update actions on assets (e.g. changeAssetName, - * setAssetDescription, etc..) for the required resource. - * - *

If the list of new {@link AssetDraft}s is {@code null}, then remove actions are built for every existing asset - * in the {@code oldAssets} list. - * @param the type of the resource the asset update actions are built for. - * @param the type of the draft, which contains the changes - * the asset update actions are built for. - * @param oldResource resource from a target project, whose asset should be updated. - * @param newResource resource draft from a source project, which contains the asset to update. - * @param oldAssets the old list of assets. - * @param newAssetDrafts the new list of asset drafts. - * @param assetActionFactory factory responsible for building asset update actions. - * @param syncOptions responsible for supplying the sync options to the sync utility method. - * It is used for triggering the warn callback within the utility - * @return a list of asset update actions on the resource of type T if the list of assets is not identical. - * Otherwise, if the assets are identical, an empty list is returned. - * @throws BuildUpdateActionException in case there are asset drafts with duplicate keys. - */ - @Nonnull - public static List> buildAssetsUpdateActions( - @Nonnull final T oldResource, - @Nonnull final D newResource, - @Nonnull final List oldAssets, - @Nullable final List newAssetDrafts, - @Nonnull final AssetActionFactory assetActionFactory, - @Nonnull final BaseSyncOptions syncOptions) - throws BuildUpdateActionException { - - if (newAssetDrafts != null) { - return buildAssetsUpdateActionsWithNewAssetDrafts(oldResource, newResource, oldAssets, newAssetDrafts, - assetActionFactory, - syncOptions); - } else { - return oldAssets.stream() - .map(Asset::getKey) - .map(assetActionFactory::buildRemoveAssetAction) - .collect(toCollection(ArrayList::new)); - } + if (newAssetDrafts != null) { + return buildAssetsUpdateActionsWithNewAssetDrafts( + oldResource, newResource, oldAssets, newAssetDrafts, assetActionFactory, syncOptions); + } else { + return oldAssets.stream() + .map(Asset::getKey) + .map(assetActionFactory::buildRemoveAssetAction) + .collect(toCollection(ArrayList::new)); } + } - /** - * Compares a list of {@link Asset}s with a list of {@link AssetDraft}s. The method serves as a generic - * implementation for assets syncing. The method takes in functions for building the required update actions ( - * AddAsset, RemoveAsset, ChangeAssetOrder and 1-1 update actions on assets (e.g. changeAssetName, - * setAssetDescription, etc..) for the required resource. - * - * @param oldAssets the old list of assets. - * @param newAssetDrafts the new list of asset drafts. - * @param assetActionFactory factory responsible for building asset update actions. - * @param the type of the resource the asset update actions are built for. - * @param syncOptions responsible for supplying the sync options to the sync utility method. - * It is used for triggering the warn callback within the utility - * @return a list of asset update actions on the resource of type T if the list of assets is not identical. - * Otherwise, if the assets are identical, an empty list is returned. - * @throws BuildUpdateActionException in case there are asset drafts with duplicate keys. - */ - @Nonnull - private static List> buildAssetsUpdateActionsWithNewAssetDrafts( - @Nonnull final T oldResource, - @Nonnull final D newResource, - @Nonnull final List oldAssets, - @Nonnull final List newAssetDrafts, - @Nonnull final AssetActionFactory assetActionFactory, - @Nonnull final BaseSyncOptions syncOptions) - throws BuildUpdateActionException { + /** + * Compares a list of {@link Asset}s with a list of {@link AssetDraft}s. The method serves as a + * generic implementation for assets syncing. The method takes in functions for building the + * required update actions ( AddAsset, RemoveAsset, ChangeAssetOrder and 1-1 update actions on + * assets (e.g. changeAssetName, setAssetDescription, etc..) for the required resource. + * + * @param oldAssets the old list of assets. + * @param newAssetDrafts the new list of asset drafts. + * @param assetActionFactory factory responsible for building asset update actions. + * @param the type of the resource the asset update actions are built for. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the warn callback within the utility + * @return a list of asset update actions on the resource of type T if the list of assets is not + * identical. Otherwise, if the assets are identical, an empty list is returned. + * @throws BuildUpdateActionException in case there are asset drafts with duplicate keys. + */ + @Nonnull + private static + List> buildAssetsUpdateActionsWithNewAssetDrafts( + @Nonnull final T oldResource, + @Nonnull final D newResource, + @Nonnull final List oldAssets, + @Nonnull final List newAssetDrafts, + @Nonnull final AssetActionFactory assetActionFactory, + @Nonnull final BaseSyncOptions syncOptions) + throws BuildUpdateActionException { - // Asset set that has only the keys of the assets which should be removed, this is used in the method - // #buildChangeAssetOrderUpdateAction in order to compare the state of the asset lists after the remove actions - // have already been applied. - final HashSet removedAssetKeys = new HashSet<>(); + // Asset set that has only the keys of the assets which should be removed, this is used in the + // method + // #buildChangeAssetOrderUpdateAction in order to compare the state of the asset lists after the + // remove actions + // have already been applied. + final HashSet removedAssetKeys = new HashSet<>(); - final Map oldAssetsKeyMap = new HashMap<>(); + final Map oldAssetsKeyMap = new HashMap<>(); - oldAssets.forEach(asset -> { - String assetKey = asset.getKey(); + oldAssets.forEach( + asset -> { + String assetKey = asset.getKey(); + if (isNotBlank(assetKey)) { + oldAssetsKeyMap.put(assetKey, asset); + } else { + syncOptions.applyWarningCallback( + new SyncException(format(ASSET_KEY_NOT_SET, "id: " + asset.getId())), asset, null); + } + }); + final Map newAssetDraftsKeyMap = new HashMap<>(); + try { + newAssetDrafts.forEach( + newAsset -> { + String assetKey = newAsset.getKey(); if (isNotBlank(assetKey)) { - oldAssetsKeyMap.put(assetKey, asset); + newAssetDraftsKeyMap.merge( + assetKey, + newAsset, + (assetDraftA, assetDraftB) -> { + throw new DuplicateKeyException( + "Supplied asset drafts have duplicate keys. Asset keys are" + + " expected to be unique inside their container (a product variant or a category)."); + }); + } else { - syncOptions.applyWarningCallback(new SyncException(format(ASSET_KEY_NOT_SET, "id: " + asset.getId())), - asset, null); + syncOptions.applyWarningCallback( + new SyncException(format(ASSET_KEY_NOT_SET, "name: " + newAsset.getName())), + null, + newAsset); } - }); - final Map newAssetDraftsKeyMap = new HashMap<>(); - try { - newAssetDrafts.forEach(newAsset -> { - String assetKey = newAsset.getKey(); - if (isNotBlank(assetKey)) { - newAssetDraftsKeyMap.merge(assetKey, newAsset, (assetDraftA, assetDraftB) -> { - throw new DuplicateKeyException("Supplied asset drafts have duplicate keys. Asset keys are" - + " expected to be unique inside their container (a product variant or a category)."); - } - ); + }); - } else { - syncOptions.applyWarningCallback(new SyncException(format(ASSET_KEY_NOT_SET, - "name: " + newAsset.getName())), null, newAsset); - } - }); + } catch (final DuplicateKeyException exception) { + throw new BuildUpdateActionException(exception); + } - } catch (final DuplicateKeyException exception) { - throw new BuildUpdateActionException(exception); - } + // It is important to have a changeAssetOrder action before an addAsset action, since + // changeAssetOrder requires + // asset ids for sorting them, and new assets don't have ids yet since they are generated + // by CTP after an asset is created. Therefore, the order of update actions must be: - // It is important to have a changeAssetOrder action before an addAsset action, since changeAssetOrder requires - // asset ids for sorting them, and new assets don't have ids yet since they are generated - // by CTP after an asset is created. Therefore, the order of update actions must be: + // 1. Remove or compare if matching. + final List> updateActions = + buildRemoveAssetOrAssetUpdateActions( + oldResource, + newResource, + oldAssets, + removedAssetKeys, + newAssetDraftsKeyMap, + assetActionFactory); - //1. Remove or compare if matching. - final List> updateActions = - buildRemoveAssetOrAssetUpdateActions(oldResource, newResource, oldAssets, removedAssetKeys, - newAssetDraftsKeyMap, assetActionFactory); + // 2. Compare ordering of assets and add a ChangeAssetOrder action if needed. + buildChangeAssetOrderUpdateAction( + oldAssets, newAssetDrafts, removedAssetKeys, assetActionFactory) + .ifPresent(updateActions::add); - //2. Compare ordering of assets and add a ChangeAssetOrder action if needed. - buildChangeAssetOrderUpdateAction(oldAssets, newAssetDrafts, removedAssetKeys, assetActionFactory) - .ifPresent(updateActions::add); + // For every new asset draft, If it doesn't exist in the old assets, then add an AddAsset action + // to the list + // of update actions. + updateActions.addAll( + buildAddAssetUpdateActions(newAssetDrafts, oldAssetsKeyMap, assetActionFactory)); - // For every new asset draft, If it doesn't exist in the old assets, then add an AddAsset action to the list - // of update actions. - updateActions.addAll(buildAddAssetUpdateActions(newAssetDrafts, oldAssetsKeyMap, assetActionFactory)); + return updateActions; + } - return updateActions; - } - - /** - * Checks if there are any asset which are not existing in the {@code newAssetDraftsKeyMap}. If there are, - * then "remove" asset update actions are built using the instance of {@link AssetActionFactory} supplied to remove - * these assets. Otherwise, if there are no assets that should be removed, an empty list is returned. - * - * @param oldAssets the list of old {@link Asset}s - * @param removedAssetKeys a set containing keys of removed assets. - * @param newAssetDraftsKeyMap a map of keys to asset drafts of the new list of asset drafts - * @param assetActionFactory factory responsible for building asset update actions. - * @param the type of the resource the asset update action is built for. - * @return a list of asset update actions on the resource of type T if there are new assets that should be added. - * Otherwise, if the assets order is identical, an empty optional is returned. - */ - @Nonnull - private static List> buildRemoveAssetOrAssetUpdateActions( - @Nonnull final T oldResource, - @Nonnull final D newResource, - @Nonnull final List oldAssets, - @Nonnull final Set removedAssetKeys, - @Nonnull final Map newAssetDraftsKeyMap, - @Nonnull final AssetActionFactory assetActionFactory) { - // For every old asset, If it doesn't exist anymore in the new asset drafts, - // then add a RemoveAsset action to the list of update actions. If the asset still exists in the new draft, - // then compare the asset fields (name, desc, etc..), and add the computed actions to the list of update - // actions. - return oldAssets - .stream() - .filter(asset -> isNotBlank(asset.getKey())) - .map(oldAsset -> { - final String oldAssetKey = oldAsset.getKey(); - final AssetDraft matchingNewAssetDraft = newAssetDraftsKeyMap.get(oldAssetKey); - return ofNullable(matchingNewAssetDraft) - .map(assetDraft -> // If asset exists, compare the two assets. - assetActionFactory.buildAssetActions(oldResource, newResource, oldAsset, assetDraft)) - .orElseGet(() -> { // If asset doesn't exist, remove asset. + /** + * Checks if there are any asset which are not existing in the {@code newAssetDraftsKeyMap}. If + * there are, then "remove" asset update actions are built using the instance of {@link + * AssetActionFactory} supplied to remove these assets. Otherwise, if there are no assets that + * should be removed, an empty list is returned. + * + * @param oldAssets the list of old {@link Asset}s + * @param removedAssetKeys a set containing keys of removed assets. + * @param newAssetDraftsKeyMap a map of keys to asset drafts of the new list of asset drafts + * @param assetActionFactory factory responsible for building asset update actions. + * @param the type of the resource the asset update action is built for. + * @return a list of asset update actions on the resource of type T if there are new assets that + * should be added. Otherwise, if the assets order is identical, an empty optional is + * returned. + */ + @Nonnull + private static List> buildRemoveAssetOrAssetUpdateActions( + @Nonnull final T oldResource, + @Nonnull final D newResource, + @Nonnull final List oldAssets, + @Nonnull final Set removedAssetKeys, + @Nonnull final Map newAssetDraftsKeyMap, + @Nonnull final AssetActionFactory assetActionFactory) { + // For every old asset, If it doesn't exist anymore in the new asset drafts, + // then add a RemoveAsset action to the list of update actions. If the asset still exists in the + // new draft, + // then compare the asset fields (name, desc, etc..), and add the computed actions to the list + // of update + // actions. + return oldAssets.stream() + .filter(asset -> isNotBlank(asset.getKey())) + .map( + oldAsset -> { + final String oldAssetKey = oldAsset.getKey(); + final AssetDraft matchingNewAssetDraft = newAssetDraftsKeyMap.get(oldAssetKey); + return ofNullable(matchingNewAssetDraft) + .map( + assetDraft -> // If asset exists, compare the two assets. + assetActionFactory.buildAssetActions( + oldResource, newResource, oldAsset, assetDraft)) + .orElseGet( + () -> { // If asset doesn't exist, remove asset. removedAssetKeys.add(oldAssetKey); - return singletonList(assetActionFactory.buildRemoveAssetAction(oldAssetKey)); - }); + return singletonList( + assetActionFactory.buildRemoveAssetAction(oldAssetKey)); + }); }) - .flatMap(Collection::stream) - .collect(toCollection(ArrayList::new)); - } - - /** - * Compares the order of a list of old {@link Asset}s and a list of new {@link AssetDraft}s. If there is a change in - * order, then a change asset order (with the new order) is built. The method filters out the removed assets from - * the old asset list using the keys in the {@code removedAssetKeys} {@link Set}. If there are no changes in order - * an empty optional is returned. - * - * @param oldAssets the list of old {@link Asset}s - * @param newAssetDrafts the list of new {@link AssetDraft}s - * @param removedAssetKeys a set containing keys of removed assets. - * @param assetActionFactory factory responsible for building asset update actions. - * @param the type of the resource the asset update action is built for. - * @return a list of asset update actions on the resource of type T if the list of the order of assets is not - * identical. Otherwise, if the assets order is identical, an empty optional is returned. - */ - @Nonnull - private static Optional> buildChangeAssetOrderUpdateAction( - @Nonnull final List oldAssets, - @Nonnull final List newAssetDrafts, - @Nonnull final Set removedAssetKeys, - @Nonnull final AssetActionFactory assetActionFactory) { + .flatMap(Collection::stream) + .collect(toCollection(ArrayList::new)); + } - final Map oldAssetKeyToIdMap = oldAssets.stream() - .filter(asset -> isNotBlank(asset.getKey())) - .collect(toMap(Asset::getKey, Asset::getId)); + /** + * Compares the order of a list of old {@link Asset}s and a list of new {@link AssetDraft}s. If + * there is a change in order, then a change asset order (with the new order) is built. The method + * filters out the removed assets from the old asset list using the keys in the {@code + * removedAssetKeys} {@link Set}. If there are no changes in order an empty optional is returned. + * + * @param oldAssets the list of old {@link Asset}s + * @param newAssetDrafts the list of new {@link AssetDraft}s + * @param removedAssetKeys a set containing keys of removed assets. + * @param assetActionFactory factory responsible for building asset update actions. + * @param the type of the resource the asset update action is built for. + * @return a list of asset update actions on the resource of type T if the list of the order of + * assets is not identical. Otherwise, if the assets order is identical, an empty optional is + * returned. + */ + @Nonnull + private static + Optional> buildChangeAssetOrderUpdateAction( + @Nonnull final List oldAssets, + @Nonnull final List newAssetDrafts, + @Nonnull final Set removedAssetKeys, + @Nonnull final AssetActionFactory assetActionFactory) { - final List newOrder = newAssetDrafts.stream() - .filter(asset -> isNotBlank(asset.getKey())) - .map(AssetDraft::getKey) - .map(oldAssetKeyToIdMap::get) - .filter(Objects::nonNull) - .collect(toList()); - - final List oldOrder = oldAssets.stream() - .filter(asset -> isNotBlank(asset.getKey())) - .filter(asset -> !removedAssetKeys.contains(asset.getKey())) - .map(Asset::getId) - .collect(toList()); + final Map oldAssetKeyToIdMap = + oldAssets.stream() + .filter(asset -> isNotBlank(asset.getKey())) + .collect(toMap(Asset::getKey, Asset::getId)); - return buildUpdateAction(oldOrder, newOrder, () -> assetActionFactory.buildChangeAssetOrderAction(newOrder)); - } + final List newOrder = + newAssetDrafts.stream() + .filter(asset -> isNotBlank(asset.getKey())) + .map(AssetDraft::getKey) + .map(oldAssetKeyToIdMap::get) + .filter(Objects::nonNull) + .collect(toList()); - /** - * Checks if there are any new asset drafts which are not existing in the {@code oldAssetsKeyMap}. If there are, - * then "add" asset update actions are built using the instance of {@link AssetActionFactory} supplied to add the - * missing assets. Otherwise, if there are no new assets, then an empty list is returned. - * - * @param newAssetDrafts the list of new {@link AssetDraft}s - * @param oldAssetsKeyMap a map of keys to assets of the old list of assets - * @param assetActionFactory factory responsible for building asset update actions. - * @param the type of the resource the asset update action is built for. - * @return a list of asset update actions on the resource of type T if there are new assets that should be added. - * Otherwise, if the assets order is identical, an empty optional is returned. - */ - @Nonnull - private static List> buildAddAssetUpdateActions( - @Nonnull final List newAssetDrafts, - @Nonnull final Map oldAssetsKeyMap, - @Nonnull final AssetActionFactory assetActionFactory) { + final List oldOrder = + oldAssets.stream() + .filter(asset -> isNotBlank(asset.getKey())) + .filter(asset -> !removedAssetKeys.contains(asset.getKey())) + .map(Asset::getId) + .collect(toList()); + return buildUpdateAction( + oldOrder, newOrder, () -> assetActionFactory.buildChangeAssetOrderAction(newOrder)); + } - final ArrayList>> optionalActions = - IntStream.range(0, newAssetDrafts.size()) - .mapToObj(assetDraftIndex -> - ofNullable(newAssetDrafts.get(assetDraftIndex)) - .filter(assetDraft -> isNotBlank(assetDraft.getKey()) - && !oldAssetsKeyMap.containsKey(assetDraft.getKey())) - .map(assetDraft -> assetActionFactory.buildAddAssetAction(assetDraft, assetDraftIndex)) - ) - .collect(toCollection(ArrayList::new)); - return filterEmptyOptionals(optionalActions); - } + /** + * Checks if there are any new asset drafts which are not existing in the {@code oldAssetsKeyMap}. + * If there are, then "add" asset update actions are built using the instance of {@link + * AssetActionFactory} supplied to add the missing assets. Otherwise, if there are no new assets, + * then an empty list is returned. + * + * @param newAssetDrafts the list of new {@link AssetDraft}s + * @param oldAssetsKeyMap a map of keys to assets of the old list of assets + * @param assetActionFactory factory responsible for building asset update actions. + * @param the type of the resource the asset update action is built for. + * @return a list of asset update actions on the resource of type T if there are new assets that + * should be added. Otherwise, if the assets order is identical, an empty optional is + * returned. + */ + @Nonnull + private static List> buildAddAssetUpdateActions( + @Nonnull final List newAssetDrafts, + @Nonnull final Map oldAssetsKeyMap, + @Nonnull final AssetActionFactory assetActionFactory) { + final ArrayList>> optionalActions = + IntStream.range(0, newAssetDrafts.size()) + .mapToObj( + assetDraftIndex -> + ofNullable(newAssetDrafts.get(assetDraftIndex)) + .filter( + assetDraft -> + isNotBlank(assetDraft.getKey()) + && !oldAssetsKeyMap.containsKey(assetDraft.getKey())) + .map( + assetDraft -> + assetActionFactory.buildAddAssetAction( + assetDraft, assetDraftIndex))) + .collect(toCollection(ArrayList::new)); + return filterEmptyOptionals(optionalActions); + } - private AssetsUpdateActionUtils() { - } + private AssetsUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java b/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java index 64d709ccda..a4b2c05a51 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java @@ -6,51 +6,52 @@ import io.sphere.sdk.client.retry.RetryableSphereClientBuilder; import io.sphere.sdk.http.AsyncHttpClientAdapter; import io.sphere.sdk.http.HttpClient; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.DefaultAsyncHttpClient; import org.asynchttpclient.DefaultAsyncHttpClientConfig; -import javax.annotation.Nonnull; -import java.util.concurrent.TimeUnit; - public final class ClientConfigurationUtils { - /** - * Creates a {@link SphereClient} with retry logic which computes a exponential backoff time delay in milliseconds. - * - * @param clientConfig the client configuration for the client. - * @return the instantiated {@link SphereClient}. - */ - public static SphereClient createClient(@Nonnull final SphereClientConfig clientConfig) { - return RetryableSphereClientBuilder.of(clientConfig, getHttpClient()).build(); - } + /** + * Creates a {@link SphereClient} with retry logic which computes a exponential backoff time delay + * in milliseconds. + * + * @param clientConfig the client configuration for the client. + * @return the instantiated {@link SphereClient}. + */ + public static SphereClient createClient(@Nonnull final SphereClientConfig clientConfig) { + return RetryableSphereClientBuilder.of(clientConfig, getHttpClient()).build(); + } - /** - * Creates a {@link BlockingSphereClient} with a custom {@code timeout} with a custom {@link TimeUnit}. - * - * @param clientConfig the client configuration for the client. - * @param timeout the timeout value for the client requests. - * @param timeUnit the timeout time unit. - * @return the instantiated {@link BlockingSphereClient}. - */ - public static SphereClient createClient(@Nonnull final SphereClientConfig clientConfig, - final long timeout, - @Nonnull final TimeUnit timeUnit) { - return BlockingSphereClient.of(createClient(clientConfig), timeout, timeUnit); - } + /** + * Creates a {@link BlockingSphereClient} with a custom {@code timeout} with a custom {@link + * TimeUnit}. + * + * @param clientConfig the client configuration for the client. + * @param timeout the timeout value for the client requests. + * @param timeUnit the timeout time unit. + * @return the instantiated {@link BlockingSphereClient}. + */ + public static SphereClient createClient( + @Nonnull final SphereClientConfig clientConfig, + final long timeout, + @Nonnull final TimeUnit timeUnit) { + return BlockingSphereClient.of(createClient(clientConfig), timeout, timeUnit); + } - /** - * Gets an asynchronous {@link HttpClient} of `asynchttpclient` library, - * to be used by as an underlying http client for the {@link SphereClient}. - * - * @return an asynchronous {@link HttpClient} - */ - private static HttpClient getHttpClient() { - final AsyncHttpClient asyncHttpClient = - new DefaultAsyncHttpClient(new DefaultAsyncHttpClientConfig.Builder().build()); - return AsyncHttpClientAdapter.of(asyncHttpClient); - } + /** + * Gets an asynchronous {@link HttpClient} of `asynchttpclient` library, to be used by as an + * underlying http client for the {@link SphereClient}. + * + * @return an asynchronous {@link HttpClient} + */ + private static HttpClient getHttpClient() { + final AsyncHttpClient asyncHttpClient = + new DefaultAsyncHttpClient(new DefaultAsyncHttpClientConfig.Builder().build()); + return AsyncHttpClientAdapter.of(asyncHttpClient); + } - private ClientConfigurationUtils() { - } + private ClientConfigurationUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/CollectionUtils.java b/src/main/java/com/commercetools/sync/commons/utils/CollectionUtils.java index e3e7c1f197..2bba62c168 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/CollectionUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/CollectionUtils.java @@ -1,7 +1,11 @@ package com.commercetools.sync.commons.utils; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; + import java.util.Collection; import java.util.List; import java.util.Map; @@ -9,157 +13,154 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; - -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class CollectionUtils { - /** - * Create a new collection which contains only elements which satisfy {@code includeCondition} predicate. - * - * @param collection {@link Collection} to be filtered - * @param includeCondition condition which verifies whether the value should be included to the final result. - * @param type of the collection items. - * @return new filtered stream with items which satisfy {@code includeCondition} predicate. If {@code collection} - * is null or empty - empty stream is returned. - */ - @Nonnull - public static Stream filterCollection(@Nullable final Collection collection, - @Nonnull final Predicate includeCondition) { - - return collection == null ? Stream.empty() - : collection.stream() - .filter(includeCondition); - } + /** + * Create a new collection which contains only elements which satisfy {@code includeCondition} + * predicate. + * + * @param collection {@link Collection} to be filtered + * @param includeCondition condition which verifies whether the value should be included to the + * final result. + * @param type of the collection items. + * @return new filtered stream with items which satisfy {@code includeCondition} predicate. If + * {@code collection} is null or empty - empty stream is returned. + */ + @Nonnull + public static Stream filterCollection( + @Nullable final Collection collection, @Nonnull final Predicate includeCondition) { - /** - * Convert a {@code collection} to a set of values using {@code keyMapper} mapping. - * - *

If the collection has duplicate keys - only one will be stored, which one is not defined though. - * - * @param collection {@link Collection} to convert - * @param keyMapper function which converts the collection entry to a key. - * @param type of collection entries - * @param type of Set key - * @return new {@link Set} which consists of items, converted from <T> entries to keys using - * {@code entryToKey} function. - */ - @Nonnull - public static Set collectionToSet(@Nullable final Collection collection, - @Nonnull final Function keyMapper) { - return emptyIfNull(collection).stream() - .map(keyMapper) - .collect(toSet()); - } + return collection == null ? Stream.empty() : collection.stream().filter(includeCondition); + } - /** - * Convert a {@code collection} to a map using {@code keyMapper} and {@code valueMapper} mappers. - * If keys are duplicated - only one value is stored, which one - undefined. - * - * @param collection {@link Collection} to convert - * @param keyMapper function which converts the collection entry to a key - * @param valueMapper function which converts the collection entry to a value - * @param type of collection entries - * @param type of Map key - * @param type of Map value - * @return new {@link Map} which consists of key-value pairs, converted from <T> entries - * @see #collectionToMap(Collection, Function) - */ - @Nonnull - public static Map collectionToMap(@Nullable final Collection collection, - @Nonnull final Function keyMapper, - @Nonnull final Function valueMapper) { - return emptyIfNull(collection) - .stream() - .collect(toMap(keyMapper, valueMapper, (k1, k2) -> k1)); // ignore duplicates - } + /** + * Convert a {@code collection} to a set of values using {@code keyMapper} mapping. + * + *

If the collection has duplicate keys - only one will be stored, which one is not defined + * though. + * + * @param collection {@link Collection} to convert + * @param keyMapper function which converts the collection entry to a key. + * @param type of collection entries + * @param type of Set key + * @return new {@link Set} which consists of items, converted from <T> entries to keys using + * {@code entryToKey} function. + */ + @Nonnull + public static Set collectionToSet( + @Nullable final Collection collection, + @Nonnull final Function keyMapper) { + return emptyIfNull(collection).stream().map(keyMapper).collect(toSet()); + } - /** - * Same as {@link #collectionToMap(Collection, Function, Function)}, but uses entries themselves as map values. - * - * @param collection {@link Collection} to convert - * @param keyMapper function which converts the collection entry to a key - * @param type of collection entries - * @param type of Map key - * @return new {@link Map} which consists of key-value pairs, converted from <T> entries - * @see #collectionToMap(Collection, Function, Function) - */ - @Nonnull - public static Map collectionToMap(@Nullable final Collection collection, - @Nonnull final Function keyMapper) { - return collectionToMap(collection, keyMapper, value -> value); - } + /** + * Convert a {@code collection} to a map using {@code keyMapper} and {@code valueMapper} mappers. + * If keys are duplicated - only one value is stored, which one - undefined. + * + * @param collection {@link Collection} to convert + * @param keyMapper function which converts the collection entry to a key + * @param valueMapper function which converts the collection entry to a value + * @param type of collection entries + * @param type of Map key + * @param type of Map value + * @return new {@link Map} which consists of key-value pairs, converted from <T> entries + * @see #collectionToMap(Collection, Function) + */ + @Nonnull + public static Map collectionToMap( + @Nullable final Collection collection, + @Nonnull final Function keyMapper, + @Nonnull final Function valueMapper) { + return emptyIfNull(collection).stream() + .collect(toMap(keyMapper, valueMapper, (k1, k2) -> k1)); // ignore duplicates + } - /** - * Safe wrapper around nullable collection instances: returns {@code collection} argument itself, - * if the {@code collection} is non-null, otherwise returns (immutable) empty collection. - * - * @param collection {@link Collection} instance to process - * @param collection entities type - * @return original {@code collection} instance, if non-null; otherwise immutable empty collection instance. - * @see #emptyIfNull(List) - * @see #emptyIfNull(Set) - * @see #emptyIfNull(Map) - */ - @Nonnull - public static Collection emptyIfNull(@Nullable final Collection collection) { - return collection == null ? emptyList() : collection; - } + /** + * Same as {@link #collectionToMap(Collection, Function, Function)}, but uses entries themselves + * as map values. + * + * @param collection {@link Collection} to convert + * @param keyMapper function which converts the collection entry to a key + * @param type of collection entries + * @param type of Map key + * @return new {@link Map} which consists of key-value pairs, converted from <T> entries + * @see #collectionToMap(Collection, Function, Function) + */ + @Nonnull + public static Map collectionToMap( + @Nullable final Collection collection, + @Nonnull final Function keyMapper) { + return collectionToMap(collection, keyMapper, value -> value); + } - /** - * Safe wrapper around nullable list instances: returns {@code list} argument itself, - * if the {@code list} is non-null, otherwise returns (immutable) empty list. - * - * @param list {@link List} instance to process - * @param list entities type - * @return original {@code list} instance, if non-null; otherwise immutable empty list instance. - * @see #emptyIfNull(Collection) - * @see #emptyIfNull(Set) - * @see #emptyIfNull(Map) - */ - @Nonnull - public static List emptyIfNull(@Nullable final List list) { - return list == null ? emptyList() : list; - } + /** + * Safe wrapper around nullable collection instances: returns {@code collection} argument itself, + * if the {@code collection} is non-null, otherwise returns (immutable) empty collection. + * + * @param collection {@link Collection} instance to process + * @param collection entities type + * @return original {@code collection} instance, if non-null; otherwise immutable empty collection + * instance. + * @see #emptyIfNull(List) + * @see #emptyIfNull(Set) + * @see #emptyIfNull(Map) + */ + @Nonnull + public static Collection emptyIfNull(@Nullable final Collection collection) { + return collection == null ? emptyList() : collection; + } + /** + * Safe wrapper around nullable list instances: returns {@code list} argument itself, if the + * {@code list} is non-null, otherwise returns (immutable) empty list. + * + * @param list {@link List} instance to process + * @param list entities type + * @return original {@code list} instance, if non-null; otherwise immutable empty list instance. + * @see #emptyIfNull(Collection) + * @see #emptyIfNull(Set) + * @see #emptyIfNull(Map) + */ + @Nonnull + public static List emptyIfNull(@Nullable final List list) { + return list == null ? emptyList() : list; + } - /** - * Safe wrapper around nullable set instances: returns {@code set} argument itself, - * if the {@code set} is non-null, otherwise returns (immutable) empty set. - * - * @param set {@link Set} instance to process - * @param set entities type - * @return original {@code set} instance, if non-null; otherwise immutable empty set instance. - * @see #emptyIfNull(Collection) - * @see #emptyIfNull(List) - * @see #emptyIfNull(Map) - */ - @Nonnull - public static Set emptyIfNull(@Nullable final Set set) { - return set == null ? emptySet() : set; - } + /** + * Safe wrapper around nullable set instances: returns {@code set} argument itself, if the {@code + * set} is non-null, otherwise returns (immutable) empty set. + * + * @param set {@link Set} instance to process + * @param set entities type + * @return original {@code set} instance, if non-null; otherwise immutable empty set instance. + * @see #emptyIfNull(Collection) + * @see #emptyIfNull(List) + * @see #emptyIfNull(Map) + */ + @Nonnull + public static Set emptyIfNull(@Nullable final Set set) { + return set == null ? emptySet() : set; + } - /** - * Safe wrapper around nullable map instances: returns {@code map} argument itself, if the {@code map} is non-null, - * otherwise returns (immutable) empty map. - * - * @param map {@link Map} instance to process - * @param map key type - * @param map value type - * @return original {@code map} instance, if non-null; otherwise immutable empty map instance. - * @see #emptyIfNull(Collection) - * @see #emptyIfNull(List) - * @see #emptyIfNull(Set) - */ - @Nonnull - public static Map emptyIfNull(@Nullable final Map map) { - return map == null ? emptyMap() : map; - } + /** + * Safe wrapper around nullable map instances: returns {@code map} argument itself, if the {@code + * map} is non-null, otherwise returns (immutable) empty map. + * + * @param map {@link Map} instance to process + * @param map key type + * @param map value type + * @return original {@code map} instance, if non-null; otherwise immutable empty map instance. + * @see #emptyIfNull(Collection) + * @see #emptyIfNull(List) + * @see #emptyIfNull(Set) + */ + @Nonnull + public static Map emptyIfNull(@Nullable final Map map) { + return map == null ? emptyMap() : map; + } - private CollectionUtils() { - } + private CollectionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/CommonTypeUpdateActionUtils.java b/src/main/java/com/commercetools/sync/commons/utils/CommonTypeUpdateActionUtils.java index a7b1ba43db..c2874aec00 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/CommonTypeUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/CommonTypeUpdateActionUtils.java @@ -1,113 +1,120 @@ package com.commercetools.sync.commons.utils; +import static java.util.Collections.emptyList; +import static java.util.Optional.ofNullable; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.ResourceIdentifier; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; - -import static java.util.Collections.emptyList; -import static java.util.Optional.ofNullable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class CommonTypeUpdateActionUtils { - /** - * Compares two {@link Object} and returns a supplied {@link UpdateAction} as a result in an - * {@link Optional}. If both the {@link Object}s have the same values, then no update action is needed and hence an - * empty {@link Optional} is returned. - * - * @param oldObject the object which should be updated - * @param newObject the object with the new information - * @param updateActionSupplier the supplier that returns the update action to return in the optional - * @param the type of the {@link UpdateAction} - * @param the type of the objects to compare - * @param certain {@link UpdateAction} implementation type - * @return A filled optional with the update action or an empty optional if the object values are identical - */ - @Nonnull - public static > Optional buildUpdateAction( - @Nullable final S oldObject, - @Nullable final S newObject, - @Nonnull final Supplier updateActionSupplier) { + /** + * Compares two {@link Object} and returns a supplied {@link UpdateAction} as a result in an + * {@link Optional}. If both the {@link Object}s have the same values, then no update action is + * needed and hence an empty {@link Optional} is returned. + * + * @param oldObject the object which should be updated + * @param newObject the object with the new information + * @param updateActionSupplier the supplier that returns the update action to return in the + * optional + * @param the type of the {@link UpdateAction} + * @param the type of the objects to compare + * @param certain {@link UpdateAction} implementation type + * @return A filled optional with the update action or an empty optional if the object values are + * identical + */ + @Nonnull + public static > Optional buildUpdateAction( + @Nullable final S oldObject, + @Nullable final S newObject, + @Nonnull final Supplier updateActionSupplier) { - return !Objects.equals(oldObject, newObject) - ? Optional.ofNullable(updateActionSupplier.get()) - : Optional.empty(); - } + return !Objects.equals(oldObject, newObject) + ? Optional.ofNullable(updateActionSupplier.get()) + : Optional.empty(); + } - /** - * Compares two objects that are of type {@link ResourceIdentifier} (or a type that extends it) and returns a - * supplied {@link UpdateAction} as a result in an {@link Optional}. If both the {@link Object}s have the same - * values, then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldResourceIdentifier the old resource identifier - * @param newResourceIdentifier the new resource identifier - * @param updateActionSupplier the supplier that returns the update action to return in the optional - * @param the type of the {@link UpdateAction} - * @param the type of the old resource identifier - * @param the type of the new resource identifier - * @param concrete {@link UpdateAction} implementation type - * @return A filled optional with the update action or an empty optional if the object values are identical - */ - @Nonnull - public static > Optional - buildUpdateActionForReferences( - @Nullable final S oldResourceIdentifier, - @Nullable final U newResourceIdentifier, - @Nonnull final Supplier updateActionSupplier) { + /** + * Compares two objects that are of type {@link ResourceIdentifier} (or a type that extends it) + * and returns a supplied {@link UpdateAction} as a result in an {@link Optional}. If both the + * {@link Object}s have the same values, then no update action is needed and hence an empty {@link + * Optional} is returned. + * + * @param oldResourceIdentifier the old resource identifier + * @param newResourceIdentifier the new resource identifier + * @param updateActionSupplier the supplier that returns the update action to return in the + * optional + * @param the type of the {@link UpdateAction} + * @param the type of the old resource identifier + * @param the type of the new resource identifier + * @param concrete {@link UpdateAction} implementation type + * @return A filled optional with the update action or an empty optional if the object values are + * identical + */ + @Nonnull + public static < + T, S extends ResourceIdentifier, U extends ResourceIdentifier, V extends UpdateAction> + Optional buildUpdateActionForReferences( + @Nullable final S oldResourceIdentifier, + @Nullable final U newResourceIdentifier, + @Nonnull final Supplier updateActionSupplier) { - return !areResourceIdentifiersEqual(oldResourceIdentifier, newResourceIdentifier) - ? Optional.ofNullable(updateActionSupplier.get()) - : Optional.empty(); - } + return !areResourceIdentifiersEqual(oldResourceIdentifier, newResourceIdentifier) + ? Optional.ofNullable(updateActionSupplier.get()) + : Optional.empty(); + } - /** - * Compares the ids of two objects that are of type {@link ResourceIdentifier} (or a type that extends it). - * - * @param oldResourceIdentifier the old resource identifier - * @param newResourceIdentifier the new resource identifier - * @param the type of the old resource identifier - * @param the type of the new resource identifier - * @return true or false depending if the resource identifiers have the same id. - */ - public static boolean areResourceIdentifiersEqual( - @Nullable final T oldResourceIdentifier, @Nullable final S newResourceIdentifier) { + /** + * Compares the ids of two objects that are of type {@link ResourceIdentifier} (or a type that + * extends it). + * + * @param oldResourceIdentifier the old resource identifier + * @param newResourceIdentifier the new resource identifier + * @param the type of the old resource identifier + * @param the type of the new resource identifier + * @return true or false depending if the resource identifiers have the same id. + */ + public static + boolean areResourceIdentifiersEqual( + @Nullable final T oldResourceIdentifier, @Nullable final S newResourceIdentifier) { - final String oldId = ofNullable(oldResourceIdentifier).map(ResourceIdentifier::getId).orElse(null); - final String newId = ofNullable(newResourceIdentifier).map(ResourceIdentifier::getId).orElse(null); + final String oldId = + ofNullable(oldResourceIdentifier).map(ResourceIdentifier::getId).orElse(null); + final String newId = + ofNullable(newResourceIdentifier).map(ResourceIdentifier::getId).orElse(null); - return Objects.equals(oldId, newId); - } + return Objects.equals(oldId, newId); + } - /** - * Compares two {@link Object} and returns a supplied list of {@link UpdateAction} as a result. - * If both the {@link Object}s have the same values, then no update action is needed - * and hence an empty list is returned. - * - * @param oldObject the object which should be updated - * @param newObject the object with the new information - * @param updateActionSupplier the supplier that returns a list of update actions if the objects are different - * @param the type of the {@link UpdateAction} - * @param the type of the objects to compare - * @param certain {@link UpdateAction} implementation type - * @return A filled optional with the update action or an empty optional if the object values are identical - */ - @Nonnull - public static > List buildUpdateActions( - @Nullable final S oldObject, - @Nullable final S newObject, - @Nonnull final Supplier> updateActionSupplier) { + /** + * Compares two {@link Object} and returns a supplied list of {@link UpdateAction} as a result. If + * both the {@link Object}s have the same values, then no update action is needed and hence an + * empty list is returned. + * + * @param oldObject the object which should be updated + * @param newObject the object with the new information + * @param updateActionSupplier the supplier that returns a list of update actions if the objects + * are different + * @param the type of the {@link UpdateAction} + * @param the type of the objects to compare + * @param certain {@link UpdateAction} implementation type + * @return A filled optional with the update action or an empty optional if the object values are + * identical + */ + @Nonnull + public static > List buildUpdateActions( + @Nullable final S oldObject, + @Nullable final S newObject, + @Nonnull final Supplier> updateActionSupplier) { - return !Objects.equals(oldObject, newObject) - ? updateActionSupplier.get() - : emptyList(); - } + return !Objects.equals(oldObject, newObject) ? updateActionSupplier.get() : emptyList(); + } - private CommonTypeUpdateActionUtils() { - } + private CommonTypeUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/CompletableFutureUtils.java b/src/main/java/com/commercetools/sync/commons/utils/CompletableFutureUtils.java index 0a4ecb6b6e..70e8946a2c 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/CompletableFutureUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/CompletableFutureUtils.java @@ -1,6 +1,8 @@ package com.commercetools.sync.commons.utils; -import javax.annotation.Nonnull; +import static com.commercetools.sync.commons.utils.StreamUtils.filterNullAndMap; +import static java.util.stream.Collectors.toList; + import java.util.Collection; import java.util.List; import java.util.Objects; @@ -9,155 +11,162 @@ import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Stream; - -import static com.commercetools.sync.commons.utils.StreamUtils.filterNullAndMap; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; public final class CompletableFutureUtils { - - /** - * Creates a Future containing a collection of value results after the mapper function is applied to each value in - * the supplied collection and completed it. The type of the returned collection is decided by the supplied - * collector. - * - * @param values collection of values to apply a mapper function that would map each to a - * {@link CompletionStage}. - * @param mapper function to map each value to a {@link CompletionStage} - * @param collector the collector to define the type of the collection returned. - * @param The type of the values. - * @param The type of the mapped completed values. - * @param The type of the collection returned in the future. - * @return a future containing a collection of completed stage results of the values after the mapper function was - * applied to each value in the supplied collection. - */ - @Nonnull - public static > CompletableFuture mapValuesToFutureOfCompletedValues( - @Nonnull final Collection values, - @Nonnull final Function> mapper, - @Nonnull final Collector collector) { - - return mapValuesToFutureOfCompletedValues(values.stream(), mapper, collector); - } - - /** - * Creates a Future containing a stream of value results after the mapper function is applied to each value in the - * supplied collection and completed it. The type of the collection is decided upon on by the supplied collector. - * - * @param values collection of values to apply a mapper function that would map each to a {@link CompletionStage}. - * @param mapper function to map each value to a {@link CompletionStage} - * @param The type of the values. - * @param The type of the mapped completed values. - * @return a future containing a stream of completed stage results of the values after the mapper function was - * applied to each value in the supplied collection. - */ - @Nonnull - public static CompletableFuture> mapValuesToFutureOfCompletedValues( - @Nonnull final Collection values, - @Nonnull final Function> mapper) { - - final List> futureList = mapValuesToFutures(values.stream(), mapper, toList()); - return collectionOfFuturesToFutureOfCollection(futureList, toList()).thenApply(List::stream); - } - - /** - * Creates a Future containing a collection of value results after the mapper function is applied to each value in - * the supplied stream and completed it. The type of the returned collection is decided by the supplied collector. - * - * @param values stream of values to apply a mapper function that would map each to a {@link CompletionStage}. - * @param mapper function to map each value to a {@link CompletionStage} - * @param collector the collector to define the type of the collection returned. - * @param The type of the values. - * @param The type of the mapping of the values. - * @param The type of the collection returned in the future. - * @return a future containing a list of completed stage results of the values after the mapper function was - * applied to each one. - */ - @Nonnull - public static > CompletableFuture mapValuesToFutureOfCompletedValues( - @Nonnull final Stream values, - @Nonnull final Function> mapper, - @Nonnull final Collector collector) { - - final List> futureList = mapValuesToFutures(values, mapper, toList()); - return collectionOfFuturesToFutureOfCollection(futureList, collector); - } - - /** - * Transforms a collection of {@code CompletionStage} into a {@code CompletionStage} of a collection, - * that will be completed once all the elements of the given futures are completed. - * In case multiple stages end exceptionally only one error is kept. The type of the returned collection is decided - * by the supplied collector. - * - *

Note: Null futures in the collection are filtered out. - * - * @param futures collection of {@code CompletionStage} - * @param collector the collector to define the type of the collection returned. - * @param the element obtained from the set of {@code CompletionStage} - * @param The type of the collection returned in the future. - * @return the {@code CompletableFuture} of a collection of elements - */ - @Nonnull - public static > CompletableFuture collectionOfFuturesToFutureOfCollection( - @Nonnull final Collection> futures, - @Nonnull final Collector collector) { - - final List> futureList = futures.stream() - .filter(Objects::nonNull) - .map(CompletionStage::toCompletableFuture) - .collect(toList()); - - final CompletableFuture[] futuresAsArray = futureList.toArray(new CompletableFuture[futureList.size()]); - return CompletableFuture.allOf(futuresAsArray) - .thenApply(ignoredResult -> futureList.stream() - .map(CompletableFuture::join) - .collect(collector)); - } - - - /** - * Maps a stream of values to a future collection using the supplied mapper function. The type of the returned - * collection is decided by the supplied collector. - * - * @param values stream of values to apply a mapper function that would map each to a {@link CompletionStage}. - * @param mapper function to map each value to a {@link CompletionStage} - * @param collector the collector to define the type of the collection returned. - * @param The type of the values. - * @param The type of the mapped values. - * @param The type of the collection returned. - * @return a collection of futures resulting from applying the mapper function on each value. - */ - @Nonnull - public static >> U mapValuesToFutures( - @Nonnull final Stream values, - @Nonnull final Function> mapper, - @Nonnull final Collector, ?, U> collector) { - - final Stream> stageStream = filterNullAndMap(values, mapper); - return toCompletableFutures(stageStream, collector); - } - - /** - * Converts a stream of {@link CompletionStage} of values of type {@code } into a - * {@link Collection} of the type of the supplied {@code collector} of {@link CompletableFuture} - * of values of type {@code }. The type of the returned collection is decided by the supplied collector. - * - *

Note: Null futures in the stream are filtered out. - * - * @param values stream of {@link CompletionStage} of values of type {@code } - * @param collector the collector to define the type of the collection returned. - * @param the type of the results of the stages. - * @param the concrete type of the collection returned. - * @return a {@link List} of {@link CompletableFuture} elements of type {@code }. - */ - @Nonnull - public static >> S toCompletableFutures( - @Nonnull final Stream> values, - @Nonnull final Collector, ?, S> collector) { - return values.filter(Objects::nonNull) - .map(CompletionStage::toCompletableFuture).collect(collector); - } - - private CompletableFutureUtils() { - } + /** + * Creates a Future containing a collection of value results after the mapper function is applied + * to each value in the supplied collection and completed it. The type of the returned collection + * is decided by the supplied collector. + * + * @param values collection of values to apply a mapper function that would map each to a {@link + * CompletionStage}. + * @param mapper function to map each value to a {@link CompletionStage} + * @param collector the collector to define the type of the collection returned. + * @param The type of the values. + * @param The type of the mapped completed values. + * @param The type of the collection returned in the future. + * @return a future containing a collection of completed stage results of the values after the + * mapper function was applied to each value in the supplied collection. + */ + @Nonnull + public static > + CompletableFuture mapValuesToFutureOfCompletedValues( + @Nonnull final Collection values, + @Nonnull final Function> mapper, + @Nonnull final Collector collector) { + + return mapValuesToFutureOfCompletedValues(values.stream(), mapper, collector); + } + + /** + * Creates a Future containing a stream of value results after the mapper function is applied to + * each value in the supplied collection and completed it. The type of the collection is decided + * upon on by the supplied collector. + * + * @param values collection of values to apply a mapper function that would map each to a {@link + * CompletionStage}. + * @param mapper function to map each value to a {@link CompletionStage} + * @param The type of the values. + * @param The type of the mapped completed values. + * @return a future containing a stream of completed stage results of the values after the mapper + * function was applied to each value in the supplied collection. + */ + @Nonnull + public static CompletableFuture> mapValuesToFutureOfCompletedValues( + @Nonnull final Collection values, @Nonnull final Function> mapper) { + + final List> futureList = + mapValuesToFutures(values.stream(), mapper, toList()); + return collectionOfFuturesToFutureOfCollection(futureList, toList()).thenApply(List::stream); + } + + /** + * Creates a Future containing a collection of value results after the mapper function is applied + * to each value in the supplied stream and completed it. The type of the returned collection is + * decided by the supplied collector. + * + * @param values stream of values to apply a mapper function that would map each to a {@link + * CompletionStage}. + * @param mapper function to map each value to a {@link CompletionStage} + * @param collector the collector to define the type of the collection returned. + * @param The type of the values. + * @param The type of the mapping of the values. + * @param The type of the collection returned in the future. + * @return a future containing a list of completed stage results of the values after the mapper + * function was applied to each one. + */ + @Nonnull + public static > + CompletableFuture mapValuesToFutureOfCompletedValues( + @Nonnull final Stream values, + @Nonnull final Function> mapper, + @Nonnull final Collector collector) { + + final List> futureList = mapValuesToFutures(values, mapper, toList()); + return collectionOfFuturesToFutureOfCollection(futureList, collector); + } + + /** + * Transforms a collection of {@code CompletionStage} into a {@code CompletionStage} of a + * collection, that will be completed once all the elements of the given futures are completed. In + * case multiple stages end exceptionally only one error is kept. The type of the returned + * collection is decided by the supplied collector. + * + *

Note: Null futures in the collection are filtered out. + * + * @param futures collection of {@code CompletionStage} + * @param collector the collector to define the type of the collection returned. + * @param the element obtained from the set of {@code CompletionStage} + * @param The type of the collection returned in the future. + * @return the {@code CompletableFuture} of a collection of elements + */ + @Nonnull + public static > + CompletableFuture collectionOfFuturesToFutureOfCollection( + @Nonnull final Collection> futures, + @Nonnull final Collector collector) { + + final List> futureList = + futures.stream() + .filter(Objects::nonNull) + .map(CompletionStage::toCompletableFuture) + .collect(toList()); + + final CompletableFuture[] futuresAsArray = + futureList.toArray(new CompletableFuture[futureList.size()]); + return CompletableFuture.allOf(futuresAsArray) + .thenApply( + ignoredResult -> futureList.stream().map(CompletableFuture::join).collect(collector)); + } + + /** + * Maps a stream of values to a future collection using the supplied mapper function. The type of + * the returned collection is decided by the supplied collector. + * + * @param values stream of values to apply a mapper function that would map each to a {@link + * CompletionStage}. + * @param mapper function to map each value to a {@link CompletionStage} + * @param collector the collector to define the type of the collection returned. + * @param The type of the values. + * @param The type of the mapped values. + * @param The type of the collection returned. + * @return a collection of futures resulting from applying the mapper function on each value. + */ + @Nonnull + public static >> U mapValuesToFutures( + @Nonnull final Stream values, + @Nonnull final Function> mapper, + @Nonnull final Collector, ?, U> collector) { + + final Stream> stageStream = filterNullAndMap(values, mapper); + return toCompletableFutures(stageStream, collector); + } + + /** + * Converts a stream of {@link CompletionStage} of values of type {@code } into a {@link + * Collection} of the type of the supplied {@code collector} of {@link CompletableFuture} of + * values of type {@code }. The type of the returned collection is decided by the supplied + * collector. + * + *

Note: Null futures in the stream are filtered out. + * + * @param values stream of {@link CompletionStage} of values of type {@code } + * @param collector the collector to define the type of the collection returned. + * @param the type of the results of the stages. + * @param the concrete type of the collection returned. + * @return a {@link List} of {@link CompletableFuture} elements of type {@code }. + */ + @Nonnull + public static >> S toCompletableFutures( + @Nonnull final Stream> values, + @Nonnull final Collector, ?, S> collector) { + return values + .filter(Objects::nonNull) + .map(CompletionStage::toCompletableFuture) + .collect(collector); + } + + private CompletableFutureUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java b/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java index ba5385609f..445b42b12a 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/CtpQueryUtils.java @@ -1,170 +1,193 @@ package com.commercetools.sync.commons.utils; +import static io.sphere.sdk.queries.QueryExecutionUtils.DEFAULT_PAGE_SIZE; + import com.commercetools.sync.commons.models.GraphQlBaseRequest; import com.commercetools.sync.commons.models.GraphQlBaseResource; import com.commercetools.sync.commons.models.GraphQlBaseResult; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.models.ResourceView; import io.sphere.sdk.queries.QueryDsl; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; import java.util.function.Function; - -import static io.sphere.sdk.queries.QueryExecutionUtils.DEFAULT_PAGE_SIZE; +import javax.annotation.Nonnull; public final class CtpQueryUtils { - /** - * Queries all elements matching a query by using a limit based pagination with a combination of id sorting and a - * page size 500. More on the algorithm can be found here: http://dev.commercetools.com/http-api.html#offset. - * - *

The method takes a callback {@link Function} that returns a result of type {@code } that is returned on - * every page of elements queried. Eventually, the method returns a {@link CompletionStage} that contains a list of - * all the results of the callbacks returned from every page. - * - *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in parallel. - * - * @param client commercetools client - * @param query query containing predicates and expansion paths - * @param pageMapper callback function that is called on every page queried - * @param type of one query result element - * @param type of the query - * @param type of the returned result of the callback function on every page. - * @return a completion stage containing a list of mapped pages as a result. - */ - @Nonnull - public static , S> CompletionStage> - queryAll(@Nonnull final SphereClient client, @Nonnull final QueryDsl query, - @Nonnull final Function, S> pageMapper) { - return queryAll(client, query, pageMapper, DEFAULT_PAGE_SIZE); - } + /** + * Queries all elements matching a query by using a limit based pagination with a combination of + * id sorting and a page size 500. More on the algorithm can be found here: + * http://dev.commercetools.com/http-api.html#offset. + * + *

The method takes a callback {@link Function} that returns a result of type {@code } that + * is returned on every page of elements queried. Eventually, the method returns a {@link + * CompletionStage} that contains a list of all the results of the callbacks returned from every + * page. + * + *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in + * parallel. + * + * @param client commercetools client + * @param query query containing predicates and expansion paths + * @param pageMapper callback function that is called on every page queried + * @param type of one query result element + * @param type of the query + * @param type of the returned result of the callback function on every page. + * @return a completion stage containing a list of mapped pages as a result. + */ + @Nonnull + public static , S> + CompletionStage> queryAll( + @Nonnull final SphereClient client, + @Nonnull final QueryDsl query, + @Nonnull final Function, S> pageMapper) { + return queryAll(client, query, pageMapper, DEFAULT_PAGE_SIZE); + } - /** - * Queries all elements matching a query by using a limit based pagination with a combination of id sorting and a - * page size 500. More on the algorithm can be found here: http://dev.commercetools.com/http-api.html#offset - * - *

The method takes a consumer {@link Consumer} that is applied on every page of elements queried. - * - *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in parallel. - * - * @param client commercetools client - * @param query query containing predicates and expansion paths - * @param pageConsumer consumer applied on every page queried - * @param type of one query result element - * @param type of the query - * @return a completion stage containing void as a result after the consumer was applied on all pages. - */ - @Nonnull - public static > CompletionStage - queryAll(@Nonnull final SphereClient client, @Nonnull final QueryDsl query, - @Nonnull final Consumer> pageConsumer) { - return queryAll(client, query, pageConsumer, DEFAULT_PAGE_SIZE); - } + /** + * Queries all elements matching a query by using a limit based pagination with a combination of + * id sorting and a page size 500. More on the algorithm can be found here: + * http://dev.commercetools.com/http-api.html#offset + * + *

The method takes a consumer {@link Consumer} that is applied on every page of elements + * queried. + * + *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in + * parallel. + * + * @param client commercetools client + * @param query query containing predicates and expansion paths + * @param pageConsumer consumer applied on every page queried + * @param type of one query result element + * @param type of the query + * @return a completion stage containing void as a result after the consumer was applied on all + * pages. + */ + @Nonnull + public static > CompletionStage queryAll( + @Nonnull final SphereClient client, + @Nonnull final QueryDsl query, + @Nonnull final Consumer> pageConsumer) { + return queryAll(client, query, pageConsumer, DEFAULT_PAGE_SIZE); + } - /** - * Queries all elements matching a query by using a limit based pagination with a combination of id sorting and the - * supplied {@code pageSize}. - * More on the algorithm can be found here: http://dev.commercetools.com/http-api.html#offset. - * - *

The method takes a callback {@link Function} that returns a result of type {@code } that is returned on - * every page of elements queried. Eventually, the method returns a {@link CompletionStage} that contains a list of - * all the results of the callbacks returned from every page. - * - *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in parallel. - * - * @param client commercetools client - * @param query query containing predicates and expansion paths - * @param pageMapper callback function that is called on every page queried - * @param type of one query result element - * @param type of the query - * @param type of the returned result of the callback function on every page. - * @param pageSize the page size. - * @return a completion stage containing a list of mapped pages as a result. - */ - @Nonnull - public static , S> CompletionStage> - queryAll(@Nonnull final SphereClient client, @Nonnull final QueryDsl query, - @Nonnull final Function, S> pageMapper, final int pageSize) { - final QueryAll queryAll = QueryAll.of(client, query, pageSize); - return queryAll.run(pageMapper); - } + /** + * Queries all elements matching a query by using a limit based pagination with a combination of + * id sorting and the supplied {@code pageSize}. More on the algorithm can be found here: + * http://dev.commercetools.com/http-api.html#offset. + * + *

The method takes a callback {@link Function} that returns a result of type {@code } that + * is returned on every page of elements queried. Eventually, the method returns a {@link + * CompletionStage} that contains a list of all the results of the callbacks returned from every + * page. + * + *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in + * parallel. + * + * @param client commercetools client + * @param query query containing predicates and expansion paths + * @param pageMapper callback function that is called on every page queried + * @param type of one query result element + * @param type of the query + * @param type of the returned result of the callback function on every page. + * @param pageSize the page size. + * @return a completion stage containing a list of mapped pages as a result. + */ + @Nonnull + public static , S> + CompletionStage> queryAll( + @Nonnull final SphereClient client, + @Nonnull final QueryDsl query, + @Nonnull final Function, S> pageMapper, + final int pageSize) { + final QueryAll queryAll = QueryAll.of(client, query, pageSize); + return queryAll.run(pageMapper); + } - /** - * Queries all elements matching a query by using a limit based pagination with a combination of id sorting and the - * supplied {@code pageSize}. - * More on the algorithm can be found here: http://dev.commercetools.com/http-api.html#offset - * - *

The method takes a {@link Consumer} that is applied on every page of the queried elements. - * - *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in parallel. - * - * @param client commercetools client - * @param query query containing predicates and expansion paths - * @param pageConsumer consumer applied on every page queried - * @param type of one query result element - * @param type of the query - * @param pageSize the page size - * @return a completion stage containing void as a result after the consumer was applied on all pages. - */ - @Nonnull - public static > CompletionStage - queryAll(@Nonnull final SphereClient client, @Nonnull final QueryDsl query, - @Nonnull final Consumer> pageConsumer, final int pageSize) { - final QueryAll queryAll = QueryAll.of(client, query, pageSize); - return queryAll.run(pageConsumer); - } + /** + * Queries all elements matching a query by using a limit based pagination with a combination of + * id sorting and the supplied {@code pageSize}. More on the algorithm can be found here: + * http://dev.commercetools.com/http-api.html#offset + * + *

The method takes a {@link Consumer} that is applied on every page of the queried elements. + * + *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in + * parallel. + * + * @param client commercetools client + * @param query query containing predicates and expansion paths + * @param pageConsumer consumer applied on every page queried + * @param type of one query result element + * @param type of the query + * @param pageSize the page size + * @return a completion stage containing void as a result after the consumer was applied on all + * pages. + */ + @Nonnull + public static > CompletionStage queryAll( + @Nonnull final SphereClient client, + @Nonnull final QueryDsl query, + @Nonnull final Consumer> pageConsumer, + final int pageSize) { + final QueryAll queryAll = QueryAll.of(client, query, pageSize); + return queryAll.run(pageConsumer); + } - /** - * Creates a graphQL query to fetch all elements where keys matching given set of keys with pagination using a - * combination of limit and id sorting. - * - *

The method takes a {@link Consumer} that is applied on every page of the queried elements. - * - *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in parallel. - * - * @param client commercetools client - * @param graphQlRequest graphql query containing predicates and pagination limits - * @param pageConsumer consumer applied on every page queried - * @param pageSize the page size. - * @return a completion stage containing void as a result after the consumer was applied on all pages. - */ - @Nonnull - public static , U extends GraphQlBaseResource> CompletionStage - queryAll(@Nonnull final SphereClient client, - @Nonnull final GraphQlBaseRequest graphQlRequest, - @Nonnull final Consumer> pageConsumer, - final int pageSize) { + /** + * Creates a graphQL query to fetch all elements where keys matching given set of keys with + * pagination using a combination of limit and id sorting. + * + *

The method takes a {@link Consumer} that is applied on every page of the queried elements. + * + *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in + * parallel. + * + * @param client commercetools client + * @param graphQlRequest graphql query containing predicates and pagination limits + * @param pageConsumer consumer applied on every page queried + * @param pageSize the page size. + * @return a completion stage containing void as a result after the consumer was applied on all + * pages. + */ + @Nonnull + public static , U extends GraphQlBaseResource> + CompletionStage queryAll( + @Nonnull final SphereClient client, + @Nonnull final GraphQlBaseRequest graphQlRequest, + @Nonnull final Consumer> pageConsumer, + final int pageSize) { - GraphQlQueryAll graphQlQueryAll = GraphQlQueryAll.of(client, graphQlRequest, pageSize); - return graphQlQueryAll.run(pageConsumer); - } + GraphQlQueryAll graphQlQueryAll = GraphQlQueryAll.of(client, graphQlRequest, pageSize); + return graphQlQueryAll.run(pageConsumer); + } - /** - * Creates a graphQL query to fetch all elements where keys matching given set of keys with pagination using a - * combination of limit and id sorting. - * - *

The method takes a {@link Consumer} that is applied on every page of the queried elements. - * - *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in parallel. - * - * @param client commercetools client - * @param graphQlRequest graphql query containing predicates and pagination limits - * @param pageConsumer consumer applied on every page queried - * @return a completion stage containing void as a result after the consumer was applied on all pages. - */ - @Nonnull - public static , U extends GraphQlBaseResource> CompletionStage - queryAll(@Nonnull final SphereClient client, - @Nonnull final GraphQlBaseRequest graphQlRequest, - @Nonnull final Consumer> pageConsumer) { + /** + * Creates a graphQL query to fetch all elements where keys matching given set of keys with + * pagination using a combination of limit and id sorting. + * + *

The method takes a {@link Consumer} that is applied on every page of the queried elements. + * + *

NOTE: This method fetches all paged results sequentially as opposed to fetching the pages in + * parallel. + * + * @param client commercetools client + * @param graphQlRequest graphql query containing predicates and pagination limits + * @param pageConsumer consumer applied on every page queried + * @return a completion stage containing void as a result after the consumer was applied on all + * pages. + */ + @Nonnull + public static , U extends GraphQlBaseResource> + CompletionStage queryAll( + @Nonnull final SphereClient client, + @Nonnull final GraphQlBaseRequest graphQlRequest, + @Nonnull final Consumer> pageConsumer) { - return queryAll(client, graphQlRequest, pageConsumer, DEFAULT_PAGE_SIZE); - } + return queryAll(client, graphQlRequest, pageConsumer, DEFAULT_PAGE_SIZE); + } - private CtpQueryUtils() { - } + private CtpQueryUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/CustomTypeReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/commons/utils/CustomTypeReferenceResolutionUtils.java index 457894a3da..bdc2ee5961 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/CustomTypeReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/CustomTypeReferenceResolutionUtils.java @@ -7,61 +7,62 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.CustomFieldsDraftBuilder; - import javax.annotation.Nonnull; import javax.annotation.Nullable; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class CustomTypeReferenceResolutionUtils { - /** - * Given a resource of type {@code T} that extends {@link Custom} (i.e. it has {@link CustomFields}, this method - * checks if the custom fields are existing (not null) and they are reference expanded. If they are then - * it returns a {@link CustomFieldsDraft} instance with the custom type key in place of the key of the reference. - * Otherwise, if it's not reference expanded it returns a {@link CustomFieldsDraft} without the key. - * If the resource has null {@link Custom}, then it returns {@code null}. - * - * @param resource the resource to replace its custom type key, if possible. - * @param the type of the resource. - * @return an instance of {@link CustomFieldsDraft} instance with the custom type key, if the - * custom type reference was existing and reference expanded on the resource. Otherwise, if its not - * reference expanded it returns a {@link CustomFieldsDraft} without a key. If the - * resource has no or null {@link Custom}, then it returns {@code null}. - */ - @Nullable - public static CustomFieldsDraft mapToCustomFieldsDraft(@Nonnull final T resource) { - final CustomFields custom = resource.getCustom(); - return mapToCustomFieldsDraft(custom); - } + /** + * Given a resource of type {@code T} that extends {@link Custom} (i.e. it has {@link + * CustomFields}, this method checks if the custom fields are existing (not null) and they are + * reference expanded. If they are then it returns a {@link CustomFieldsDraft} instance with the + * custom type key in place of the key of the reference. Otherwise, if it's not reference expanded + * it returns a {@link CustomFieldsDraft} without the key. If the resource has null {@link + * Custom}, then it returns {@code null}. + * + * @param resource the resource to replace its custom type key, if possible. + * @param the type of the resource. + * @return an instance of {@link CustomFieldsDraft} instance with the custom type key, if the + * custom type reference was existing and reference expanded on the resource. Otherwise, if + * its not reference expanded it returns a {@link CustomFieldsDraft} without a key. If the + * resource has no or null {@link Custom}, then it returns {@code null}. + */ + @Nullable + public static CustomFieldsDraft mapToCustomFieldsDraft( + @Nonnull final T resource) { + final CustomFields custom = resource.getCustom(); + return mapToCustomFieldsDraft(custom); + } - /** - * Given a custom {@link CustomFields}, this method provides checking to certain resources which do not extends - * {@link Custom}, such as {@link ShoppingList}, {@link LineItem} and {@link TextLineItem}. If the custom fields - * are existing (not null) and they are reference expanded. If they are then it returns a {@link CustomFieldsDraft} - * instance with the custom type key in place of the key of the reference. Otherwise, if it's not reference expanded - * it returns a {@link CustomFieldsDraft} without the key. If the resource has null {@link Custom}, then it returns - * {@code null}. - * - * @param custom the resource to replace its custom type key, if possible. - * @return an instance of {@link CustomFieldsDraft} instance with the custom type key, if the - * custom type reference was existing and reference expanded on the resource. Otherwise, if its not - * reference expanded it returns a {@link CustomFieldsDraft} without a key. If the - * resource has no or null {@link Custom}, then it returns {@code null}. - */ - @Nullable - public static CustomFieldsDraft mapToCustomFieldsDraft(@Nullable final CustomFields custom) { - if (custom != null) { - if (custom.getType().getObj() != null) { - return CustomFieldsDraft.ofTypeKeyAndJson(custom.getType().getObj().getKey(), - custom.getFieldsJsonMap()); - } - return CustomFieldsDraftBuilder.of(custom).build(); - } - return null; + /** + * Given a custom {@link CustomFields}, this method provides checking to certain resources which + * do not extends {@link Custom}, such as {@link ShoppingList}, {@link LineItem} and {@link + * TextLineItem}. If the custom fields are existing (not null) and they are reference expanded. If + * they are then it returns a {@link CustomFieldsDraft} instance with the custom type key in place + * of the key of the reference. Otherwise, if it's not reference expanded it returns a {@link + * CustomFieldsDraft} without the key. If the resource has null {@link Custom}, then it returns + * {@code null}. + * + * @param custom the resource to replace its custom type key, if possible. + * @return an instance of {@link CustomFieldsDraft} instance with the custom type key, if the + * custom type reference was existing and reference expanded on the resource. Otherwise, if + * its not reference expanded it returns a {@link CustomFieldsDraft} without a key. If the + * resource has no or null {@link Custom}, then it returns {@code null}. + */ + @Nullable + public static CustomFieldsDraft mapToCustomFieldsDraft(@Nullable final CustomFields custom) { + if (custom != null) { + if (custom.getType().getObj() != null) { + return CustomFieldsDraft.ofTypeKeyAndJson( + custom.getType().getObj().getKey(), custom.getFieldsJsonMap()); + } + return CustomFieldsDraftBuilder.of(custom).build(); } + return null; + } - private CustomTypeReferenceResolutionUtils() { - } + private CustomTypeReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/CustomUpdateActionUtils.java b/src/main/java/com/commercetools/sync/commons/utils/CustomUpdateActionUtils.java index 466e7f6d7c..ba7624c074 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/CustomUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/CustomUpdateActionUtils.java @@ -1,5 +1,10 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.SyncException; @@ -14,9 +19,6 @@ import io.sphere.sdk.types.CustomDraft; import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.Map; @@ -24,424 +26,508 @@ import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class CustomUpdateActionUtils { - private static final String CUSTOM_TYPE_IDS_NOT_SET = "Custom type ids are not set for both the old and new %s."; - private static final String CUSTOM_FIELDS_UPDATE_ACTIONS_BUILD_FAILED = "Failed to build custom fields update " - + "actions on the %s with id '%s'. Reason: %s"; - private static final String CUSTOM_TYPE_ID_IS_BLANK = "New resource's custom type id is blank (empty/null)."; + private static final String CUSTOM_TYPE_IDS_NOT_SET = + "Custom type ids are not set for both the old and new %s."; + private static final String CUSTOM_FIELDS_UPDATE_ACTIONS_BUILD_FAILED = + "Failed to build custom fields update " + "actions on the %s with id '%s'. Reason: %s"; + private static final String CUSTOM_TYPE_ID_IS_BLANK = + "New resource's custom type id is blank (empty/null)."; - /** - * This method is a syntactic sugar for the method {@link #buildCustomUpdateActions(Resource, Object, Custom, - * CustomDraft, GenericCustomActionBuilder, Integer, Function, Function, Function, BaseSyncOptions)} - * but this one is only for primary resources (i.e resources which have their own endpoints for example channels, - * categories, inventory entries. For more details of the inner logic and different scenarios, check the Javadoc of - * the other method. - * - * @param the type of the old {@link Resource} which has the custom fields. - * @param the type of the new resource {@link CustomDraft}. - * @param oldResource the resource which should be updated. - * @param newResource the resource draft where we get the new custom fields. - * @param customActionBuilder the builder instance responsible for building the custom update actions. - * @param syncOptions responsible for supplying the sync options to the sync utility method. - * @return a list that contains all the update actions needed, otherwise an empty list if no update actions are - * needed. - * @see #buildCustomUpdateActions(Resource, Object, Custom, CustomDraft, GenericCustomActionBuilder, Integer, - * Function, Function, Function, BaseSyncOptions) ) - */ - @Nonnull - public static , S extends CustomDraft> List> - buildPrimaryResourceCustomUpdateActions( - @Nonnull final T oldResource, - @Nonnull final S newResource, - @Nonnull final GenericCustomActionBuilder customActionBuilder, - @Nonnull final BaseSyncOptions syncOptions) { - return buildCustomUpdateActions( - oldResource, newResource, customActionBuilder, null, - resource -> resource.getId(), - resource -> resource.toReference().getTypeId(), - resource -> null, // No update ID needed for primary resources. - syncOptions); - } + /** + * This method is a syntactic sugar for the method {@link #buildCustomUpdateActions(Resource, + * Object, Custom, CustomDraft, GenericCustomActionBuilder, Integer, Function, Function, Function, + * BaseSyncOptions)} but this one is only for primary resources (i.e resources which have their + * own endpoints for example channels, categories, inventory entries. For more details of the + * inner logic and different scenarios, check the Javadoc of the other method. + * + * @param the type of the old {@link Resource} which has the custom fields. + * @param the type of the new resource {@link CustomDraft}. + * @param oldResource the resource which should be updated. + * @param newResource the resource draft where we get the new custom fields. + * @param customActionBuilder the builder instance responsible for building the custom update + * actions. + * @param syncOptions responsible for supplying the sync options to the sync utility method. + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + * @see #buildCustomUpdateActions(Resource, Object, Custom, CustomDraft, + * GenericCustomActionBuilder, Integer, Function, Function, Function, BaseSyncOptions) ) + */ + @Nonnull + public static , S extends CustomDraft> + List> buildPrimaryResourceCustomUpdateActions( + @Nonnull final T oldResource, + @Nonnull final S newResource, + @Nonnull final GenericCustomActionBuilder customActionBuilder, + @Nonnull final BaseSyncOptions syncOptions) { + return buildCustomUpdateActions( + oldResource, + newResource, + customActionBuilder, + null, + resource -> resource.getId(), + resource -> resource.toReference().getTypeId(), + resource -> null, // No update ID needed for primary resources. + syncOptions); + } + /** + * Compares the {@link CustomFields} of an old resource {@code T} (for example {@link Category}, + * {@link io.sphere.sdk.products.Product}, etc..), to the {@link CustomFieldsDraft}, of a new + * resource draft {@code S} (for example {@link CategoryDraft}, {@link + * io.sphere.sdk.products.ProductVariantDraft}, etc..), and returns a {@link List}<{@link + * UpdateAction}> as a result. If no update action is needed, for example in the case where + * both the {@link CustomFields} and the {@link CustomFieldsDraft} are null, an empty {@link + * List}<{@link UpdateAction}> is returned. A {@link BaseSyncOptions} instance is injected + * into the method which is responsible for supplying the sync options to the sync utility method. + * For example, custom error callbacks for errors. The {@link TypeService} is injected also for + * fetching the key of the old resource type from it's cache (see {@link + * CustomUpdateActionUtils#buildNonNullCustomFieldsUpdateActions(CustomFields, CustomFieldsDraft, + * Custom, GenericCustomActionBuilder, Integer, Function, Function, Function, BaseSyncOptions)}). + * + *

An update action will be added to the result list in the following cases:- + * + *

    + *
  1. If the new resources's custom type is set, but old resources's custom type is not. A + * "setCustomType" update actions is added, which sets the custom type (and all it's fields + * to the old resource). + *
  2. If the new resource's custom type is not set, but the old resource's custom type is set. + * A "setCustomType" update action is added, which removes the type set on the old resource. + *
  3. If both the resources custom types are the same and the custom fields are both set. The + * custom field values of both resources are then calculated. (see {@link + * CustomUpdateActionUtils#buildSetCustomFieldsUpdateActions(Map, Map, Custom, + * GenericCustomActionBuilder, Integer, Function)} )}) + *
  4. If the keys of both custom types are different, then a "setCustomType" update action is + * added, where the old resource's custom type is set to be as the new one's. + *
  5. If both resources custom type keys are identical but the custom fields of the new + * resource's custom type is not set. + *
+ * + *

An update action will not be added to the result list in the following cases:- + * + *

    + *
  1. If both the resources' custom types are not set. + *
  2. If both the resources' custom type keys are not set. + *
  3. Custom fields are both empty. + *
  4. Custom field JSON values have different ordering. + *
  5. Custom field values are identical. + *
+ * + * @param the type of the new {@link Resource} which is the super {@link Resource} of the + * Resource to update. + * @param the type of the old {@link Resource} which has the custom fields. + * @param the type of the new resource {@link CustomDraft}. + * @param the type of the resource in which the update actions will be applied on. + * @param oldMainResource the main resource of the resource which should be updated. + * @param newMainResourceDraft the main resource of the resource draft where we get the new custom + * fields. + * @param oldResource the resource which should be updated. + * @param newResourceDraft the resource draft where we get the new custom fields. + * @param customActionBuilder the builder instance responsible for building the custom update + * actions. + * @param variantId optional field representing the variant id in case the oldResource is an + * asset. + * @param resourceIdGetter a function used to get the id of the resource being updated. + * @param resourceTypeIdGetter a function used to get the Type id of the resource being updated. + * @param updateIdGetter a function used to get the id/key needed for updating the resource that + * has the custom fields. + * @param syncOptions responsible for supplying the sync options to the sync utility method. + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + @SuppressWarnings("unchecked") + @Nonnull + public static > + List> buildCustomUpdateActions( + @Nullable final Resource oldMainResource, + @Nullable final D newMainResourceDraft, + @Nonnull final T oldResource, + @Nonnull final S newResourceDraft, + @Nonnull final GenericCustomActionBuilder customActionBuilder, + @Nullable final Integer variantId, + @Nonnull final Function resourceIdGetter, + @Nonnull final Function resourceTypeIdGetter, + @Nonnull final Function updateIdGetter, + @Nonnull final BaseSyncOptions syncOptions) { - /** - * Compares the {@link CustomFields} of an old resource {@code T} (for example {@link Category}, - * {@link io.sphere.sdk.products.Product}, etc..), to the {@link CustomFieldsDraft}, of a new - * resource draft {@code S} (for example {@link CategoryDraft}, {@link io.sphere.sdk.products.ProductVariantDraft}, - * etc..), and returns a {@link List}<{@link UpdateAction}> as a result. If no update action is needed, - * for example in the case where both the {@link CustomFields} and the {@link CustomFieldsDraft} are null, an empty - * {@link List}<{@link UpdateAction}> is returned. A {@link BaseSyncOptions} instance is injected into the - * method which is responsible for supplying the sync options to the sync utility method. For example, custom error - * callbacks for errors. The {@link TypeService} is injected also for fetching the key of the old resource type - * from it's cache (see {@link CustomUpdateActionUtils#buildNonNullCustomFieldsUpdateActions(CustomFields, - * CustomFieldsDraft, Custom, GenericCustomActionBuilder, Integer, Function, Function, Function, BaseSyncOptions)}). - * - *

An update action will be added to the result list in the following cases:- - *

    - *
  1. If the new resources's custom type is set, but old resources's custom type is not. A "setCustomType" update - * actions is added, which sets the custom type (and all it's fields to the old resource).
  2. - *
  3. If the new resource's custom type is not set, but the old resource's custom type is set. A - * "setCustomType" update action is added, which removes the type set on the old resource.
  4. - *
  5. If both the resources custom types are the same and the custom fields are both set. The custom - * field values of both resources are then calculated. (see - * {@link CustomUpdateActionUtils#buildSetCustomFieldsUpdateActions(Map, Map, Custom, GenericCustomActionBuilder, - * Integer, Function)} )})
  6. - *
  7. If the keys of both custom types are different, then a "setCustomType" update action is added, where the - * old resource's custom type is set to be as the new one's.
  8. - *
  9. If both resources custom type keys are identical but the custom fields of the new resource's custom type is - * not set.
  10. - *
- * - *

An update action will not be added to the result list in the following cases:- - *

    - *
  1. If both the resources' custom types are not set.
  2. - *
  3. If both the resources' custom type keys are not set.
  4. - *
  5. Custom fields are both empty.
  6. - *
  7. Custom field JSON values have different ordering.
  8. - *
  9. Custom field values are identical.
  10. - *
- * - * @param the type of the new {@link Resource} which is the super {@link Resource} of the Resource to update. - * @param the type of the old {@link Resource} which has the custom fields. - * @param the type of the new resource {@link CustomDraft}. - * @param the type of the resource in which the update actions will be applied on. - * - * @param oldMainResource the main resource of the resource which should be updated. - * @param newMainResourceDraft the main resource of the resource draft where we get the new custom fields. - * @param oldResource the resource which should be updated. - * @param newResourceDraft the resource draft where we get the new custom fields. - * @param customActionBuilder the builder instance responsible for building the custom update actions. - * @param variantId optional field representing the variant id in case the oldResource is an asset. - * @param resourceIdGetter a function used to get the id of the resource being updated. - * @param resourceTypeIdGetter a function used to get the Type id of the resource being updated. - * @param updateIdGetter a function used to get the id/key needed for updating the resource that has the custom - * fields. - * @param syncOptions responsible for supplying the sync options to the sync utility method. - * @return a list that contains all the update actions needed, otherwise an - * empty list if no update actions are needed. - */ - @SuppressWarnings("unchecked") - @Nonnull - public static > List> - buildCustomUpdateActions( - @Nullable final Resource oldMainResource, - @Nullable final D newMainResourceDraft, - @Nonnull final T oldResource, - @Nonnull final S newResourceDraft, - @Nonnull final GenericCustomActionBuilder customActionBuilder, - @Nullable final Integer variantId, - @Nonnull final Function resourceIdGetter, - @Nonnull final Function resourceTypeIdGetter, - @Nonnull final Function updateIdGetter, - @Nonnull final BaseSyncOptions syncOptions) { - - final CustomFields oldResourceCustomFields = oldResource.getCustom(); - final CustomFieldsDraft newResourceCustomFields = newResourceDraft.getCustom(); - if (oldResourceCustomFields != null && newResourceCustomFields != null) { - try { - return buildNonNullCustomFieldsUpdateActions(oldResourceCustomFields, newResourceCustomFields, - oldResource, customActionBuilder, variantId, resourceIdGetter, resourceTypeIdGetter, - updateIdGetter, syncOptions); - } catch (BuildUpdateActionException exception) { - final String errorMessage = format(CUSTOM_FIELDS_UPDATE_ACTIONS_BUILD_FAILED, - resourceTypeIdGetter.apply(oldResource), - resourceIdGetter.apply(oldResource), exception.getMessage()); - syncOptions.applyErrorCallback(new SyncException(errorMessage, exception), - oldMainResource != null ? oldMainResource : oldResource, - newMainResourceDraft != null ? newMainResourceDraft : newResourceDraft, - null); - } - } else { - if (oldResourceCustomFields == null) { - if (newResourceCustomFields != null) { - // New resource's custom fields are set, but old resources's custom fields are not set. So we - // should set the custom type and fields of the new resource to the old one. - final String newCustomFieldsTypeId = newResourceCustomFields.getType().getId(); - if (isBlank(newCustomFieldsTypeId)) { - final String errorMessage = format(CUSTOM_FIELDS_UPDATE_ACTIONS_BUILD_FAILED, - resourceTypeIdGetter.apply(oldResource), resourceIdGetter.apply(oldResource), - CUSTOM_TYPE_ID_IS_BLANK); - syncOptions.applyErrorCallback(new SyncException(errorMessage, null), - oldMainResource != null ? oldMainResource : oldResource, - newMainResourceDraft != null ? newMainResourceDraft : newResourceDraft, - null); - } else { - final Map newCustomFieldsJsonMap = newResourceCustomFields.getFields(); - final Optional> updateAction = buildTypedSetCustomTypeUpdateAction( - newCustomFieldsTypeId, newCustomFieldsJsonMap, oldResource, customActionBuilder, - variantId, resourceIdGetter, resourceTypeIdGetter, updateIdGetter, syncOptions); - return updateAction.map(Collections::singletonList).orElseGet(Collections::emptyList); - } - } - } else { - // New resource's custom fields are not set, but old resource's custom fields are set. So we - // should remove the custom type from the old resource. - - return singletonList( - customActionBuilder.buildRemoveCustomTypeAction(variantId, updateIdGetter.apply(oldResource))); - } - } - return Collections.emptyList(); - } - - @Nonnull - private static > List> - buildCustomUpdateActions( - @Nonnull final T oldResource, - @Nonnull final S newResourceDraft, - @Nonnull final GenericCustomActionBuilder customActionBuilder, - @Nullable final Integer variantId, - @Nonnull final Function resourceIdGetter, - @Nonnull final Function resourceTypeIdGetter, - @Nonnull final Function updateIdGetter, - @Nonnull final BaseSyncOptions syncOptions) { - return buildCustomUpdateActions( - null, - null, + final CustomFields oldResourceCustomFields = oldResource.getCustom(); + final CustomFieldsDraft newResourceCustomFields = newResourceDraft.getCustom(); + if (oldResourceCustomFields != null && newResourceCustomFields != null) { + try { + return buildNonNullCustomFieldsUpdateActions( + oldResourceCustomFields, + newResourceCustomFields, oldResource, - newResourceDraft, customActionBuilder, variantId, resourceIdGetter, resourceTypeIdGetter, - updateIdGetter, // No update ID needed for primary resources. + updateIdGetter, syncOptions); + } catch (BuildUpdateActionException exception) { + final String errorMessage = + format( + CUSTOM_FIELDS_UPDATE_ACTIONS_BUILD_FAILED, + resourceTypeIdGetter.apply(oldResource), + resourceIdGetter.apply(oldResource), + exception.getMessage()); + syncOptions.applyErrorCallback( + new SyncException(errorMessage, exception), + oldMainResource != null ? oldMainResource : oldResource, + newMainResourceDraft != null ? newMainResourceDraft : newResourceDraft, + null); + } + } else { + if (oldResourceCustomFields == null) { + if (newResourceCustomFields != null) { + // New resource's custom fields are set, but old resources's custom fields are not set. So + // we + // should set the custom type and fields of the new resource to the old one. + final String newCustomFieldsTypeId = newResourceCustomFields.getType().getId(); + if (isBlank(newCustomFieldsTypeId)) { + final String errorMessage = + format( + CUSTOM_FIELDS_UPDATE_ACTIONS_BUILD_FAILED, + resourceTypeIdGetter.apply(oldResource), + resourceIdGetter.apply(oldResource), + CUSTOM_TYPE_ID_IS_BLANK); + syncOptions.applyErrorCallback( + new SyncException(errorMessage, null), + oldMainResource != null ? oldMainResource : oldResource, + newMainResourceDraft != null ? newMainResourceDraft : newResourceDraft, + null); + } else { + final Map newCustomFieldsJsonMap = + newResourceCustomFields.getFields(); + final Optional> updateAction = + buildTypedSetCustomTypeUpdateAction( + newCustomFieldsTypeId, + newCustomFieldsJsonMap, + oldResource, + customActionBuilder, + variantId, + resourceIdGetter, + resourceTypeIdGetter, + updateIdGetter, + syncOptions); + return updateAction.map(Collections::singletonList).orElseGet(Collections::emptyList); + } + } + } else { + // New resource's custom fields are not set, but old resource's custom fields are set. So we + // should remove the custom type from the old resource. + + return singletonList( + customActionBuilder.buildRemoveCustomTypeAction( + variantId, updateIdGetter.apply(oldResource))); + } } + return Collections.emptyList(); + } - /** - * Compares a non-null {@link CustomFields} to a non-null {@link CustomFieldsDraft} and returns a - * {@link List}<{@link UpdateAction}> as a result. The keys are used to compare the custom types. - * The key of the old resource custom type is fetched from the caching mechanism of the {@link TypeService} instance - * supplied as a param to the method. The key of the new resource custom type is expected to be set on the type. - * If no update action is needed an empty {@link List}<{@link UpdateAction}> is returned. - * - *

An update action will be added to the result list in the following cases:- - *

    - *
  1. If both the resources custom type keys are the same and the custom fields are both set. The custom - * field values of both resources are then calculated. (see - * {@link CustomUpdateActionUtils#buildSetCustomFieldsUpdateActions(Map, Map, Custom, GenericCustomActionBuilder, - * Integer, Function)}) - *
  2. - *
  3. If the keys of both custom types are different, then a "setCustomType" update action is added, where the - * old resource's custom type is set to be as the new one's.
  4. - *
  5. If both resources custom type keys are identical but the custom fields - * of the new resource's custom type is not set.
  6. - *
- * - *

An update action will not be added to the result list in the following cases:- - *

    - *
  1. If both the resources' custom type keys are not set.
  2. - *
- * - * @param the type of the old {@link Resource} which has the custom fields. - * @param the type of the resource in which the update actions will be applied on. - * @param oldCustomFields the old resource's custom fields. - * @param newCustomFields the new resource draft's custom fields. - * @param resource the resource that the custom fields are on. It is used to identify the type of the - * resource, to call the corresponding update actions. - * @param customActionBuilder the builder instance responsible for building the custom update actions. - * @param variantId optional field representing the variant id in case the oldResource is an asset. - * @param resourceIdGetter a function used to get the id of the resource being updated. - * @param resourceTypeIdGetter a function used to get the Type id of the resource being updated. - * @param updateIdGetter a function used to get the id/key needed for updating the resource that has the - * custom fields. - * @param syncOptions responsible for supplying the sync options to the sync utility method. - * @return a list that contains all the update actions needed, otherwise an empty list if no update - * actions are needed. - */ - @Nonnull - static > List> buildNonNullCustomFieldsUpdateActions( - @Nonnull final CustomFields oldCustomFields, - @Nonnull final CustomFieldsDraft newCustomFields, - @Nonnull final T resource, - @Nonnull final GenericCustomActionBuilder customActionBuilder, - @Nullable final Integer variantId, - @Nonnull final Function resourceIdGetter, - @Nonnull final Function resourceTypeIdGetter, - @Nonnull final Function updateIdGetter, - @Nonnull final BaseSyncOptions syncOptions) throws BuildUpdateActionException { + @Nonnull + private static > + List> buildCustomUpdateActions( + @Nonnull final T oldResource, + @Nonnull final S newResourceDraft, + @Nonnull final GenericCustomActionBuilder customActionBuilder, + @Nullable final Integer variantId, + @Nonnull final Function resourceIdGetter, + @Nonnull final Function resourceTypeIdGetter, + @Nonnull final Function updateIdGetter, + @Nonnull final BaseSyncOptions syncOptions) { + return buildCustomUpdateActions( + null, + null, + oldResource, + newResourceDraft, + customActionBuilder, + variantId, + resourceIdGetter, + resourceTypeIdGetter, + updateIdGetter, // No update ID needed for primary resources. + syncOptions); + } - final String oldCustomTypeId = oldCustomFields.getType().getId(); - final Map oldCustomFieldsJsonMap = oldCustomFields.getFieldsJsonMap(); - final String newCustomTypeId = newCustomFields.getType().getId(); - final Map newCustomFieldsJsonMap = newCustomFields.getFields(); + /** + * Compares a non-null {@link CustomFields} to a non-null {@link CustomFieldsDraft} and returns a + * {@link List}<{@link UpdateAction}> as a result. The keys are used to compare the custom + * types. The key of the old resource custom type is fetched from the caching mechanism of the + * {@link TypeService} instance supplied as a param to the method. The key of the new resource + * custom type is expected to be set on the type. If no update action is needed an empty {@link + * List}<{@link UpdateAction}> is returned. + * + *

An update action will be added to the result list in the following cases:- + * + *

    + *
  1. If both the resources custom type keys are the same and the custom fields are both set. + * The custom field values of both resources are then calculated. (see {@link + * CustomUpdateActionUtils#buildSetCustomFieldsUpdateActions(Map, Map, Custom, + * GenericCustomActionBuilder, Integer, Function)}) + *
  2. If the keys of both custom types are different, then a "setCustomType" update action is + * added, where the old resource's custom type is set to be as the new one's. + *
  3. If both resources custom type keys are identical but the custom fields of the new + * resource's custom type is not set. + *
+ * + *

An update action will not be added to the result list in the following cases:- + * + *

    + *
  1. If both the resources' custom type keys are not set. + *
+ * + * @param the type of the old {@link Resource} which has the custom fields. + * @param the type of the resource in which the update actions will be applied on. + * @param oldCustomFields the old resource's custom fields. + * @param newCustomFields the new resource draft's custom fields. + * @param resource the resource that the custom fields are on. It is used to identify the type of + * the resource, to call the corresponding update actions. + * @param customActionBuilder the builder instance responsible for building the custom update + * actions. + * @param variantId optional field representing the variant id in case the oldResource is an + * asset. + * @param resourceIdGetter a function used to get the id of the resource being updated. + * @param resourceTypeIdGetter a function used to get the Type id of the resource being updated. + * @param updateIdGetter a function used to get the id/key needed for updating the resource that + * has the custom fields. + * @param syncOptions responsible for supplying the sync options to the sync utility method. + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + @Nonnull + static > + List> buildNonNullCustomFieldsUpdateActions( + @Nonnull final CustomFields oldCustomFields, + @Nonnull final CustomFieldsDraft newCustomFields, + @Nonnull final T resource, + @Nonnull final GenericCustomActionBuilder customActionBuilder, + @Nullable final Integer variantId, + @Nonnull final Function resourceIdGetter, + @Nonnull final Function resourceTypeIdGetter, + @Nonnull final Function updateIdGetter, + @Nonnull final BaseSyncOptions syncOptions) + throws BuildUpdateActionException { - if (Objects.equals(oldCustomTypeId, newCustomTypeId)) { - if (isBlank(oldCustomTypeId)) { - throw new BuildUpdateActionException( - format(CUSTOM_TYPE_IDS_NOT_SET, resourceTypeIdGetter.apply(resource))); - } - if (newCustomFieldsJsonMap == null) { - // New resource's custom fields are null/not set. So we should unset old custom fields. - final Optional> updateAction = buildTypedSetCustomTypeUpdateAction(newCustomTypeId, - null, resource, customActionBuilder, variantId, resourceIdGetter, - resourceTypeIdGetter, updateIdGetter, syncOptions); + final String oldCustomTypeId = oldCustomFields.getType().getId(); + final Map oldCustomFieldsJsonMap = oldCustomFields.getFieldsJsonMap(); + final String newCustomTypeId = newCustomFields.getType().getId(); + final Map newCustomFieldsJsonMap = newCustomFields.getFields(); - return updateAction.map(Collections::singletonList).orElseGet(Collections::emptyList); - } - // old and new resource's custom fields are set. So we should calculate update actions for the - // the fields of both. - return buildSetCustomFieldsUpdateActions(oldCustomFieldsJsonMap, newCustomFieldsJsonMap, resource, - customActionBuilder, variantId, updateIdGetter); - } else { - final Optional> updateAction = buildTypedSetCustomTypeUpdateAction(newCustomTypeId, - newCustomFieldsJsonMap, resource, customActionBuilder, variantId, resourceIdGetter, - resourceTypeIdGetter, updateIdGetter, syncOptions); - return updateAction.map(Collections::singletonList).orElseGet(Collections::emptyList); - } + if (Objects.equals(oldCustomTypeId, newCustomTypeId)) { + if (isBlank(oldCustomTypeId)) { + throw new BuildUpdateActionException( + format(CUSTOM_TYPE_IDS_NOT_SET, resourceTypeIdGetter.apply(resource))); + } + if (newCustomFieldsJsonMap == null) { + // New resource's custom fields are null/not set. So we should unset old custom fields. + final Optional> updateAction = + buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + null, + resource, + customActionBuilder, + variantId, + resourceIdGetter, + resourceTypeIdGetter, + updateIdGetter, + syncOptions); + + return updateAction.map(Collections::singletonList).orElseGet(Collections::emptyList); + } + // old and new resource's custom fields are set. So we should calculate update actions for the + // the fields of both. + return buildSetCustomFieldsUpdateActions( + oldCustomFieldsJsonMap, + newCustomFieldsJsonMap, + resource, + customActionBuilder, + variantId, + updateIdGetter); + } else { + final Optional> updateAction = + buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + newCustomFieldsJsonMap, + resource, + customActionBuilder, + variantId, + resourceIdGetter, + resourceTypeIdGetter, + updateIdGetter, + syncOptions); + return updateAction.map(Collections::singletonList).orElseGet(Collections::emptyList); } + } - /** - * Compares two {@link Map} objects representing a map of the custom field name to the JSON representation - * of the value of the corresponding custom field. It returns a {@link List}<{@link UpdateAction}> as a - * result. - * If no update action is needed an empty {@link List}<{@link UpdateAction}> is returned. - * - *

An update action will be added to the result list in the following cases:- - *

    - *
  1. A custom field value is changed.
  2. - *
  3. A custom field value is removed.
  4. - *
  5. A new custom field value is added.
  6. - *
- * - *

An update action will not be added to the result list in the following cases:- - *

    - *
  1. Custom fields are both empty.
  2. - *
  3. Custom field JSON values have different ordering.
  4. - *
  5. Custom field values are identical.
  6. - *
- * - * @param the type of the old {@link Resource} which has the custom fields. - * @param the type of the resource in which the update actions will be applied on. - * @param oldCustomFields the old resource's custom fields map of JSON values. - * @param newCustomFields the new resource's custom fields map of JSON values. - * @param resource the resource that the custom fields are on. It is used to identify the type of - * the resource, to call the corresponding update actions. - * @param customActionBuilder the builder instance responsible for building the custom update actions. - * @param variantId optional field representing the variant id in case the oldResource is an asset. - * @param updateIdGetter a function used to get the id/key needed for updating the resource that has the - * custom fields. - * @return a list that contains all the update actions needed, otherwise an empty list if no - * update actions are needed. - */ - @Nonnull - static > List> buildSetCustomFieldsUpdateActions( - @Nonnull final Map oldCustomFields, - @Nonnull final Map newCustomFields, - @Nonnull final T resource, - @Nonnull final GenericCustomActionBuilder customActionBuilder, - @Nullable final Integer variantId, - @Nonnull final Function updateIdGetter) { + /** + * Compares two {@link Map} objects representing a map of the custom field name to the JSON + * representation of the value of the corresponding custom field. It returns a {@link + * List}<{@link UpdateAction}> as a result. If no update action is needed an empty {@link + * List}<{@link UpdateAction}> is returned. + * + *

An update action will be added to the result list in the following cases:- + * + *

    + *
  1. A custom field value is changed. + *
  2. A custom field value is removed. + *
  3. A new custom field value is added. + *
+ * + *

An update action will not be added to the result list in the following cases:- + * + *

    + *
  1. Custom fields are both empty. + *
  2. Custom field JSON values have different ordering. + *
  3. Custom field values are identical. + *
+ * + * @param the type of the old {@link Resource} which has the custom fields. + * @param the type of the resource in which the update actions will be applied on. + * @param oldCustomFields the old resource's custom fields map of JSON values. + * @param newCustomFields the new resource's custom fields map of JSON values. + * @param resource the resource that the custom fields are on. It is used to identify the type of + * the resource, to call the corresponding update actions. + * @param customActionBuilder the builder instance responsible for building the custom update + * actions. + * @param variantId optional field representing the variant id in case the oldResource is an + * asset. + * @param updateIdGetter a function used to get the id/key needed for updating the resource that + * has the custom fields. + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + @Nonnull + static > + List> buildSetCustomFieldsUpdateActions( + @Nonnull final Map oldCustomFields, + @Nonnull final Map newCustomFields, + @Nonnull final T resource, + @Nonnull final GenericCustomActionBuilder customActionBuilder, + @Nullable final Integer variantId, + @Nonnull final Function updateIdGetter) { - final List> customFieldsUpdateActions = - buildNewOrModifiedCustomFieldsUpdateActions(oldCustomFields, newCustomFields, resource, - customActionBuilder, variantId, updateIdGetter); + final List> customFieldsUpdateActions = + buildNewOrModifiedCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + resource, + customActionBuilder, + variantId, + updateIdGetter); - final List> removedCustomFieldsActions = - buildRemovedCustomFieldsUpdateActions(oldCustomFields, newCustomFields, resource, - customActionBuilder, variantId, updateIdGetter); + final List> removedCustomFieldsActions = + buildRemovedCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + resource, + customActionBuilder, + variantId, + updateIdGetter); - customFieldsUpdateActions.addAll(removedCustomFieldsActions); - return customFieldsUpdateActions; - } + customFieldsUpdateActions.addAll(removedCustomFieldsActions); + return customFieldsUpdateActions; + } - /** - * Traverses the new resource's custom fields map of JSON values {@link Map} to create - * "setCustomField" update actions that represent either the modification of an existent custom field's - * value or the addition of a new one. It returns a {@link List}<{@link UpdateAction}> as a result. - * If no update action is needed an empty {@link List}<{@link UpdateAction}> is returned. - * - *

Note: Null value custom fields are filtered out. In other words, - * no update actions would be built for fields with null values. - * - * @param the type of the old {@link Resource} which has the custom fields. - * @param the type of the resource in which the update actions will be applied on. - * @param oldCustomFields the old resource's custom fields map of JSON values. - * @param newCustomFields the new resource's custom fields map of JSON values. - * @param resource the resource that the custom fields are on. It is used to identify the - * type of the resource, to call the corresponding update actions. - * @param customActionBuilder the builder instance responsible for building the custom update actions. - * @param variantId optional field representing the variant id in case the oldResource is an asset. - * @param updateIdGetter a function used to get the id/key needed for updating the resource that has the - * custom fields. - * @return a list that contains all the update actions needed, otherwise an empty list if no update actions are - * needed. - */ - @Nonnull - private static > List> - buildNewOrModifiedCustomFieldsUpdateActions( - @Nonnull final Map oldCustomFields, - @Nonnull final Map newCustomFields, - @Nonnull final T resource, - @Nonnull final GenericCustomActionBuilder customActionBuilder, - @Nullable final Integer variantId, - @Nonnull final Function updateIdGetter) { + /** + * Traverses the new resource's custom fields map of JSON values {@link Map} to create + * "setCustomField" update actions that represent either the modification of an existent custom + * field's value or the addition of a new one. It returns a {@link List}<{@link + * UpdateAction}> as a result. If no update action is needed an empty {@link List}<{@link + * UpdateAction}> is returned. + * + *

Note: Null value custom fields are filtered out. In other words, no update actions would be + * built for fields with null values. + * + * @param the type of the old {@link Resource} which has the custom fields. + * @param the type of the resource in which the update actions will be applied on. + * @param oldCustomFields the old resource's custom fields map of JSON values. + * @param newCustomFields the new resource's custom fields map of JSON values. + * @param resource the resource that the custom fields are on. It is used to identify the type of + * the resource, to call the corresponding update actions. + * @param customActionBuilder the builder instance responsible for building the custom update + * actions. + * @param variantId optional field representing the variant id in case the oldResource is an + * asset. + * @param updateIdGetter a function used to get the id/key needed for updating the resource that + * has the custom fields. + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + @Nonnull + private static > + List> buildNewOrModifiedCustomFieldsUpdateActions( + @Nonnull final Map oldCustomFields, + @Nonnull final Map newCustomFields, + @Nonnull final T resource, + @Nonnull final GenericCustomActionBuilder customActionBuilder, + @Nullable final Integer variantId, + @Nonnull final Function updateIdGetter) { - return newCustomFields - .keySet() - .stream() - .filter(newCustomFieldName -> { - final JsonNode newCustomFieldValue = newCustomFields.get(newCustomFieldName); - final JsonNode oldCustomFieldValue = oldCustomFields.get(newCustomFieldName); + return newCustomFields.keySet().stream() + .filter( + newCustomFieldName -> { + final JsonNode newCustomFieldValue = newCustomFields.get(newCustomFieldName); + final JsonNode oldCustomFieldValue = oldCustomFields.get(newCustomFieldName); - return !isNullJsonValue(newCustomFieldValue) - && !Objects.equals(newCustomFieldValue, oldCustomFieldValue); + return !isNullJsonValue(newCustomFieldValue) + && !Objects.equals(newCustomFieldValue, oldCustomFieldValue); }) - .map(newCustomFieldName -> customActionBuilder.buildSetCustomFieldAction(variantId, - updateIdGetter.apply(resource), newCustomFieldName, - newCustomFields.get(newCustomFieldName))) - .collect(Collectors.toList()); - } + .map( + newCustomFieldName -> + customActionBuilder.buildSetCustomFieldAction( + variantId, + updateIdGetter.apply(resource), + newCustomFieldName, + newCustomFields.get(newCustomFieldName))) + .collect(Collectors.toList()); + } - private static boolean isNullJsonValue(@Nullable final JsonNode value) { - return !Objects.nonNull(value) || value.isNull(); - } + private static boolean isNullJsonValue(@Nullable final JsonNode value) { + return !Objects.nonNull(value) || value.isNull(); + } - /** - * Traverses the old resource's custom fields map of JSON values {@link Map} to create update - * "setCustomField" update actions that represent removal of an existent custom field from the new resource's custom - * fields. It returns a {@link List}<{@link UpdateAction}> as a result. - * If no update action is needed an empty {@link List}<{@link UpdateAction}> is returned. - * - * @param the type of the old {@link Resource} which has the custom fields. - * @param the type of the resource in which the update actions will be applied on. - * @param oldCustomFields the old resource's custom fields map of JSON values. - * @param newCustomFields the new resources's custom fields map of JSON values. - * @param resource the resource that the custom fields are on. It is used to identify the type of - * the resource, to call the corresponding update actions. - * @param customActionBuilder the builder instance responsible for building the custom update actions. - * @param variantId optional field representing the variant id in case the oldResource is an asset. - * @param updateIdGetter a function used to get the id/key needed for updating the resource that has the - * custom fields. - * @return a list that contains all the update actions needed, otherwise an empty list if no update actions are - * needed. - */ - @Nonnull - private static > List> - buildRemovedCustomFieldsUpdateActions( - @Nonnull final Map oldCustomFields, - @Nonnull final Map newCustomFields, - @Nonnull final T resource, - @Nonnull final GenericCustomActionBuilder customActionBuilder, - @Nullable final Integer variantId, - @Nonnull final Function updateIdGetter) { + /** + * Traverses the old resource's custom fields map of JSON values {@link Map} to create update + * "setCustomField" update actions that represent removal of an existent custom field from the new + * resource's custom fields. It returns a {@link List}<{@link UpdateAction}> as a result. If + * no update action is needed an empty {@link List}<{@link UpdateAction}> is returned. + * + * @param the type of the old {@link Resource} which has the custom fields. + * @param the type of the resource in which the update actions will be applied on. + * @param oldCustomFields the old resource's custom fields map of JSON values. + * @param newCustomFields the new resources's custom fields map of JSON values. + * @param resource the resource that the custom fields are on. It is used to identify the type of + * the resource, to call the corresponding update actions. + * @param customActionBuilder the builder instance responsible for building the custom update + * actions. + * @param variantId optional field representing the variant id in case the oldResource is an + * asset. + * @param updateIdGetter a function used to get the id/key needed for updating the resource that + * has the custom fields. + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + @Nonnull + private static > + List> buildRemovedCustomFieldsUpdateActions( + @Nonnull final Map oldCustomFields, + @Nonnull final Map newCustomFields, + @Nonnull final T resource, + @Nonnull final GenericCustomActionBuilder customActionBuilder, + @Nullable final Integer variantId, + @Nonnull final Function updateIdGetter) { - return oldCustomFields - .keySet() - .stream() - .filter(oldCustomFieldsName -> isNullJsonValue(newCustomFields.get(oldCustomFieldsName))) - .map(oldCustomFieldsName -> customActionBuilder.buildSetCustomFieldAction(variantId, - updateIdGetter.apply(resource), oldCustomFieldsName, null)) - .collect(Collectors.toList()); - } + return oldCustomFields.keySet().stream() + .filter(oldCustomFieldsName -> isNullJsonValue(newCustomFields.get(oldCustomFieldsName))) + .map( + oldCustomFieldsName -> + customActionBuilder.buildSetCustomFieldAction( + variantId, updateIdGetter.apply(resource), oldCustomFieldsName, null)) + .collect(Collectors.toList()); + } - private CustomUpdateActionUtils() { - } + private CustomUpdateActionUtils() {} } - diff --git a/src/main/java/com/commercetools/sync/commons/utils/EnumValuesUpdateActionUtils.java b/src/main/java/com/commercetools/sync/commons/utils/EnumValuesUpdateActionUtils.java index f3c92457af..6be4d3e0c4 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/EnumValuesUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/EnumValuesUpdateActionUtils.java @@ -1,14 +1,17 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Optional.empty; +import static java.util.Optional.of; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.EnumValue; import io.sphere.sdk.models.WithKey; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -17,417 +20,404 @@ import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.stream.Stream; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Optional.empty; -import static java.util.Optional.of; -import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; /** - * The utils in this class are only meant for the internal use of the commercetools-sync-java library. + * The utils in this class are only meant for the internal use of the commercetools-sync-java + * library. */ public final class EnumValuesUpdateActionUtils { - /** - * Compares a list of old {@code oldEnumValues} with a list of new {@code newEnumValues} for a given definition. - * The method serves as an implementation for enum values syncing. The method takes in functions - * for building the required update actions (AddEnumValue, ChangeEnumValueOrder - * and 1-1 update actions on enum values (e.g. changeLabel) for the required resource. - * - * @param definitionName the definition name whose enum values are going to be synced. - * @param oldEnumValues the old list of plain enum values. - * @param newEnumValues the new list of plain enum values. - * @param removeEnumCallback the function that is called in order to remove the new enum instance. - * @param matchingEnumCallback the function that is called to get the update action resulting from - * comparing the enum value fields one by one. - * @param addEnumCallback the function that is called in order to add the new enum instance. - * @param changeOrderEnumCallback the function that is called to apply the change in the order. - * @param changeOrderWithKeysEnumCallback the function that is called to apply the change in the order with keys. - * @param the enum type of the elements to change the order for. - * @param the type of the resource in which the update actions will be applied on. - * @return a list of enum values update actions if the list of plain enum values is not identical. - * Otherwise, if the plain enum values are identical, an empty list is returned. - */ - @Nonnull - public static List> buildActions( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nullable final List newEnumValues, - @Nullable final BiFunction, UpdateAction> removeEnumCallback, - @Nullable final TriFunction>> matchingEnumCallback, - @Nullable final BiFunction> addEnumCallback, - @Nullable final BiFunction, UpdateAction> changeOrderEnumCallback, - @Nullable final BiFunction, UpdateAction> changeOrderWithKeysEnumCallback) { - - if (newEnumValues != null) { - return buildUpdateActions(definitionName, - oldEnumValues, - newEnumValues, - removeEnumCallback, - matchingEnumCallback, - addEnumCallback, - changeOrderEnumCallback, - changeOrderWithKeysEnumCallback - ); - } else if (removeEnumCallback != null) { - return buildRemoveEnumValuesUpdateAction( - definitionName, - oldEnumValues, - null, - removeEnumCallback) - .map(Collections::singletonList) - .orElse(emptyList()); - } - - return emptyList(); + /** + * Compares a list of old {@code oldEnumValues} with a list of new {@code newEnumValues} for a + * given definition. The method serves as an implementation for enum values syncing. The method + * takes in functions for building the required update actions (AddEnumValue, ChangeEnumValueOrder + * and 1-1 update actions on enum values (e.g. changeLabel) for the required resource. + * + * @param definitionName the definition name whose enum values are going to be synced. + * @param oldEnumValues the old list of plain enum values. + * @param newEnumValues the new list of plain enum values. + * @param removeEnumCallback the function that is called in order to remove the new enum instance. + * @param matchingEnumCallback the function that is called to get the update action resulting from + * comparing the enum value fields one by one. + * @param addEnumCallback the function that is called in order to add the new enum instance. + * @param changeOrderEnumCallback the function that is called to apply the change in the order. + * @param changeOrderWithKeysEnumCallback the function that is called to apply the change in the + * order with keys. + * @param the enum type of the elements to change the order for. + * @param the type of the resource in which the update actions will be applied on. + * @return a list of enum values update actions if the list of plain enum values is not identical. + * Otherwise, if the plain enum values are identical, an empty list is returned. + */ + @Nonnull + public static List> buildActions( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nullable final List newEnumValues, + @Nullable final BiFunction, UpdateAction> removeEnumCallback, + @Nullable final TriFunction>> matchingEnumCallback, + @Nullable final BiFunction> addEnumCallback, + @Nullable final BiFunction, UpdateAction> changeOrderEnumCallback, + @Nullable + final BiFunction, UpdateAction> changeOrderWithKeysEnumCallback) { + + if (newEnumValues != null) { + return buildUpdateActions( + definitionName, + oldEnumValues, + newEnumValues, + removeEnumCallback, + matchingEnumCallback, + addEnumCallback, + changeOrderEnumCallback, + changeOrderWithKeysEnumCallback); + } else if (removeEnumCallback != null) { + return buildRemoveEnumValuesUpdateAction( + definitionName, oldEnumValues, null, removeEnumCallback) + .map(Collections::singletonList) + .orElse(emptyList()); } - @Nonnull - private static List> buildUpdateActions( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nullable final BiFunction, UpdateAction> removeEnumValuesUpdateActionCallback, - @Nullable final TriFunction>> matchingEnumCallback, - @Nullable final BiFunction> addEnumCallback, - @Nullable final BiFunction, UpdateAction> changeOrderEnumCallback, - @Nullable final BiFunction, UpdateAction> changeOrderWithKeysEnumCallback) { - - final Optional> removeEnumValuesUpdateAction = - getRemoveEnumValuesUpdateAction(definitionName, oldEnumValues, newEnumValues, - removeEnumValuesUpdateActionCallback); - - final List> matchingEnumValuesUpdateActions = getMatchingEnumValuesUpdateActions( - definitionName, oldEnumValues, newEnumValues, matchingEnumCallback); - - final List> addEnumValuesUpdateActions = getAddEnumValuesUpdateActions(definitionName, - oldEnumValues, newEnumValues, addEnumCallback); - - final Optional> changeEnumValuesOrderUpdateAction = getChangeEnumValuesOrderUpdateAction( - definitionName, oldEnumValues, newEnumValues, changeOrderEnumCallback); - - final Optional> changeEnumValuesWithKeysOrderUpdateActions = - getChangeEnumValuesWithKeysOrderUpdateAction( - definitionName, oldEnumValues, newEnumValues, - changeOrderWithKeysEnumCallback); - - return Stream - .of( - removeEnumValuesUpdateAction.map(Collections::singletonList).orElse(emptyList()), - matchingEnumValuesUpdateActions, - addEnumValuesUpdateActions, - changeEnumValuesOrderUpdateAction.map(Collections::singletonList).orElse(emptyList()), - changeEnumValuesWithKeysOrderUpdateActions.map(Collections::singletonList).orElse(emptyList())) - .flatMap(Collection::stream) + return emptyList(); + } + + @Nonnull + private static List> buildUpdateActions( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nullable + final BiFunction, UpdateAction> + removeEnumValuesUpdateActionCallback, + @Nullable final TriFunction>> matchingEnumCallback, + @Nullable final BiFunction> addEnumCallback, + @Nullable final BiFunction, UpdateAction> changeOrderEnumCallback, + @Nullable + final BiFunction, UpdateAction> changeOrderWithKeysEnumCallback) { + + final Optional> removeEnumValuesUpdateAction = + getRemoveEnumValuesUpdateAction( + definitionName, oldEnumValues, newEnumValues, removeEnumValuesUpdateActionCallback); + + final List> matchingEnumValuesUpdateActions = + getMatchingEnumValuesUpdateActions( + definitionName, oldEnumValues, newEnumValues, matchingEnumCallback); + + final List> addEnumValuesUpdateActions = + getAddEnumValuesUpdateActions( + definitionName, oldEnumValues, newEnumValues, addEnumCallback); + + final Optional> changeEnumValuesOrderUpdateAction = + getChangeEnumValuesOrderUpdateAction( + definitionName, oldEnumValues, newEnumValues, changeOrderEnumCallback); + + final Optional> changeEnumValuesWithKeysOrderUpdateActions = + getChangeEnumValuesWithKeysOrderUpdateAction( + definitionName, oldEnumValues, newEnumValues, changeOrderWithKeysEnumCallback); + + return Stream.of( + removeEnumValuesUpdateAction.map(Collections::singletonList).orElse(emptyList()), + matchingEnumValuesUpdateActions, + addEnumValuesUpdateActions, + changeEnumValuesOrderUpdateAction.map(Collections::singletonList).orElse(emptyList()), + changeEnumValuesWithKeysOrderUpdateActions + .map(Collections::singletonList) + .orElse(emptyList())) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + @Nonnull + private static + Optional> getChangeEnumValuesWithKeysOrderUpdateAction( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nullable + final BiFunction, UpdateAction> + changeOrderWithKeysEnumCallback) { + + return changeOrderWithKeysEnumCallback == null + ? Optional.empty() + : buildChangeEnumValuesWithKeysOrderUpdateAction( + definitionName, oldEnumValues, newEnumValues, changeOrderWithKeysEnumCallback); + } + + @Nonnull + private static + Optional> getChangeEnumValuesOrderUpdateAction( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nullable final BiFunction, UpdateAction> changeOrderEnumCallback) { + + return changeOrderEnumCallback == null + ? Optional.empty() + : buildChangeEnumValuesOrderUpdateAction( + definitionName, oldEnumValues, newEnumValues, changeOrderEnumCallback); + } + + @Nonnull + private static List> getAddEnumValuesUpdateActions( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nullable final BiFunction> addEnumCallback) { + + return addEnumCallback == null + ? emptyList() + : buildAddEnumValuesUpdateActions( + definitionName, oldEnumValues, newEnumValues, addEnumCallback); + } + + @Nonnull + private static List> getMatchingEnumValuesUpdateActions( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nullable final TriFunction>> matchingEnumCallback) { + + return matchingEnumCallback == null + ? emptyList() + : buildMatchingEnumValuesUpdateActions( + definitionName, oldEnumValues, newEnumValues, matchingEnumCallback); + } + + @Nonnull + private static Optional> getRemoveEnumValuesUpdateAction( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nullable + final BiFunction, UpdateAction> + removeEnumValuesUpdateActionCallback) { + + return removeEnumValuesUpdateActionCallback == null + ? Optional.empty() + : buildRemoveEnumValuesUpdateAction( + definitionName, oldEnumValues, newEnumValues, removeEnumValuesUpdateActionCallback); + } + + /** + * Compares the order of a list of old enum values and a list of new enum values. If there is a + * change in order, then a change of enum values order (with the new order) is built. If there are + * no changes in order an empty optional is returned. + * + * @param definitionName the definition name whose enum values belong to. + * @param oldEnumValues the list of old enum values. + * @param newEnumValues the list of new enum values. + * @param changeOrderEnumCallback the function that is called to apply the change in the order. + * @param the enum type of the elements to change the order for. + * @param the type of the resource in which the update actions will be applied on. + * @return an optional update action if the the order of the enum values is not identical. + * Otherwise, if the enum values order is identical, an empty optional is returned. + */ + @Nonnull + static Optional> buildChangeEnumValuesOrderUpdateAction( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nonnull final BiFunction, UpdateAction> changeOrderEnumCallback) { + + final Pair, List> keysPair = + getAllKeysAndNewKeysPair(oldEnumValues, newEnumValues); + return buildUpdateAction( + keysPair.getLeft(), // all keys + keysPair.getRight(), // new keys + () -> changeOrderEnumCallback.apply(definitionName, newEnumValues)); + } + + /** + * Compares the order of a list of old enum values and a list of new enum values. If there is a + * change in order, then a change of enum values order (with the new order) is built. If there are + * no changes in order an empty optional is returned. + * + * @param definitionName the definition name whose enum values belong to. + * @param oldEnumValues the list of old enum values. + * @param newEnumValues the list of new enum values. + * @param changeOrderEnumCallback the function that is called to apply the change in the order. + * @param the enum type of the elements to change the order for. + * @return an optional update action if the the order of the enum values is not identical. + * Otherwise, if the enum values order is identical, an empty optional is returned. + */ + @Nonnull + private static + Optional> buildChangeEnumValuesWithKeysOrderUpdateAction( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nonnull + final BiFunction, UpdateAction> changeOrderEnumCallback) { + + final Pair, List> keysPair = + getAllKeysAndNewKeysPair(oldEnumValues, newEnumValues); + return buildUpdateAction( + keysPair.getLeft(), // all keys + keysPair.getRight(), // new keys + () -> changeOrderEnumCallback.apply(definitionName, keysPair.getRight())); + } + + @Nonnull + private static Pair, List> getAllKeysAndNewKeysPair( + @Nonnull final List oldEnumValues, @Nonnull final List newEnumValues) { + + final List newKeys = + newEnumValues.stream().map(WithKey::getKey).collect(Collectors.toList()); + + final List existingKeys = + oldEnumValues.stream() + .map(WithKey::getKey) + .filter(newKeys::contains) .collect(Collectors.toList()); - } - - @Nonnull - private static Optional> getChangeEnumValuesWithKeysOrderUpdateAction( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nullable final BiFunction, UpdateAction> changeOrderWithKeysEnumCallback) { - - return changeOrderWithKeysEnumCallback == null ? Optional.empty() : - buildChangeEnumValuesWithKeysOrderUpdateAction( - definitionName, - oldEnumValues, - newEnumValues, - changeOrderWithKeysEnumCallback - ); - } - @Nonnull - private static Optional> getChangeEnumValuesOrderUpdateAction( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nullable final BiFunction, UpdateAction> changeOrderEnumCallback) { - - return changeOrderEnumCallback == null ? Optional.empty() : - buildChangeEnumValuesOrderUpdateAction( - definitionName, - oldEnumValues, - newEnumValues, - changeOrderEnumCallback - ); - } - - @Nonnull - private static List> getAddEnumValuesUpdateActions( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nullable final BiFunction> addEnumCallback) { - - return addEnumCallback == null ? emptyList() : buildAddEnumValuesUpdateActions( - definitionName, - oldEnumValues, - newEnumValues, - addEnumCallback - ); - } - - @Nonnull - private static List> getMatchingEnumValuesUpdateActions( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nullable final TriFunction>> matchingEnumCallback) { - - return matchingEnumCallback == null ? emptyList() : - buildMatchingEnumValuesUpdateActions( - definitionName, - oldEnumValues, - newEnumValues, - matchingEnumCallback - ); - } - - @Nonnull - private static Optional> getRemoveEnumValuesUpdateAction( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nullable final BiFunction, UpdateAction> removeEnumValuesUpdateActionCallback) { - - return removeEnumValuesUpdateActionCallback == null ? Optional.empty() : - buildRemoveEnumValuesUpdateAction( - definitionName, - oldEnumValues, - newEnumValues, - removeEnumValuesUpdateActionCallback); - } - - /** - * Compares the order of a list of old enum values and a list of new enum values. If there is a change in order, - * then a change of enum values order (with the new order) is built. If there are no changes in order an empty - * optional is returned. - * - * @param definitionName the definition name whose enum values belong to. - * @param oldEnumValues the list of old enum values. - * @param newEnumValues the list of new enum values. - * @param changeOrderEnumCallback the function that is called to apply the change in the order. - * @param the enum type of the elements to change the order for. - * @param the type of the resource in which the update actions will be applied on. - * @return an optional update action if the the order of the enum values is not identical. - * Otherwise, if the enum values order is identical, an empty optional is returned. - */ - @Nonnull - static Optional> buildChangeEnumValuesOrderUpdateAction( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nonnull final BiFunction, UpdateAction> changeOrderEnumCallback) { - - final Pair, List> keysPair = getAllKeysAndNewKeysPair(oldEnumValues, newEnumValues); - return buildUpdateAction( - keysPair.getLeft(), //all keys - keysPair.getRight(), // new keys - () -> changeOrderEnumCallback.apply(definitionName, newEnumValues) - ); - } - - /** - * Compares the order of a list of old enum values and a list of new enum values. If there is a change in order, - * then a change of enum values order (with the new order) is built. If there are no changes in order an empty - * optional is returned. - * - * @param definitionName the definition name whose enum values belong to. - * @param oldEnumValues the list of old enum values. - * @param newEnumValues the list of new enum values. - * @param changeOrderEnumCallback the function that is called to apply the change in the order. - * @param the enum type of the elements to change the order for. - * @return an optional update action if the the order of the enum values is not identical. - * Otherwise, if the enum values order is identical, an empty optional is returned. - */ - @Nonnull - private static Optional> buildChangeEnumValuesWithKeysOrderUpdateAction( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nonnull final BiFunction, UpdateAction> changeOrderEnumCallback) { - - final Pair, List> keysPair = getAllKeysAndNewKeysPair(oldEnumValues, newEnumValues); - return buildUpdateAction( - keysPair.getLeft(), //all keys - keysPair.getRight(), // new keys - () -> changeOrderEnumCallback.apply(definitionName, keysPair.getRight()) - ); - } - - @Nonnull - private static Pair, List> getAllKeysAndNewKeysPair( - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues) { - - final List newKeys = newEnumValues - .stream() - .map(WithKey::getKey) - .collect(Collectors.toList()); - - final List existingKeys = oldEnumValues - .stream() - .map(WithKey::getKey) - .filter(newKeys::contains) - .collect(Collectors.toList()); - - final List notExistingKeys = newKeys - .stream() - .filter(newKey -> !existingKeys.contains(newKey)) - .collect(Collectors.toList()); - - final List allKeys = Stream.concat(existingKeys.stream(), notExistingKeys.stream()) - .collect(Collectors.toList()); - - return ImmutablePair.of(allKeys, newKeys); - } - - /** - * Checks if there are any new enum values which are not existing in the {@code oldEnumValues}. - * If there are, then "add" enum values update actions are built. - * Otherwise, if there are no new enum values, then an empty list is returned. - * - * @param definitionName the definition name whose enum values belong to. - * @param oldEnumValues the list of olf enum values. - * @param newEnumValues the list of new enum values. - * @param addEnumCallback the function that is called in order to add the new enum instance. - * @param the enum type of the element to add. - * @param the type of the resource in which the update actions will be applied on. - * @return a list of enum values update actions if there are new enum value that should be added. - * Otherwise, if the enum values are identical, an empty optional is returned. - */ - @Nonnull - static List> buildAddEnumValuesUpdateActions( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nonnull final BiFunction> addEnumCallback) { - - final Map oldEnumValuesKeyMap = getEnumValuesKeyMapWithKeyValidation( - definitionName, - oldEnumValues - ); - - final Map newEnumValuesKeyMap = getEnumValuesKeyMapWithKeyValidation( - definitionName, - newEnumValues - ); - - return newEnumValuesKeyMap.values() - .stream() - .filter(newEnumValue -> !oldEnumValuesKeyMap.containsKey(newEnumValue.getKey())) - .map(newEnumValue -> addEnumCallback.apply(definitionName, newEnumValue)) - .collect(Collectors.toList()); - } - - /** - * Checks if there are any old enum values which are not existing in the {@code newEnumValues}. - * If there are, then "remove" enum values update actions are built. - * Otherwise, if there are no old enum values, then an empty list is returned. - * - * @param definitionName the definition name whose enum values belong to. - * @param oldEnumValues the list of old enum values. - * @param newEnumValues the list of new enum values. - * @param removeEnumCallback the function that is called in order to remove the new enum instance. - * @param the enum type of the elements of the list. - * @param the type of the resource in which the update actions will be applied on. - * @return a list of enum values update actions if there are any old enum value that should be removed. - * Otherwise, if the enum values are identical, an empty optional is returned. - */ - @Nonnull - static Optional> buildRemoveEnumValuesUpdateAction( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nullable final List newEnumValues, - @Nonnull final BiFunction, UpdateAction> removeEnumCallback) { - - final Map newEnumValuesKeyMap = getEnumValuesKeyMapWithKeyValidation( - definitionName, - ofNullable(newEnumValues).orElse(emptyList()) - ); - - final List keysToRemove = oldEnumValues - .stream() - .map(WithKey::getKey) - .filter(oldEnumValueKey -> !newEnumValuesKeyMap.containsKey(oldEnumValueKey)) - .collect(Collectors.toList()); - - return keysToRemove.isEmpty() ? empty() : - of(removeEnumCallback.apply(definitionName, keysToRemove)); - } - - /** - * Checks if there are any enum values which are existing in the {@code newEnumValues}. - * If there are, then compare the enum value fields, and add the computed actions to the list of - * update actions. - * - * @param definitionName the definition name whose enum values belong to. - * @param oldEnumValues the list of old enum values. - * @param newEnumValues the list of new enum values. - * @param matchingEnumCallback the function that is called to get the update action resulting from comparing - * the enum value fields one by one. - * @param the enum type of the elements of the list. - * @param the type of the resource in which the update actions will be applied on. - * @return a list of enum update actions if there are enum values that are existing in the map of new enum values. - * If the enum value still exists, then compare the enum value fields (label), and add the computed - * actions to the list of update actions. Otherwise, if the enum values are identical, an empty optional - * is returned. - */ - @Nonnull - static List> buildMatchingEnumValuesUpdateActions( - @Nonnull final String definitionName, - @Nonnull final List oldEnumValues, - @Nonnull final List newEnumValues, - @Nonnull final TriFunction>> matchingEnumCallback) { - - final Map newEnumValuesKeyMap = getEnumValuesKeyMapWithKeyValidation( - definitionName, - newEnumValues - ); - - return oldEnumValues - .stream() - .filter(oldEnumValue -> newEnumValuesKeyMap.containsKey(oldEnumValue.getKey())) - .map(oldEnumValue -> matchingEnumCallback.apply( - definitionName, - oldEnumValue, - newEnumValuesKeyMap.get(oldEnumValue.getKey()) - )) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } + final List notExistingKeys = + newKeys.stream() + .filter(newKey -> !existingKeys.contains(newKey)) + .collect(Collectors.toList()); - /** - * Given a list of new {@link EnumValue}s, gets a map where the keys are the enum value key, and the values - * are the enum instances. - * - * @param definitionName the definition name whose the enum values belong to. - * @param enumValues the list of enum values. - * @param the enum type of the elements of the list. - * @return a map with the enum value key as a key of the map, and the enum - * value as a value of the map. - */ - @Nonnull - private static Map getEnumValuesKeyMapWithKeyValidation( - @Nonnull final String definitionName, - @Nonnull final List enumValues) { - - return enumValues.stream().collect( - toMap(WithKey::getKey, enumValue -> enumValue, - (enumValueA, enumValueB) -> { - throw new DuplicateKeyException(format("Enum Values have duplicated keys. " - + "Definition name: '%s', Duplicated enum value: '%s'. " - + "Enum Values are expected to be unique inside their definition.", - definitionName, enumValueA.getKey())); - } - )); - } + final List allKeys = + Stream.concat(existingKeys.stream(), notExistingKeys.stream()).collect(Collectors.toList()); + + return ImmutablePair.of(allKeys, newKeys); + } + + /** + * Checks if there are any new enum values which are not existing in the {@code oldEnumValues}. If + * there are, then "add" enum values update actions are built. Otherwise, if there are no new enum + * values, then an empty list is returned. + * + * @param definitionName the definition name whose enum values belong to. + * @param oldEnumValues the list of olf enum values. + * @param newEnumValues the list of new enum values. + * @param addEnumCallback the function that is called in order to add the new enum instance. + * @param the enum type of the element to add. + * @param the type of the resource in which the update actions will be applied on. + * @return a list of enum values update actions if there are new enum value that should be added. + * Otherwise, if the enum values are identical, an empty optional is returned. + */ + @Nonnull + static List> buildAddEnumValuesUpdateActions( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nonnull final BiFunction> addEnumCallback) { + + final Map oldEnumValuesKeyMap = + getEnumValuesKeyMapWithKeyValidation(definitionName, oldEnumValues); + + final Map newEnumValuesKeyMap = + getEnumValuesKeyMapWithKeyValidation(definitionName, newEnumValues); + + return newEnumValuesKeyMap.values().stream() + .filter(newEnumValue -> !oldEnumValuesKeyMap.containsKey(newEnumValue.getKey())) + .map(newEnumValue -> addEnumCallback.apply(definitionName, newEnumValue)) + .collect(Collectors.toList()); + } + + /** + * Checks if there are any old enum values which are not existing in the {@code newEnumValues}. If + * there are, then "remove" enum values update actions are built. Otherwise, if there are no old + * enum values, then an empty list is returned. + * + * @param definitionName the definition name whose enum values belong to. + * @param oldEnumValues the list of old enum values. + * @param newEnumValues the list of new enum values. + * @param removeEnumCallback the function that is called in order to remove the new enum instance. + * @param the enum type of the elements of the list. + * @param the type of the resource in which the update actions will be applied on. + * @return a list of enum values update actions if there are any old enum value that should be + * removed. Otherwise, if the enum values are identical, an empty optional is returned. + */ + @Nonnull + static Optional> buildRemoveEnumValuesUpdateAction( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nullable final List newEnumValues, + @Nonnull final BiFunction, UpdateAction> removeEnumCallback) { + + final Map newEnumValuesKeyMap = + getEnumValuesKeyMapWithKeyValidation( + definitionName, ofNullable(newEnumValues).orElse(emptyList())); + + final List keysToRemove = + oldEnumValues.stream() + .map(WithKey::getKey) + .filter(oldEnumValueKey -> !newEnumValuesKeyMap.containsKey(oldEnumValueKey)) + .collect(Collectors.toList()); - private EnumValuesUpdateActionUtils() { - } + return keysToRemove.isEmpty() + ? empty() + : of(removeEnumCallback.apply(definitionName, keysToRemove)); + } + + /** + * Checks if there are any enum values which are existing in the {@code newEnumValues}. If there + * are, then compare the enum value fields, and add the computed actions to the list of update + * actions. + * + * @param definitionName the definition name whose enum values belong to. + * @param oldEnumValues the list of old enum values. + * @param newEnumValues the list of new enum values. + * @param matchingEnumCallback the function that is called to get the update action resulting from + * comparing the enum value fields one by one. + * @param the enum type of the elements of the list. + * @param the type of the resource in which the update actions will be applied on. + * @return a list of enum update actions if there are enum values that are existing in the map of + * new enum values. If the enum value still exists, then compare the enum value fields + * (label), and add the computed actions to the list of update actions. Otherwise, if the enum + * values are identical, an empty optional is returned. + */ + @Nonnull + static List> buildMatchingEnumValuesUpdateActions( + @Nonnull final String definitionName, + @Nonnull final List oldEnumValues, + @Nonnull final List newEnumValues, + @Nonnull final TriFunction>> matchingEnumCallback) { + + final Map newEnumValuesKeyMap = + getEnumValuesKeyMapWithKeyValidation(definitionName, newEnumValues); + + return oldEnumValues.stream() + .filter(oldEnumValue -> newEnumValuesKeyMap.containsKey(oldEnumValue.getKey())) + .map( + oldEnumValue -> + matchingEnumCallback.apply( + definitionName, oldEnumValue, newEnumValuesKeyMap.get(oldEnumValue.getKey()))) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + /** + * Given a list of new {@link EnumValue}s, gets a map where the keys are the enum value key, and + * the values are the enum instances. + * + * @param definitionName the definition name whose the enum values belong to. + * @param enumValues the list of enum values. + * @param the enum type of the elements of the list. + * @return a map with the enum value key as a key of the map, and the enum value as a value of the + * map. + */ + @Nonnull + private static Map getEnumValuesKeyMapWithKeyValidation( + @Nonnull final String definitionName, @Nonnull final List enumValues) { + + return enumValues.stream() + .collect( + toMap( + WithKey::getKey, + enumValue -> enumValue, + (enumValueA, enumValueB) -> { + throw new DuplicateKeyException( + format( + "Enum Values have duplicated keys. " + + "Definition name: '%s', Duplicated enum value: '%s'. " + + "Enum Values are expected to be unique inside their definition.", + definitionName, enumValueA.getKey())); + })); + } + + private EnumValuesUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/FilterUtils.java b/src/main/java/com/commercetools/sync/commons/utils/FilterUtils.java index 276d98cdd2..8974c019d0 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/FilterUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/FilterUtils.java @@ -2,41 +2,44 @@ import com.commercetools.sync.products.ActionGroup; import com.commercetools.sync.products.SyncFilter; - -import javax.annotation.Nonnull; import java.util.function.Supplier; +import javax.annotation.Nonnull; public final class FilterUtils { - /** - * Given a {@link SyncFilter}, a {@link ActionGroup} and 2 {@link Supplier}. This method checks if the action group - * passes the criteria of the syncFiler, i.e. either of the following cases: - *

    - *
  • The {@link SyncFilter} is null.
  • - *
  • Passes the {@link SyncFilter}.
  • - *
- * - *

If the action group passes the filter, then the first supplier is executed, if not then the second supplier is - * executed. - * - * @param syncFilter defines the blacklisting/whitelisted action groups. - * @param actionGroup the action group under check. - * @param resultIfFilteredIn the supplier to be executed if the action group passes the filter. - * @param resultIfFilteredOut the supplier to be executed if the action group doesn't pass the filter. - * @param the type of the result of the suppliers and, in turn, the return of this method. - * @return the result of the executed supplier. - */ - @Nonnull - public static T executeSupplierIfPassesFilter( - @Nonnull final SyncFilter syncFilter, @Nonnull final ActionGroup actionGroup, - @Nonnull final Supplier resultIfFilteredIn, @Nonnull final Supplier resultIfFilteredOut) { - // If syncFilter is null or the action group passes the filter, execute resultIfFilteredIn supplier. - final Supplier resultantSupplier = syncFilter.filterActionGroup(actionGroup) - ? resultIfFilteredIn : resultIfFilteredOut; + /** + * Given a {@link SyncFilter}, a {@link ActionGroup} and 2 {@link Supplier}. This method checks if + * the action group passes the criteria of the syncFiler, i.e. either of the following cases: + * + *

    + *
  • The {@link SyncFilter} is null. + *
  • Passes the {@link SyncFilter}. + *
+ * + *

If the action group passes the filter, then the first supplier is executed, if not then the + * second supplier is executed. + * + * @param syncFilter defines the blacklisting/whitelisted action groups. + * @param actionGroup the action group under check. + * @param resultIfFilteredIn the supplier to be executed if the action group passes the filter. + * @param resultIfFilteredOut the supplier to be executed if the action group doesn't pass the + * filter. + * @param the type of the result of the suppliers and, in turn, the return of this method. + * @return the result of the executed supplier. + */ + @Nonnull + public static T executeSupplierIfPassesFilter( + @Nonnull final SyncFilter syncFilter, + @Nonnull final ActionGroup actionGroup, + @Nonnull final Supplier resultIfFilteredIn, + @Nonnull final Supplier resultIfFilteredOut) { + // If syncFilter is null or the action group passes the filter, execute resultIfFilteredIn + // supplier. + final Supplier resultantSupplier = + syncFilter.filterActionGroup(actionGroup) ? resultIfFilteredIn : resultIfFilteredOut; - return resultantSupplier.get(); - } + return resultantSupplier.get(); + } - private FilterUtils() { - } + private FilterUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/GenericUpdateActionUtils.java b/src/main/java/com/commercetools/sync/commons/utils/GenericUpdateActionUtils.java index 95e7d7d52c..edc93fb2fa 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/GenericUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/GenericUpdateActionUtils.java @@ -1,5 +1,7 @@ package com.commercetools.sync.commons.utils; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isBlank; import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.helpers.GenericCustomActionBuilder; @@ -7,64 +9,64 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.Resource; import io.sphere.sdk.types.Custom; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Map; import java.util.Optional; import java.util.function.Function; - -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; final class GenericUpdateActionUtils { - /** - * Creates a CTP "setCustomType" update action on the given resource {@code T} according to the type of the - * {@link GenericCustomActionBuilder} passed. If the {@code customTypeId} passed is blank (null/empty), the error - * callback is triggered with an error message that the setCustomType update action cannot be built with a blank id, - * and an empty optional is returned. - * - * @param the type of the resource which has the custom fields. - * @param the type of the resource to do the update action on. - * @param customTypeId the id of the new custom type. - * @param customFieldsJsonMap the custom fields map of JSON values. - * @param resource the resource which has the custom fields. - * @param customActionBuilder the builder instance responsible for building the custom update actions. - * @param variantId optional field representing the variant id in case the oldResource is an asset. - * @param resourceIdGetter a function used to get the id of the resource being updated. - * @param resourceTypeIdGetter a function used to get the type id of the resource being updated. - * @param updateIdGetter a function used to get the id/key needed for updating the resource that has the - * custom fields. - * @param syncOptions responsible for supplying the sync options to the sync utility method. - * @return a setCustomType update action of the type of the resource it's requested on, or an empty optional - * if the {@code customTypeId} is blank. - */ - @Nonnull - static > Optional> buildTypedSetCustomTypeUpdateAction( - @Nullable final String customTypeId, - @Nullable final Map customFieldsJsonMap, - @Nonnull final T resource, - @Nonnull final GenericCustomActionBuilder customActionBuilder, - @Nullable final Integer variantId, - @Nonnull final Function resourceIdGetter, - @Nonnull final Function resourceTypeIdGetter, - @Nonnull final Function updateIdGetter, - @Nonnull final BaseSyncOptions syncOptions) { + /** + * Creates a CTP "setCustomType" update action on the given resource {@code T} according to the + * type of the {@link GenericCustomActionBuilder} passed. If the {@code customTypeId} passed is + * blank (null/empty), the error callback is triggered with an error message that the + * setCustomType update action cannot be built with a blank id, and an empty optional is returned. + * + * @param the type of the resource which has the custom fields. + * @param the type of the resource to do the update action on. + * @param customTypeId the id of the new custom type. + * @param customFieldsJsonMap the custom fields map of JSON values. + * @param resource the resource which has the custom fields. + * @param customActionBuilder the builder instance responsible for building the custom update + * actions. + * @param variantId optional field representing the variant id in case the oldResource is an + * asset. + * @param resourceIdGetter a function used to get the id of the resource being updated. + * @param resourceTypeIdGetter a function used to get the type id of the resource being updated. + * @param updateIdGetter a function used to get the id/key needed for updating the resource that + * has the custom fields. + * @param syncOptions responsible for supplying the sync options to the sync utility method. + * @return a setCustomType update action of the type of the resource it's requested on, or an + * empty optional if the {@code customTypeId} is blank. + */ + @Nonnull + static > + Optional> buildTypedSetCustomTypeUpdateAction( + @Nullable final String customTypeId, + @Nullable final Map customFieldsJsonMap, + @Nonnull final T resource, + @Nonnull final GenericCustomActionBuilder customActionBuilder, + @Nullable final Integer variantId, + @Nonnull final Function resourceIdGetter, + @Nonnull final Function resourceTypeIdGetter, + @Nonnull final Function updateIdGetter, + @Nonnull final BaseSyncOptions syncOptions) { - if (!isBlank(customTypeId)) { - return Optional.of( - customActionBuilder.buildSetCustomTypeAction(variantId, updateIdGetter.apply(resource), - customTypeId, customFieldsJsonMap)); - } else { - final String errorMessage = format("Failed to build 'setCustomType' update action on the " - + "%s with id '%s'. Reason: New Custom Type id is blank (null/empty).", - resourceTypeIdGetter.apply(resource), resourceIdGetter.apply(resource)); - syncOptions.applyErrorCallback(errorMessage); - return Optional.empty(); - } + if (!isBlank(customTypeId)) { + return Optional.of( + customActionBuilder.buildSetCustomTypeAction( + variantId, updateIdGetter.apply(resource), customTypeId, customFieldsJsonMap)); + } else { + final String errorMessage = + format( + "Failed to build 'setCustomType' update action on the " + + "%s with id '%s'. Reason: New Custom Type id is blank (null/empty).", + resourceTypeIdGetter.apply(resource), resourceIdGetter.apply(resource)); + syncOptions.applyErrorCallback(errorMessage); + return Optional.empty(); } + } - private GenericUpdateActionUtils() { - } + private GenericUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/GraphQlQueryAll.java b/src/main/java/com/commercetools/sync/commons/utils/GraphQlQueryAll.java index 354bc7e936..7a015cc4ff 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/GraphQlQueryAll.java +++ b/src/main/java/com/commercetools/sync/commons/utils/GraphQlQueryAll.java @@ -1,130 +1,133 @@ package com.commercetools.sync.commons.utils; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.models.GraphQlBaseRequest; import com.commercetools.sync.commons.models.GraphQlBaseResource; import com.commercetools.sync.commons.models.GraphQlBaseResult; import io.sphere.sdk.client.SphereClient; - -import javax.annotation.Nonnull; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; - -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; final class GraphQlQueryAll, U extends GraphQlBaseResource> { - private final SphereClient client; - private final GraphQlBaseRequest graphqlRequest; - private final long pageSize; - - private Consumer> pageConsumer; - - private GraphQlQueryAll(@Nonnull final SphereClient client, - @Nonnull final GraphQlBaseRequest graphqlRequest, - final long pageSize) { - - this.client = client; - this.graphqlRequest = graphqlRequest.withLimit(pageSize); - this.pageSize = pageSize; - } - - @Nonnull - static , U extends GraphQlBaseResource> GraphQlQueryAll of( - @Nonnull final SphereClient client, - @Nonnull final GraphQlBaseRequest graphqlRequest, - final int pageSize) { - - return new GraphQlQueryAll<>(client, graphqlRequest, pageSize); + private final SphereClient client; + private final GraphQlBaseRequest graphqlRequest; + private final long pageSize; + + private Consumer> pageConsumer; + + private GraphQlQueryAll( + @Nonnull final SphereClient client, + @Nonnull final GraphQlBaseRequest graphqlRequest, + final long pageSize) { + + this.client = client; + this.graphqlRequest = graphqlRequest.withLimit(pageSize); + this.pageSize = pageSize; + } + + @Nonnull + static , U extends GraphQlBaseResource> GraphQlQueryAll of( + @Nonnull final SphereClient client, + @Nonnull final GraphQlBaseRequest graphqlRequest, + final int pageSize) { + + return new GraphQlQueryAll<>(client, graphqlRequest, pageSize); + } + + /** + * Given a {@link Consumer} to a page of results of type {@link T}, this method sets this + * instance's {@code pageConsumer} to the supplied value, then it makes requests to fetch the + * entire result space of a graphql query request {@link GraphQlBaseRequest} to CTP, while + * accepting the consumer on each fetched page. + * + * @param pageConsumer the consumer to accept on each fetched page of the result space. + * @return a future containing void after the consumer accepted all the pages. + */ + @Nonnull + CompletionStage run(@Nonnull final Consumer> pageConsumer) { + + this.pageConsumer = pageConsumer; + final CompletionStage firstPage = client.execute(graphqlRequest); + return queryNextPages(firstPage); + } + + /** + * Given a completion stage {@code currentPageStage} containing a current graphql result {@link + * GraphQlBaseRequest}, this method composes the completion stage by first checking if the result + * is null or not. If it is not, then it recursively (by calling itself with the next page's + * completion stage result) composes to the supplied stage, stages of the all next pages' + * processing. If there is no next page, then the result of the {@code currentPageStage} would be + * null and this method would just return a completed future containing null result, which in turn + * signals the last page of processing. + * + * @param currentPageStage a future containing a graphql result {@link GraphQlBaseRequest}. + */ + @Nonnull + private CompletionStage queryNextPages(@Nonnull final CompletionStage currentPageStage) { + return currentPageStage.thenCompose( + currentPage -> + currentPage != null + ? queryNextPages(processPageAndGetNext(currentPage)) + : completedFuture(null)); + } + + /** + * Given a graphql query result representing a page {@link GraphQlBaseRequest}, this method checks + * if there are elements in the result (size > 0), then it consumes the resultant list using this + * instance's {@code pageConsumer}. Then it attempts to fetch the next page if it exists and + * returns a completion stage containing the result of the next page. If there is a next page, + * then a new future of the next page is returned. If there are no more results, the method + * returns a completed future containing null. + * + * @param page the current page result. + * @return If there is a next page, then a new future of the next page is returned. If there are + * no more results, the method returns a completed future containing null. + */ + @Nonnull + private CompletionStage processPageAndGetNext(@Nonnull final T page) { + final Set currentPageElements = page.getResults(); + if (!currentPageElements.isEmpty()) { + consumePageElements(currentPageElements); + return getNextPageStage(currentPageElements); } - - /** - * Given a {@link Consumer} to a page of results of type {@link T}, this method sets this instance's - * {@code pageConsumer} to the supplied value, then it makes requests to fetch the entire result space of a - * graphql query request {@link GraphQlBaseRequest} to CTP, while accepting the consumer on each fetched - * page. - * - * @param pageConsumer the consumer to accept on each fetched page of the result space. - * @return a future containing void after the consumer accepted all the pages. - */ - @Nonnull - CompletionStage run(@Nonnull final Consumer> pageConsumer) { - - this.pageConsumer = pageConsumer; - final CompletionStage firstPage = client.execute(graphqlRequest); - return queryNextPages(firstPage); - } - - /** - * Given a completion stage {@code currentPageStage} containing a current graphql result - * {@link GraphQlBaseRequest}, this method composes the completion stage by first checking if the result - * is null or not. If it is not, then it recursively (by calling itself with the next page's completion stage - * result) composes to the supplied stage, stages of the all next pages' processing. If there is no next page, - * then the result of the {@code currentPageStage} would be null and this method would just return a completed - * future containing null result, which in turn signals the last page of processing. - * - * @param currentPageStage a future containing a graphql result {@link GraphQlBaseRequest}. - */ - @Nonnull - private CompletionStage queryNextPages( - @Nonnull final CompletionStage currentPageStage) { - return currentPageStage.thenCompose(currentPage -> - currentPage != null ? queryNextPages(processPageAndGetNext(currentPage)) : completedFuture(null)); - } - - /** - * Given a graphql query result representing a page {@link GraphQlBaseRequest}, this method checks if there - * are elements in the result (size > 0), then it consumes the resultant list using this instance's {@code - * pageConsumer}. Then it attempts to fetch the next page if it exists and returns a completion stage - * containing the result of the next page. If there is a next page, then a new future of the next page is returned. - * If there are no more results, the method returns a completed future containing null. - * - * @param page the current page result. - * @return If there is a next page, then a new future of the next page is returned. If there are no more results, - * the method returns a completed future containing null. - */ - @Nonnull - private CompletionStage processPageAndGetNext( - @Nonnull final T page) { - final Set currentPageElements = page.getResults(); - if (!currentPageElements.isEmpty()) { - consumePageElements(currentPageElements); - return getNextPageStage(currentPageElements); - } - return completedFuture(null); - } - - private void consumePageElements(@Nonnull final Set pageElements) { - pageConsumer.accept(pageElements); - } - - /** - * Given a list of page elements of type {@link U}, this method checks if this page is the last - * page or not by checking if the result size is equal to this instance's {@code pageSize}). If it is, then it means - * there might be still more results. However, if not, then it means for sure there are no more results and this - * is the last page. If there is a next page, the id of the last element in the list is used and a future is - * created containing the fetched results which have an id greater than the id of the last element in the list - * and this future is returned. If there are no more results, the method returns a completed future containing null. - * - * @param pageElements set of page elements of type {@link U}. - * @return a future containing the fetched results which have an id greater than the id of the last element - * in the set. - */ - @Nonnull - private CompletionStage getNextPageStage( - @Nonnull final Set pageElements) { - if (pageElements.size() == pageSize) { - String lastElementId = EMPTY; - for (U pageElement : pageElements) { - lastElementId = pageElement.getId(); - } - final String queryPredicate = isBlank(lastElementId) ? null : format("id > \\\\\\\"%s\\\\\\\"", - lastElementId); - - return client.execute(graphqlRequest.withPredicate(queryPredicate)); - } - return completedFuture(null); + return completedFuture(null); + } + + private void consumePageElements(@Nonnull final Set pageElements) { + pageConsumer.accept(pageElements); + } + + /** + * Given a list of page elements of type {@link U}, this method checks if this page is the last + * page or not by checking if the result size is equal to this instance's {@code pageSize}). If it + * is, then it means there might be still more results. However, if not, then it means for sure + * there are no more results and this is the last page. If there is a next page, the id of the + * last element in the list is used and a future is created containing the fetched results which + * have an id greater than the id of the last element in the list and this future is returned. If + * there are no more results, the method returns a completed future containing null. + * + * @param pageElements set of page elements of type {@link U}. + * @return a future containing the fetched results which have an id greater than the id of the + * last element in the set. + */ + @Nonnull + private CompletionStage getNextPageStage(@Nonnull final Set pageElements) { + if (pageElements.size() == pageSize) { + String lastElementId = EMPTY; + for (U pageElement : pageElements) { + lastElementId = pageElement.getId(); + } + final String queryPredicate = + isBlank(lastElementId) ? null : format("id > \\\\\\\"%s\\\\\\\"", lastElementId); + + return client.execute(graphqlRequest.withPredicate(queryPredicate)); } + return completedFuture(null); + } } diff --git a/src/main/java/com/commercetools/sync/commons/utils/OptionalUtils.java b/src/main/java/com/commercetools/sync/commons/utils/OptionalUtils.java index b9040dc7a6..3416cf012d 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/OptionalUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/OptionalUtils.java @@ -1,67 +1,75 @@ package com.commercetools.sync.commons.utils; -import javax.annotation.Nonnull; +import static java.util.Arrays.asList; + import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; - -import static java.util.Arrays.asList; +import javax.annotation.Nonnull; public final class OptionalUtils { - /** - * Takes a {@link Collection} of {@link Optional}s containing some elements of type {@code T}, this method filters - * out any empty {@link Optional} and returns a new list containing the values of the non-empty optionals. - * - * @param optionals the collection of optionals that should be filtered out of empty optionals. - * @param The type of the elements in the Optionals in the supplied collection {@code optionals}. - * @return a new list containing the values of the non-empty optionals from the supplied {@code optionals} - * collection. - */ - @Nonnull - public static List filterEmptyOptionals(@Nonnull final Collection> optionals) { - return filterEmptyOptionals(optionals, ArrayList::new); - } + /** + * Takes a {@link Collection} of {@link Optional}s containing some elements of type {@code T}, + * this method filters out any empty {@link Optional} and returns a new list containing the values + * of the non-empty optionals. + * + * @param optionals the collection of optionals that should be filtered out of empty optionals. + * @param The type of the elements in the Optionals in the supplied collection {@code + * optionals}. + * @return a new list containing the values of the non-empty optionals from the supplied {@code + * optionals} collection. + */ + @Nonnull + public static List filterEmptyOptionals(@Nonnull final Collection> optionals) { + return filterEmptyOptionals(optionals, ArrayList::new); + } - /** - * Takes zero or more {@link Optional}s containing some elements of type {@code T}, this method filters - * out any empty {@link Optional} and returns a new list containing the values of the non-empty optionals. - * - * @param optionals zero or more optionals that should be filtered out of empty optionals. - * @param The type of the elements in the Optionals in the supplied collection {@code optionals}. - * @return a new list containing the values of the non-empty optionals from the supplied {@code optionals}. - */ - @Nonnull - @SafeVarargs - public static List filterEmptyOptionals(@Nonnull final Optional... optionals) { - return filterEmptyOptionals(asList(optionals)); - } + /** + * Takes zero or more {@link Optional}s containing some elements of type {@code T}, this method + * filters out any empty {@link Optional} and returns a new list containing the values of the + * non-empty optionals. + * + * @param optionals zero or more optionals that should be filtered out of empty optionals. + * @param The type of the elements in the Optionals in the supplied collection {@code + * optionals}. + * @return a new list containing the values of the non-empty optionals from the supplied {@code + * optionals}. + */ + @Nonnull + @SafeVarargs + public static List filterEmptyOptionals(@Nonnull final Optional... optionals) { + return filterEmptyOptionals(asList(optionals)); + } - /** - * Takes a {@link Collection} of {@link Optional}s containing some elements of type {@code T}, this method filters - * out any empty {@link Optional} and returns a new collection, of a type based on the supplied - * {@code collectionFactory}, containing the values of the non-empty optionals. - * - * @param optionals the collection of optionals that should be filtered out of empty optionals. - * @param collectionFactory the factory which decided on the type of the returned collection. - * @param The type of the elements in the Optionals in the supplied collection {@code optionals}. - * @param The type of the collection that should be returned containing the values of the non-empty optionals. - * @return a new collection, of a type based on the supplied {@code collectionFactory}, - * containing the values of the non-empty optionals from the supplied {@code optionals} collection. - */ - @Nonnull - private static > C filterEmptyOptionals(@Nonnull final Collection> optionals, - @Nonnull final Supplier collectionFactory) { + /** + * Takes a {@link Collection} of {@link Optional}s containing some elements of type {@code T}, + * this method filters out any empty {@link Optional} and returns a new collection, of a type + * based on the supplied {@code collectionFactory}, containing the values of the non-empty + * optionals. + * + * @param optionals the collection of optionals that should be filtered out of empty optionals. + * @param collectionFactory the factory which decided on the type of the returned collection. + * @param The type of the elements in the Optionals in the supplied collection {@code + * optionals}. + * @param The type of the collection that should be returned containing the values of the + * non-empty optionals. + * @return a new collection, of a type based on the supplied {@code collectionFactory}, containing + * the values of the non-empty optionals from the supplied {@code optionals} collection. + */ + @Nonnull + private static > C filterEmptyOptionals( + @Nonnull final Collection> optionals, + @Nonnull final Supplier collectionFactory) { - return optionals.stream() - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toCollection(collectionFactory)); - } + return optionals.stream() + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toCollection(collectionFactory)); + } - private OptionalUtils() { - } + private OptionalUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/QuadConsumer.java b/src/main/java/com/commercetools/sync/commons/utils/QuadConsumer.java index fb2064d528..8065c2c7e4 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/QuadConsumer.java +++ b/src/main/java/com/commercetools/sync/commons/utils/QuadConsumer.java @@ -4,11 +4,11 @@ import javax.annotation.Nullable; /** - * Represents an operation that accepts four arguments and returns no result. This is the - * quad-arity specialization of {@link Consumer}. + * Represents an operation that accepts four arguments and returns no result. This is the quad-arity + * specialization of {@link Consumer}. * - *

This is a functional interface - * whose functional method is {@link #accept(Object, Object, Object, Object)}. + *

This is a functional interface whose functional method is + * {@link #accept(Object, Object, Object, Object)}. * * @param the type of the first argument to the function * @param the type of the second argument to the function @@ -19,15 +19,17 @@ @FunctionalInterface public interface QuadConsumer { - /** - * Performs operation on the given arguments. - * - * @param firstParam the first argument. - * @param secondParam the second argument. - * @param thirdParam the third argument. - * @param fourthParam the fourth argument. - */ - void accept(@Nullable final T firstParam, @Nullable final U secondParam, - @Nullable final S thirdParam, @Nullable final V fourthParam); - + /** + * Performs operation on the given arguments. + * + * @param firstParam the first argument. + * @param secondParam the second argument. + * @param thirdParam the third argument. + * @param fourthParam the fourth argument. + */ + void accept( + @Nullable final T firstParam, + @Nullable final U secondParam, + @Nullable final S thirdParam, + @Nullable final V fourthParam); } diff --git a/src/main/java/com/commercetools/sync/commons/utils/QueryAll.java b/src/main/java/com/commercetools/sync/commons/utils/QueryAll.java index f39491967c..c41a58003a 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/QueryAll.java +++ b/src/main/java/com/commercetools/sync/commons/utils/QueryAll.java @@ -1,161 +1,173 @@ package com.commercetools.sync.commons.utils; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; + import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.models.ResourceView; import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.queries.QueryDsl; import io.sphere.sdk.queries.QueryPredicate; import io.sphere.sdk.queries.QuerySort; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; import java.util.function.Function; - -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; +import javax.annotation.Nonnull; final class QueryAll, S> { - private final SphereClient client; - private final QueryDsl baseQuery; - private final long pageSize; - - private Function, S> pageMapper; - private final List mappedResultsTillNow; - - private Consumer> pageConsumer; - - private QueryAll(@Nonnull final SphereClient client, - @Nonnull final QueryDsl baseQuery, - final long pageSize) { - - this.client = client; - this.baseQuery = withDefaults(baseQuery, pageSize); - this.pageSize = pageSize; - this.mappedResultsTillNow = new ArrayList<>(); + private final SphereClient client; + private final QueryDsl baseQuery; + private final long pageSize; + + private Function, S> pageMapper; + private final List mappedResultsTillNow; + + private Consumer> pageConsumer; + + private QueryAll( + @Nonnull final SphereClient client, + @Nonnull final QueryDsl baseQuery, + final long pageSize) { + + this.client = client; + this.baseQuery = withDefaults(baseQuery, pageSize); + this.pageSize = pageSize; + this.mappedResultsTillNow = new ArrayList<>(); + } + + @Nonnull + private static > QueryDsl withDefaults( + @Nonnull final QueryDsl query, final long pageSize) { + + final C withLimit = query.withLimit(pageSize).withFetchTotal(false); + return !withLimit.sort().isEmpty() ? withLimit : withLimit.withSort(QuerySort.of("id asc")); + } + + @Nonnull + static , S> QueryAll of( + @Nonnull final SphereClient client, + @Nonnull final QueryDsl baseQuery, + final int pageSize) { + + return new QueryAll<>(client, baseQuery, pageSize); + } + + /** + * Given a {@link Function} to a page of resources of type {@code T} that returns a mapped result + * of type {@code S}, this method sets this instance's {@code pageMapper} to the supplied value, + * then it makes requests to fetch the entire result space of the resource {@code T} on CTP, while + * applying the function on each fetched page. + * + * @param pageMapper the function to apply on each fetched page of the result space. + * @return a future containing a list of mapped results of type {@code S}, after the function + * applied all the pages. + */ + @Nonnull + CompletionStage> run(@Nonnull final Function, S> pageMapper) { + this.pageMapper = pageMapper; + final CompletionStage> firstPage = client.execute(baseQuery); + return queryNextPages(firstPage).thenApply(voidResult -> this.mappedResultsTillNow); + } + + /** + * Given a {@link Consumer} to a page of resources of type {@code T}, this method sets this + * instance's {@code pageConsumer} to the supplied value, then it makes requests to fetch the + * entire result space of the resource {@code T} on CTP, while accepting the consumer on each + * fetched page. + * + * @param pageConsumer the consumer to accept on each fetched page of the result space. + * @return a future containing void after the consumer accepted all the pages. + */ + @Nonnull + CompletionStage run(@Nonnull final Consumer> pageConsumer) { + this.pageConsumer = pageConsumer; + final CompletionStage> firstPage = client.execute(baseQuery); + return queryNextPages(firstPage).thenAccept(voidResult -> {}); + } + + /** + * Given a completion stage {@code currentPageStage} containing a current page result {@link + * PagedQueryResult}, this method composes the completion stage by first checking if the result is + * null or not. If it is not, then it recursivley (by calling itself with the next page's + * completion stage result) composes to the supplied stage, stages of the all next pages' + * processing. If there is no next page, then the result of the {@code currentPageStage} would be + * null and this method would just return a completed future containing containing null result, + * which in turn signals the last page of processing. + * + * @param currentPageStage a future containing a result {@link PagedQueryResult}. + */ + @Nonnull + private CompletionStage queryNextPages( + @Nonnull final CompletionStage> currentPageStage) { + return currentPageStage.thenCompose( + currentPage -> + currentPage != null + ? queryNextPages(processPageAndGetNext(currentPage)) + : completedFuture(null)); + } + + /** + * Given a page result {@link PagedQueryResult}, this method checks if there are elements in the + * result (size > 0), then it maps or consumes the resultant list using this instance's {@code + * pageMapper} or {code pageConsumer} whichever is available. Then it attempts to fetch the next + * page if it exists and returns a completion stage containing the result of the next page. If + * there is a next page, then a new future of the next page is returned. If there are no more + * results, the method returns a completed future containing null. + * + * @param page the current page result. + * @return If there is a next page, then a new future of the next page is returned. If there are + * no more results, the method returns a completed future containing null. + */ + @Nonnull + private CompletionStage> processPageAndGetNext( + @Nonnull final PagedQueryResult page) { + final List currentPageElements = page.getResults(); + if (!currentPageElements.isEmpty()) { + mapOrConsume(currentPageElements); + return getNextPageStage(currentPageElements); } - - @Nonnull - private static > QueryDsl withDefaults( - @Nonnull final QueryDsl query, final long pageSize) { - - final C withLimit = query.withLimit(pageSize).withFetchTotal(false); - return !withLimit.sort().isEmpty() ? withLimit : withLimit.withSort(QuerySort.of("id asc")); + return completedFuture(null); + } + + /** + * Given a list of page elements of resource {@code T}, this method checks if this instance's + * {@code pageConsumer} or {@code pageMapper} is set (not null). The one which is set is then + * applied on the list of page elements. + * + * @param pageElements list of page elements of resource {@code T}. + */ + private void mapOrConsume(@Nonnull final List pageElements) { + if (pageConsumer != null) { + pageConsumer.accept(pageElements); + } else { + mappedResultsTillNow.add(pageMapper.apply(pageElements)); } - - @Nonnull - static , S> QueryAll of( - @Nonnull final SphereClient client, - @Nonnull final QueryDsl baseQuery, - final int pageSize) { - - return new QueryAll<>(client, baseQuery, pageSize); - } - - /** - * Given a {@link Function} to a page of resources of type {@code T} that returns a mapped result of type {@code S}, - * this method sets this instance's {@code pageMapper} to the supplied value, then it makes requests to fetch the - * entire result space of the resource {@code T} on CTP, while applying the function on each fetched page. - * - * @param pageMapper the function to apply on each fetched page of the result space. - * @return a future containing a list of mapped results of type {@code S}, after the function applied all the pages. - */ - @Nonnull - CompletionStage> run(@Nonnull final Function, S> pageMapper) { - this.pageMapper = pageMapper; - final CompletionStage> firstPage = client.execute(baseQuery); - return queryNextPages(firstPage) - .thenApply(voidResult -> this.mappedResultsTillNow); - } - - /** - * Given a {@link Consumer} to a page of resources of type {@code T}, this method sets this instance's - * {@code pageConsumer} to the supplied value, then it makes requests to fetch the entire result space of the - * resource {@code T} on CTP, while accepting the consumer on each fetched page. - * - * @param pageConsumer the consumer to accept on each fetched page of the result space. - * @return a future containing void after the consumer accepted all the pages. - */ - @Nonnull - CompletionStage run(@Nonnull final Consumer> pageConsumer) { - this.pageConsumer = pageConsumer; - final CompletionStage> firstPage = client.execute(baseQuery); - return queryNextPages(firstPage).thenAccept(voidResult -> { }); - } - - /** - * Given a completion stage {@code currentPageStage} containing a current page result {@link PagedQueryResult}, this - * method composes the completion stage by first checking if the result is null or not. If it is not, then it - * recursivley (by calling itself with the next page's completion stage result) composes to the supplied stage, - * stages of the all next pages' processing. If there is no next page, then the result of the - * {@code currentPageStage} would be null and this method would just return a completed future containing - * containing null result, which in turn signals the last page of processing. - * - * @param currentPageStage a future containing a result {@link PagedQueryResult}. - */ - @Nonnull - private CompletionStage queryNextPages(@Nonnull final CompletionStage> currentPageStage) { - return currentPageStage.thenCompose(currentPage -> - currentPage != null ? queryNextPages(processPageAndGetNext(currentPage)) : completedFuture(null)); - } - - /** - * Given a page result {@link PagedQueryResult}, this method checks if there are elements in the result (size > 0), - * then it maps or consumes the resultant list using this instance's {@code pageMapper} or {code pageConsumer} - * whichever is available. Then it attempts to fetch the next page if it exists and returns a completion stage - * containing the result of the next page. If there is a next page, then a new future of the next page is returned. - * If there are no more results, the method returns a completed future containing null. - * - * @param page the current page result. - * @return If there is a next page, then a new future of the next page is returned. If there are no more results, - * the method returns a completed future containing null. - */ - @Nonnull - private CompletionStage> processPageAndGetNext(@Nonnull final PagedQueryResult page) { - final List currentPageElements = page.getResults(); - if (!currentPageElements.isEmpty()) { - mapOrConsume(currentPageElements); - return getNextPageStage(currentPageElements); - } - return completedFuture(null); - } - - /** - * Given a list of page elements of resource {@code T}, this method checks if this instance's {@code pageConsumer} - * or {@code pageMapper} is set (not null). The one which is set is then applied on the list of page elements. - * - * - * @param pageElements list of page elements of resource {@code T}. - */ - private void mapOrConsume(@Nonnull final List pageElements) { - if (pageConsumer != null) { - pageConsumer.accept(pageElements); - } else { - mappedResultsTillNow.add(pageMapper.apply(pageElements)); - } - } - - /** - * Given a list of page elements of resource {@code T}, this method checks if this page is the last page or not by - * checking if the result size is equal to this instance's {@code pageSize}). If It is, then it means there might be - * still more results. However, if not, then it means for sure there are no more results and this is the last page. - * If there is a next page, the id of the last element in the list is fetched and a future is created containing the - * fetched results which have an id greater than the id of the last element in the list and this future is returned. - * If there are no more results, the method returns a completed future containing null. - * - * @param pageElements list of page elements of resource {@code T}. - * @return a future containing the fetched results which have an id greater than the id of the last element - * in the list. - */ - @Nonnull - private CompletionStage> getNextPageStage(@Nonnull final List pageElements) { - if (pageElements.size() == pageSize) { - final String lastElementId = pageElements.get(pageElements.size() - 1).getId(); - final QueryPredicate queryPredicate = QueryPredicate.of(format("id > \"%s\"", lastElementId)); - return client.execute(baseQuery.plusPredicates(queryPredicate)); - } - return completedFuture(null); + } + + /** + * Given a list of page elements of resource {@code T}, this method checks if this page is the + * last page or not by checking if the result size is equal to this instance's {@code pageSize}). + * If It is, then it means there might be still more results. However, if not, then it means for + * sure there are no more results and this is the last page. If there is a next page, the id of + * the last element in the list is fetched and a future is created containing the fetched results + * which have an id greater than the id of the last element in the list and this future is + * returned. If there are no more results, the method returns a completed future containing null. + * + * @param pageElements list of page elements of resource {@code T}. + * @return a future containing the fetched results which have an id greater than the id of the + * last element in the list. + */ + @Nonnull + private CompletionStage> getNextPageStage( + @Nonnull final List pageElements) { + if (pageElements.size() == pageSize) { + final String lastElementId = pageElements.get(pageElements.size() - 1).getId(); + final QueryPredicate queryPredicate = + QueryPredicate.of(format("id > \"%s\"", lastElementId)); + return client.execute(baseQuery.plusPredicates(queryPredicate)); } + return completedFuture(null); + } } diff --git a/src/main/java/com/commercetools/sync/commons/utils/ResourceIdentifierUtils.java b/src/main/java/com/commercetools/sync/commons/utils/ResourceIdentifierUtils.java index ef120b1d95..f5e5c69db7 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/ResourceIdentifierUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/ResourceIdentifierUtils.java @@ -1,60 +1,60 @@ package com.commercetools.sync.commons.utils; +import static java.util.Optional.ofNullable; + import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.models.Referenceable; import io.sphere.sdk.models.ResourceIdentifier; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Objects; import java.util.Optional; - -import static java.util.Optional.ofNullable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class ResourceIdentifierUtils { - public static final String REFERENCE_TYPE_ID_FIELD = "typeId"; - public static final String REFERENCE_ID_FIELD = "id"; - - /** - * Given a {@link Referenceable} {@code resource} of the type {@code T}, if it is not null, this method applies the - * {@link Referenceable#toResourceIdentifier()} method to return it as a {@link ResourceIdentifier} of - * the type {@code T}. If it is {@code null}, this method returns {@code null}. - * - * @param resource represents the resource to return as a {@link ResourceIdentifier} if not {@code null}. - * @param type of the resource supplied. - * @param represents the type of the {@link ResourceIdentifier} returned. - * @return the supplied resource in the as a {@link ResourceIdentifier} if not {@code null}. If it is {@code null}, - * this method returns {@code null}. - */ - @Nullable - public static , S> ResourceIdentifier toResourceIdentifierIfNotNull( - @Nullable final T resource) { - return ofNullable(resource) - .map(Referenceable::toResourceIdentifier) - .orElse(null); - } - - /** - * Given a {@link JsonNode} {@code referenceValue} which is the JSON representation of CTP Reference object, - * this method checks if it is has a {@code typeId} with the value equal to {@code referenceTypeId}. - * - * @param referenceValue JSON representation of CTP reference object - * @param referenceTypeId the typeId to check of the reference is of the same type or not. - * @return true if the typeId field of the reference has the same value as {@code referenceTypeId}, otherwise, - * false. - */ - public static boolean isReferenceOfType(@Nonnull final JsonNode referenceValue, final String referenceTypeId) { - return getReferenceTypeId(referenceValue) - .map(resolvedReferenceTypeId -> Objects.equals(resolvedReferenceTypeId, referenceTypeId)) - .orElse(false); - } - - @Nonnull - private static Optional getReferenceTypeId(@Nonnull final JsonNode referenceValue) { - final JsonNode typeId = referenceValue.get(REFERENCE_TYPE_ID_FIELD); - return Optional.ofNullable(typeId).map(JsonNode::asText); - } - - private ResourceIdentifierUtils() { - } + public static final String REFERENCE_TYPE_ID_FIELD = "typeId"; + public static final String REFERENCE_ID_FIELD = "id"; + + /** + * Given a {@link Referenceable} {@code resource} of the type {@code T}, if it is not null, this + * method applies the {@link Referenceable#toResourceIdentifier()} method to return it as a {@link + * ResourceIdentifier} of the type {@code T}. If it is {@code null}, this method returns {@code + * null}. + * + * @param resource represents the resource to return as a {@link ResourceIdentifier} if not {@code + * null}. + * @param type of the resource supplied. + * @param represents the type of the {@link ResourceIdentifier} returned. + * @return the supplied resource in the as a {@link ResourceIdentifier} if not {@code null}. If it + * is {@code null}, this method returns {@code null}. + */ + @Nullable + public static , S> ResourceIdentifier toResourceIdentifierIfNotNull( + @Nullable final T resource) { + return ofNullable(resource).map(Referenceable::toResourceIdentifier).orElse(null); + } + + /** + * Given a {@link JsonNode} {@code referenceValue} which is the JSON representation of CTP + * Reference object, this method checks if it is has a {@code typeId} with the value equal to + * {@code referenceTypeId}. + * + * @param referenceValue JSON representation of CTP reference object + * @param referenceTypeId the typeId to check of the reference is of the same type or not. + * @return true if the typeId field of the reference has the same value as {@code + * referenceTypeId}, otherwise, false. + */ + public static boolean isReferenceOfType( + @Nonnull final JsonNode referenceValue, final String referenceTypeId) { + return getReferenceTypeId(referenceValue) + .map(resolvedReferenceTypeId -> Objects.equals(resolvedReferenceTypeId, referenceTypeId)) + .orElse(false); + } + + @Nonnull + private static Optional getReferenceTypeId(@Nonnull final JsonNode referenceValue) { + final JsonNode typeId = referenceValue.get(REFERENCE_TYPE_ID_FIELD); + return Optional.ofNullable(typeId).map(JsonNode::asText); + } + + private ResourceIdentifierUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/StreamUtils.java b/src/main/java/com/commercetools/sync/commons/utils/StreamUtils.java index fd354a4e55..8fa83b21e0 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/StreamUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/StreamUtils.java @@ -1,30 +1,28 @@ package com.commercetools.sync.commons.utils; -import javax.annotation.Nonnull; import java.util.Objects; import java.util.function.Function; import java.util.stream.Stream; +import javax.annotation.Nonnull; public final class StreamUtils { - /** - * Applies the supplied {@code mapper} function on every non-null element in the supplied {@link Stream} of - * {@code elements}. - * - * @param elements the stream of elements. - * @param mapper the mapper function to apply on every element. - * @param the type of the elements in the stream. - * @param the resulting type after applying the mapper function on an element. - * @return a stream of the resulting mapped elements. - */ - @Nonnull - public static Stream filterNullAndMap( - @Nonnull final Stream elements, - @Nonnull final Function mapper) { + /** + * Applies the supplied {@code mapper} function on every non-null element in the supplied {@link + * Stream} of {@code elements}. + * + * @param elements the stream of elements. + * @param mapper the mapper function to apply on every element. + * @param the type of the elements in the stream. + * @param the resulting type after applying the mapper function on an element. + * @return a stream of the resulting mapped elements. + */ + @Nonnull + public static Stream filterNullAndMap( + @Nonnull final Stream elements, @Nonnull final Function mapper) { - return elements.filter(Objects::nonNull).map(mapper); - } + return elements.filter(Objects::nonNull).map(mapper); + } - private StreamUtils() { - } + private StreamUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/SyncSolutionInfo.java b/src/main/java/com/commercetools/sync/commons/utils/SyncSolutionInfo.java index f415033763..3729621532 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/SyncSolutionInfo.java +++ b/src/main/java/com/commercetools/sync/commons/utils/SyncSolutionInfo.java @@ -3,23 +3,20 @@ import io.sphere.sdk.client.SolutionInfo; public final class SyncSolutionInfo extends SolutionInfo { - private static final String LIB_NAME = "commercetools-sync-java"; - /** - * This value is injected by the script at gradle-scripts/set-library-version.gradle. - */ - public static final String LIB_VERSION = "#{LIB_VERSION}"; + private static final String LIB_NAME = "commercetools-sync-java"; + /** This value is injected by the script at gradle-scripts/set-library-version.gradle. */ + public static final String LIB_VERSION = "#{LIB_VERSION}"; - /** - * Extends {@link SolutionInfo} class of the JVM SDK to append to the User-Agent header with information of the - * commercetools-sync-java library - * - *

A User-Agent header with a solution information looks like this: - * {@code commercetools-jvm-sdk/1.46.1 (AHC/2.0) Java/1.8.0_92-b14 (Mac OS X; x86_64) - * {@value LIB_NAME}/{@value LIB_VERSION}}

- * - */ - public SyncSolutionInfo() { - setName(LIB_NAME); - setVersion(LIB_VERSION); - } -} \ No newline at end of file + /** + * Extends {@link SolutionInfo} class of the JVM SDK to append to the User-Agent header with + * information of the commercetools-sync-java library + * + *

A User-Agent header with a solution information looks like this: {@code + * commercetools-jvm-sdk/1.46.1 (AHC/2.0) Java/1.8.0_92-b14 (Mac OS X; x86_64) {@value + * LIB_NAME}/{@value LIB_VERSION}} + */ + public SyncSolutionInfo() { + setName(LIB_NAME); + setVersion(LIB_VERSION); + } +} diff --git a/src/main/java/com/commercetools/sync/commons/utils/SyncUtils.java b/src/main/java/com/commercetools/sync/commons/utils/SyncUtils.java index 62f8460d45..991e70b11f 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/SyncUtils.java +++ b/src/main/java/com/commercetools/sync/commons/utils/SyncUtils.java @@ -3,99 +3,98 @@ import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.models.WithKey; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class SyncUtils { - private static final String UUID_REGEX = "[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"; + private static final String UUID_REGEX = + "[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"; - /** - * Given a list of elements and a {@code batchSize}, this method distributes the elements into batches with the - * {@code batchSize}. Each batch is represented by a {@link List} of elements and all the batches are grouped and - * represented by a {@link List}<{@link List}> of elements, which is returned by the method. - * - * @param the type of the draft elements. - * @param elements the list of elements to split into batches. - * @param batchSize the size of each batch. - * @return a list of lists where each list represents a batch of elements. - */ - public static List> batchElements(@Nonnull final List elements, final int batchSize) { - List> batches = new ArrayList<>(); - for (int i = 0; i < elements.size() && batchSize > 0; i += batchSize) { - batches.add(elements.subList(i, Math.min(i + batchSize, elements.size()))); - } - return batches; + /** + * Given a list of elements and a {@code batchSize}, this method distributes the elements into + * batches with the {@code batchSize}. Each batch is represented by a {@link List} of elements and + * all the batches are grouped and represented by a {@link List}<{@link List}> of elements, + * which is returned by the method. + * + * @param the type of the draft elements. + * @param elements the list of elements to split into batches. + * @param batchSize the size of each batch. + * @return a list of lists where each list represents a batch of elements. + */ + public static List> batchElements( + @Nonnull final List elements, final int batchSize) { + List> batches = new ArrayList<>(); + for (int i = 0; i < elements.size() && batchSize > 0; i += batchSize) { + batches.add(elements.subList(i, Math.min(i + batchSize, elements.size()))); } + return batches; + } - /** - * Given a reference to a resource of type {@code T}, this method - * checks if the reference is expanded. If it is, then it executes the {@code keyInReferenceSupplier} and returns - * it's result. Otherwise, it returns the supplied reference as is. Since, the reference could be {@code null}, this - * method could also return null if the reference was not expanded. - * - *

This method expects the passed supplier to either

- * - * @param reference the reference of the resource to check if it's expanded. - * @param the type of the resource. - * @param keyInReferenceSupplier the supplier to execute and return its result if the {@code reference} was - * expanded. - * - * @return returns the result of the {@code keyInReferenceSupplier} if the {@code reference} was expanded. - * Otherwise, it returns the supplied reference as is. - */ - @Nullable - public static Reference getReferenceWithKeyReplaced( - @Nullable final Reference reference, - @Nonnull final Supplier> keyInReferenceSupplier) { + /** + * Given a reference to a resource of type {@code T}, this method checks if the reference is + * expanded. If it is, then it executes the {@code keyInReferenceSupplier} and returns it's + * result. Otherwise, it returns the supplied reference as is. Since, the reference could be + * {@code null}, this method could also return null if the reference was not expanded. + * + *

This method expects the passed supplier to either + * + * @param reference the reference of the resource to check if it's expanded. + * @param the type of the resource. + * @param keyInReferenceSupplier the supplier to execute and return its result if the {@code + * reference} was expanded. + * @return returns the result of the {@code keyInReferenceSupplier} if the {@code reference} was + * expanded. Otherwise, it returns the supplied reference as is. + */ + @Nullable + public static Reference getReferenceWithKeyReplaced( + @Nullable final Reference reference, + @Nonnull final Supplier> keyInReferenceSupplier) { - if (reference != null && reference.getObj() != null) { - return keyInReferenceSupplier.get(); - } - return reference; + if (reference != null && reference.getObj() != null) { + return keyInReferenceSupplier.get(); } + return reference; + } - /** - * Given a reference to a resource of type {@code T}, this method - * checks if the reference is expanded. If it is, then it return the resource identifier with key. - * Otherwise, it returns the resource identifier with id. - * Since, the reference could be {@code null}, this method could also return null if the reference was not expanded. - * - * @param reference the reference of the resource to check if it's expanded. - * @param the type of the resource. - * - * @return returns the resource identifier with key if the {@code reference} was expanded. - * Otherwise, it returns the resource identifier with id. - */ - @Nullable - public static ResourceIdentifier getResourceIdentifierWithKey( - @Nullable final Reference reference) { + /** + * Given a reference to a resource of type {@code T}, this method checks if the reference is + * expanded. If it is, then it return the resource identifier with key. Otherwise, it returns the + * resource identifier with id. Since, the reference could be {@code null}, this method could also + * return null if the reference was not expanded. + * + * @param reference the reference of the resource to check if it's expanded. + * @param the type of the resource. + * @return returns the resource identifier with key if the {@code reference} was expanded. + * Otherwise, it returns the resource identifier with id. + */ + @Nullable + public static ResourceIdentifier getResourceIdentifierWithKey( + @Nullable final Reference reference) { - if (reference != null) { - if (reference.getObj() != null) { - return ResourceIdentifier.ofKey(reference.getObj().getKey()); - } - return ResourceIdentifier.ofId(reference.getId()); - } - - return null; + if (reference != null) { + if (reference.getObj() != null) { + return ResourceIdentifier.ofKey(reference.getObj().getKey()); + } + return ResourceIdentifier.ofId(reference.getId()); } - /** - * Given an id as {@link String}, this method checks whether if it is in UUID format or not. - * - * @param id to check if it is in UUID format. - * @return true if it is in UUID format, otherwise false. - */ - public static boolean isUuid(@Nonnull final String id) { - final Pattern regexPattern = Pattern.compile(UUID_REGEX); - return regexPattern.matcher(id).matches(); - } + return null; + } - private SyncUtils() { - } + /** + * Given an id as {@link String}, this method checks whether if it is in UUID format or not. + * + * @param id to check if it is in UUID format. + * @return true if it is in UUID format, otherwise false. + */ + public static boolean isUuid(@Nonnull final String id) { + final Pattern regexPattern = Pattern.compile(UUID_REGEX); + return regexPattern.matcher(id).matches(); + } + + private SyncUtils() {} } diff --git a/src/main/java/com/commercetools/sync/commons/utils/TriConsumer.java b/src/main/java/com/commercetools/sync/commons/utils/TriConsumer.java index 97072e5be9..c2f9b5caaf 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/TriConsumer.java +++ b/src/main/java/com/commercetools/sync/commons/utils/TriConsumer.java @@ -7,8 +7,8 @@ * Represents an operation that accepts three arguments and returns no result. This is the * three-arity specialization of {@link Consumer}. * - *

This is a functional interface - * whose functional method is {@link #accept(Object, Object, Object)}. + *

This is a functional interface whose functional method is + * {@link #accept(Object, Object, Object)}. * * @param the type of the first argument to the function * @param the type of the second argument to the function @@ -18,14 +18,13 @@ @FunctionalInterface public interface TriConsumer { - /** - * Performs operation on the given arguments. - * - * @param firstParam the first argument. - * @param secondParam the second argument. - * @param thirdParam the third argument. - */ - void accept(@Nullable final T firstParam, @Nullable final U secondParam, - @Nullable final V thirdParam); - + /** + * Performs operation on the given arguments. + * + * @param firstParam the first argument. + * @param secondParam the second argument. + * @param thirdParam the third argument. + */ + void accept( + @Nullable final T firstParam, @Nullable final U secondParam, @Nullable final V thirdParam); } diff --git a/src/main/java/com/commercetools/sync/commons/utils/TriFunction.java b/src/main/java/com/commercetools/sync/commons/utils/TriFunction.java index c0e991b654..5bf60d9ecb 100644 --- a/src/main/java/com/commercetools/sync/commons/utils/TriFunction.java +++ b/src/main/java/com/commercetools/sync/commons/utils/TriFunction.java @@ -1,32 +1,31 @@ package com.commercetools.sync.commons.utils; -import javax.annotation.Nullable; import java.util.function.Function; +import javax.annotation.Nullable; /** - * Represents a function that accepts three arguments and produces a result. - * This is the three-arity specialization of {@link Function}. + * Represents a function that accepts three arguments and produces a result. This is the three-arity + * specialization of {@link Function}. * - *

This is a functional interface - * whose functional method is {@link #apply(Object, Object, Object)}. + *

This is a functional interface whose functional method is + * {@link #apply(Object, Object, Object)}. * * @param the type of the first argument to the function * @param the type of the second argument to the function * @param the type of the third argument to the function * @param the type of the result of the function - * * @see Function */ @FunctionalInterface -public interface TriFunction { +public interface TriFunction { - /** - * Applies this function to the given arguments. - * - * @param firstParam the first argument. - * @param secondParam the second argument. - * @param thirdParam the third argument. - * @return the function result. - */ - R apply(@Nullable T firstParam, @Nullable U secondParam, @Nullable S thirdParam); + /** + * Applies this function to the given arguments. + * + * @param firstParam the first argument. + * @param secondParam the second argument. + * @param thirdParam the third argument. + * @return the function result. + */ + R apply(@Nullable T firstParam, @Nullable U secondParam, @Nullable S thirdParam); } diff --git a/src/main/java/com/commercetools/sync/customers/CustomerSync.java b/src/main/java/com/commercetools/sync/customers/CustomerSync.java index ab60312e70..941089a74d 100644 --- a/src/main/java/com/commercetools/sync/customers/CustomerSync.java +++ b/src/main/java/com/commercetools/sync/customers/CustomerSync.java @@ -1,5 +1,13 @@ package com.commercetools.sync.customers; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static com.commercetools.sync.customers.utils.CustomerSyncUtils.buildActions; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; + import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.customers.helpers.CustomerBatchValidator; @@ -14,275 +22,299 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.customers.Customer; import io.sphere.sdk.customers.CustomerDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static com.commercetools.sync.customers.utils.CustomerSyncUtils.buildActions; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; - -/** - * This class syncs customer drafts with the corresponding customers in the CTP project. - */ -public class CustomerSync extends BaseSync { - - private static final String CTP_CUSTOMER_FETCH_FAILED = "Failed to fetch existing customers with keys: '%s'."; - private static final String FAILED_TO_PROCESS = "Failed to process the CustomerDraft with key:'%s'. Reason: %s"; - private static final String CTP_CUSTOMER_UPDATE_FAILED = "Failed to update customer with key: '%s'. Reason: %s"; - - private final CustomerService customerService; - private final CustomerReferenceResolver referenceResolver; - private final CustomerBatchValidator batchValidator; - - /** - * Takes a {@link CustomerSyncOptions} to instantiate a new {@link CustomerSync} instance that could be used to - * sync customer drafts in the CTP project specified in the injected {@link CustomerSync} instance. - * - * @param customerSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - */ - public CustomerSync(@Nonnull final CustomerSyncOptions customerSyncOptions) { - this(customerSyncOptions, - new CustomerServiceImpl(customerSyncOptions), - new TypeServiceImpl(customerSyncOptions), - new CustomerGroupServiceImpl(customerSyncOptions)); - } - - /** - * Takes a {@link CustomerSyncOptions} and service instances to instantiate a new {@link CustomerSync} instance - * that could be used to sync customer drafts in the CTP project specified in the injected - * {@link CustomerSyncOptions} instance. - * - *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and passed to. - * - * @param customerSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param customerService the customer service which is responsible for fetching/caching the Customers from the - * CTP project. - * @param typeService the type service which is responsible for fetching/caching the Types from the CTP project. - * @param customerGroupService the customer group service which is responsible for fetching/caching the - * CustomerGroups from the CTP project. - */ - protected CustomerSync(@Nonnull final CustomerSyncOptions customerSyncOptions, - @Nonnull final CustomerService customerService, - @Nonnull final TypeService typeService, - @Nonnull final CustomerGroupService customerGroupService) { - super(new CustomerSyncStatistics(), customerSyncOptions); - this.customerService = customerService; - this.referenceResolver = new CustomerReferenceResolver(getSyncOptions(), typeService, customerGroupService); - this.batchValidator = new CustomerBatchValidator(getSyncOptions(), getStatistics()); - } - - /** - * Iterates through the whole {@code customerDrafts} list and accumulates its valid drafts to batches. - * Every batch is then processed by {@link CustomerSync#processBatch(List)}. - * - *

Inherited doc: - * {@inheritDoc} - * - * @param customerDrafts {@link List} of {@link CustomerDraft}'s that would be synced into CTP project. - * @return {@link CompletionStage} with {@link CustomerSyncStatistics} holding statistics of all sync - * processes performed by this sync instance. - */ - @Override - protected CompletionStage process(@Nonnull final List customerDrafts) { - final List> batches = batchElements(customerDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, completedFuture(statistics)); - +/** This class syncs customer drafts with the corresponding customers in the CTP project. */ +public class CustomerSync + extends BaseSync { + + private static final String CTP_CUSTOMER_FETCH_FAILED = + "Failed to fetch existing customers with keys: '%s'."; + private static final String FAILED_TO_PROCESS = + "Failed to process the CustomerDraft with key:'%s'. Reason: %s"; + private static final String CTP_CUSTOMER_UPDATE_FAILED = + "Failed to update customer with key: '%s'. Reason: %s"; + + private final CustomerService customerService; + private final CustomerReferenceResolver referenceResolver; + private final CustomerBatchValidator batchValidator; + + /** + * Takes a {@link CustomerSyncOptions} to instantiate a new {@link CustomerSync} instance that + * could be used to sync customer drafts in the CTP project specified in the injected {@link + * CustomerSync} instance. + * + * @param customerSyncOptions the container of all the options of the sync process including the + * CTP project client and/or configuration and other sync-specific options. + */ + public CustomerSync(@Nonnull final CustomerSyncOptions customerSyncOptions) { + this( + customerSyncOptions, + new CustomerServiceImpl(customerSyncOptions), + new TypeServiceImpl(customerSyncOptions), + new CustomerGroupServiceImpl(customerSyncOptions)); + } + + /** + * Takes a {@link CustomerSyncOptions} and service instances to instantiate a new {@link + * CustomerSync} instance that could be used to sync customer drafts in the CTP project specified + * in the injected {@link CustomerSyncOptions} instance. + * + *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and + * passed to. + * + * @param customerSyncOptions the container of all the options of the sync process including the + * CTP project client and/or configuration and other sync-specific options. + * @param customerService the customer service which is responsible for fetching/caching the + * Customers from the CTP project. + * @param typeService the type service which is responsible for fetching/caching the Types from + * the CTP project. + * @param customerGroupService the customer group service which is responsible for + * fetching/caching the CustomerGroups from the CTP project. + */ + protected CustomerSync( + @Nonnull final CustomerSyncOptions customerSyncOptions, + @Nonnull final CustomerService customerService, + @Nonnull final TypeService typeService, + @Nonnull final CustomerGroupService customerGroupService) { + super(new CustomerSyncStatistics(), customerSyncOptions); + this.customerService = customerService; + this.referenceResolver = + new CustomerReferenceResolver(getSyncOptions(), typeService, customerGroupService); + this.batchValidator = new CustomerBatchValidator(getSyncOptions(), getStatistics()); + } + + /** + * Iterates through the whole {@code customerDrafts} list and accumulates its valid drafts to + * batches. Every batch is then processed by {@link CustomerSync#processBatch(List)}. + * + *

Inherited doc: {@inheritDoc} + * + * @param customerDrafts {@link List} of {@link CustomerDraft}'s that would be synced into CTP + * project. + * @return {@link CompletionStage} with {@link CustomerSyncStatistics} holding statistics of all + * sync processes performed by this sync instance. + */ + @Override + protected CompletionStage process( + @Nonnull final List customerDrafts) { + final List> batches = + batchElements(customerDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, completedFuture(statistics)); + } + + @Override + protected CompletionStage processBatch( + @Nonnull final List batch) { + final ImmutablePair, CustomerBatchValidator.ReferencedKeys> result = + batchValidator.validateAndCollectReferencedKeys(batch); + + final Set validCustomerDrafts = result.getLeft(); + if (validCustomerDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return completedFuture(statistics); } - @Override - protected CompletionStage processBatch(@Nonnull final List batch) { - final ImmutablePair, CustomerBatchValidator.ReferencedKeys> result = - batchValidator.validateAndCollectReferencedKeys(batch); - - final Set validCustomerDrafts = result.getLeft(); - if (validCustomerDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return completedFuture(statistics); - } - - return referenceResolver - .populateKeyToIdCachesForReferencedKeys(result.getRight()) - .handle(ImmutablePair::new) - .thenCompose(cachingResponse -> { - final Throwable cachingException = cachingResponse.getValue(); - if (cachingException != null) { - handleError(new SyncException("Failed to build a cache of keys to ids.", cachingException), - validCustomerDrafts.size()); - return CompletableFuture.completedFuture(null); - } - - final Set validCustomerKeys = - validCustomerDrafts.stream().map(CustomerDraft::getKey).collect(toSet()); - - return customerService - .fetchMatchingCustomersByKeys(validCustomerKeys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { + return referenceResolver + .populateKeyToIdCachesForReferencedKeys(result.getRight()) + .handle(ImmutablePair::new) + .thenCompose( + cachingResponse -> { + final Throwable cachingException = cachingResponse.getValue(); + if (cachingException != null) { + handleError( + new SyncException("Failed to build a cache of keys to ids.", cachingException), + validCustomerDrafts.size()); + return CompletableFuture.completedFuture(null); + } + + final Set validCustomerKeys = + validCustomerDrafts.stream().map(CustomerDraft::getKey).collect(toSet()); + + return customerService + .fetchMatchingCustomersByKeys(validCustomerKeys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { final Set fetchedCustomers = fetchResponse.getKey(); final Throwable exception = fetchResponse.getValue(); if (exception != null) { - final String errorMessage = format(CTP_CUSTOMER_FETCH_FAILED, validCustomerKeys); - handleError(new SyncException(errorMessage, exception), validCustomerKeys.size()); - return CompletableFuture.completedFuture(null); + final String errorMessage = + format(CTP_CUSTOMER_FETCH_FAILED, validCustomerKeys); + handleError( + new SyncException(errorMessage, exception), validCustomerKeys.size()); + return CompletableFuture.completedFuture(null); } else { - return syncBatch(fetchedCustomers, validCustomerDrafts); + return syncBatch(fetchedCustomers, validCustomerDrafts); } - }); + }); }) - .thenApply(ignoredResult -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignoredResult -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); - } - - @Nonnull - private CompletionStage syncBatch( - @Nonnull final Set oldCustomers, - @Nonnull final Set newCustomerDrafts) { - - final Map oldCustomerMap = oldCustomers - .stream() - .collect(toMap(Customer::getKey, identity())); - - return CompletableFuture.allOf(newCustomerDrafts - .stream() - .map(customerDraft -> - referenceResolver - .resolveReferences(customerDraft) - .thenCompose(resolvedCustomerDraft -> syncDraft(oldCustomerMap, resolvedCustomerDraft)) - .exceptionally(completionException -> { - final String errorMessage = format(FAILED_TO_PROCESS, customerDraft.getKey(), - completionException.getMessage()); - handleError(new SyncException(errorMessage, completionException), 1); - return null; - }) - ) + } + + @Nonnull + private CompletionStage syncBatch( + @Nonnull final Set oldCustomers, + @Nonnull final Set newCustomerDrafts) { + + final Map oldCustomerMap = + oldCustomers.stream().collect(toMap(Customer::getKey, identity())); + + return CompletableFuture.allOf( + newCustomerDrafts.stream() + .map( + customerDraft -> + referenceResolver + .resolveReferences(customerDraft) + .thenCompose( + resolvedCustomerDraft -> + syncDraft(oldCustomerMap, resolvedCustomerDraft)) + .exceptionally( + completionException -> { + final String errorMessage = + format( + FAILED_TO_PROCESS, + customerDraft.getKey(), + completionException.getMessage()); + handleError(new SyncException(errorMessage, completionException), 1); + return null; + })) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); - } - - @Nonnull - private CompletionStage syncDraft( - @Nonnull final Map oldCustomerMap, - @Nonnull final CustomerDraft newCustomerDraft) { + } - final Customer oldCustomer = oldCustomerMap.get(newCustomerDraft.getKey()); - return Optional.ofNullable(oldCustomer) - .map(customer -> buildActionsAndUpdate(oldCustomer, newCustomerDraft)) - .orElseGet(() -> applyCallbackAndCreate(newCustomerDraft)); - } + @Nonnull + private CompletionStage syncDraft( + @Nonnull final Map oldCustomerMap, + @Nonnull final CustomerDraft newCustomerDraft) { - @Nonnull - private CompletionStage buildActionsAndUpdate( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomerDraft) { + final Customer oldCustomer = oldCustomerMap.get(newCustomerDraft.getKey()); + return Optional.ofNullable(oldCustomer) + .map(customer -> buildActionsAndUpdate(oldCustomer, newCustomerDraft)) + .orElseGet(() -> applyCallbackAndCreate(newCustomerDraft)); + } - final List> updateActions = - buildActions(oldCustomer, newCustomerDraft, syncOptions); + @Nonnull + private CompletionStage buildActionsAndUpdate( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomerDraft) { - final List> updateActionsAfterCallback - = syncOptions.applyBeforeUpdateCallback(updateActions, newCustomerDraft, oldCustomer); + final List> updateActions = + buildActions(oldCustomer, newCustomerDraft, syncOptions); - if (!updateActionsAfterCallback.isEmpty()) { - return updateCustomer(oldCustomer, newCustomerDraft, updateActionsAfterCallback); - } + final List> updateActionsAfterCallback = + syncOptions.applyBeforeUpdateCallback(updateActions, newCustomerDraft, oldCustomer); - return completedFuture(null); + if (!updateActionsAfterCallback.isEmpty()) { + return updateCustomer(oldCustomer, newCustomerDraft, updateActionsAfterCallback); } - @Nonnull - private CompletionStage updateCustomer( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomerDraft, - @Nonnull final List> updateActionsAfterCallback) { - - return customerService - .updateCustomer(oldCustomer, updateActionsAfterCallback) - .handle(ImmutablePair::of) - .thenCompose(updateResponse -> { - final Throwable exception = updateResponse.getValue(); - if (exception != null) { - return executeSupplierIfConcurrentModificationException( - exception, - () -> fetchAndUpdate(oldCustomer, newCustomerDraft), - () -> { - final String errorMessage = - format(CTP_CUSTOMER_UPDATE_FAILED, newCustomerDraft.getKey(), exception.getMessage()); - handleError(new SyncException(errorMessage, exception), 1); - return CompletableFuture.completedFuture(null); - }); - } else { - statistics.incrementUpdated(); - return CompletableFuture.completedFuture(null); - } + return completedFuture(null); + } + + @Nonnull + private CompletionStage updateCustomer( + @Nonnull final Customer oldCustomer, + @Nonnull final CustomerDraft newCustomerDraft, + @Nonnull final List> updateActionsAfterCallback) { + + return customerService + .updateCustomer(oldCustomer, updateActionsAfterCallback) + .handle(ImmutablePair::of) + .thenCompose( + updateResponse -> { + final Throwable exception = updateResponse.getValue(); + if (exception != null) { + return executeSupplierIfConcurrentModificationException( + exception, + () -> fetchAndUpdate(oldCustomer, newCustomerDraft), + () -> { + final String errorMessage = + format( + CTP_CUSTOMER_UPDATE_FAILED, + newCustomerDraft.getKey(), + exception.getMessage()); + handleError(new SyncException(errorMessage, exception), 1); + return CompletableFuture.completedFuture(null); + }); + } else { + statistics.incrementUpdated(); + return CompletableFuture.completedFuture(null); + } }); - } - - @Nonnull - private CompletionStage fetchAndUpdate( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomerDraft) { - - final String customerKey = oldCustomer.getKey(); - return customerService - .fetchCustomerByKey(customerKey) - .handle(ImmutablePair::of) - .thenCompose(fetchResponse -> { - final Optional fetchedCustomerOptional = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(CTP_CUSTOMER_UPDATE_FAILED, customerKey, + } + + @Nonnull + private CompletionStage fetchAndUpdate( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomerDraft) { + + final String customerKey = oldCustomer.getKey(); + return customerService + .fetchCustomerByKey(customerKey) + .handle(ImmutablePair::of) + .thenCompose( + fetchResponse -> { + final Optional fetchedCustomerOptional = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + CTP_CUSTOMER_UPDATE_FAILED, + customerKey, "Failed to fetch from CTP while retrying after concurrency modification."); - handleError(new SyncException(errorMessage, exception), 1); - return CompletableFuture.completedFuture(null); - } - - return fetchedCustomerOptional - .map(fetchedCustomer -> buildActionsAndUpdate(fetchedCustomer, newCustomerDraft)) - .orElseGet(() -> { - final String errorMessage = format(CTP_CUSTOMER_UPDATE_FAILED, customerKey, - "Not found when attempting to fetch while retrying after concurrency modification."); + handleError(new SyncException(errorMessage, exception), 1); + return CompletableFuture.completedFuture(null); + } + + return fetchedCustomerOptional + .map(fetchedCustomer -> buildActionsAndUpdate(fetchedCustomer, newCustomerDraft)) + .orElseGet( + () -> { + final String errorMessage = + format( + CTP_CUSTOMER_UPDATE_FAILED, + customerKey, + "Not found when attempting to fetch while retrying after concurrency modification."); handleError(new SyncException(errorMessage, null), 1); return CompletableFuture.completedFuture(null); - }); + }); }); - } - - @Nonnull - private CompletionStage applyCallbackAndCreate(@Nonnull final CustomerDraft customerDraft) { - - return syncOptions - .applyBeforeCreateCallback(customerDraft) - .map(draft -> customerService - .createCustomer(draft) - .thenAccept(customerOptional -> { - if (customerOptional.isPresent()) { - statistics.incrementCreated(); - } else { - statistics.incrementFailed(); - } - })) - .orElseGet(() -> CompletableFuture.completedFuture(null)); - } - - private void handleError(@Nonnull final SyncException syncException, - final int failedTimes) { - syncOptions.applyErrorCallback(syncException); - statistics.incrementFailed(failedTimes); - } + } + + @Nonnull + private CompletionStage applyCallbackAndCreate(@Nonnull final CustomerDraft customerDraft) { + + return syncOptions + .applyBeforeCreateCallback(customerDraft) + .map( + draft -> + customerService + .createCustomer(draft) + .thenAccept( + customerOptional -> { + if (customerOptional.isPresent()) { + statistics.incrementCreated(); + } else { + statistics.incrementFailed(); + } + })) + .orElseGet(() -> CompletableFuture.completedFuture(null)); + } + + private void handleError(@Nonnull final SyncException syncException, final int failedTimes) { + syncOptions.applyErrorCallback(syncException); + statistics.incrementFailed(failedTimes); + } } diff --git a/src/main/java/com/commercetools/sync/customers/CustomerSyncOptions.java b/src/main/java/com/commercetools/sync/customers/CustomerSyncOptions.java index 2995c1eba3..52170fb25a 100644 --- a/src/main/java/com/commercetools/sync/customers/CustomerSyncOptions.java +++ b/src/main/java/com/commercetools/sync/customers/CustomerSyncOptions.java @@ -9,30 +9,43 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.customers.Customer; import io.sphere.sdk.customers.CustomerDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class CustomerSyncOptions extends BaseSyncOptions { - CustomerSyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallback, - @Nullable final TriConsumer, Optional> - warningCallback, - final int batchSize, - @Nullable final TriFunction>, CustomerDraft, Customer, - List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, final long cacheSize) { - super(ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, cacheSize); - } + CustomerSyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallback, + @Nullable + final TriConsumer, Optional> + warningCallback, + final int batchSize, + @Nullable + final TriFunction< + List>, + CustomerDraft, + Customer, + List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { + super( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } } diff --git a/src/main/java/com/commercetools/sync/customers/CustomerSyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/customers/CustomerSyncOptionsBuilder.java index 23c066fc4b..b34defa267 100644 --- a/src/main/java/com/commercetools/sync/customers/CustomerSyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/customers/CustomerSyncOptionsBuilder.java @@ -1,59 +1,59 @@ package com.commercetools.sync.customers; - import com.commercetools.sync.commons.BaseSyncOptionsBuilder; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customers.Customer; import io.sphere.sdk.customers.CustomerDraft; - import javax.annotation.Nonnull; -public final class CustomerSyncOptionsBuilder extends BaseSyncOptionsBuilder { - public static final int BATCH_SIZE_DEFAULT = 50; - - private CustomerSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } - - /** - * Creates a new instance of {@link CustomerSyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 50). - * - * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link CustomerSyncOptionsBuilder} - */ - public static CustomerSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new CustomerSyncOptionsBuilder(ctpClient) - .batchSize(BATCH_SIZE_DEFAULT); - } - - /** - * Creates a new instance of {@link CustomerSyncOptions} enriched with all attributes provided to {@code this} - * builder. - * - * @return new instance of {@link CustomerSyncOptions} - */ - @Override - public CustomerSyncOptions build() { - return new CustomerSyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize); - } - - /** - * Returns an instance of this class to be used in the superclass's generic methods. Please see the JavaDoc in the - * overridden method for further details. - * - * @return an instance of this class. - */ - @Override - protected CustomerSyncOptionsBuilder getThis() { - return this; - } +public final class CustomerSyncOptionsBuilder + extends BaseSyncOptionsBuilder< + CustomerSyncOptionsBuilder, CustomerSyncOptions, Customer, CustomerDraft> { + public static final int BATCH_SIZE_DEFAULT = 50; + + private CustomerSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } + + /** + * Creates a new instance of {@link CustomerSyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 50). + * + * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the + * target CTP project. + * @return new instance of {@link CustomerSyncOptionsBuilder} + */ + public static CustomerSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new CustomerSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } + + /** + * Creates a new instance of {@link CustomerSyncOptions} enriched with all attributes provided to + * {@code this} builder. + * + * @return new instance of {@link CustomerSyncOptions} + */ + @Override + public CustomerSyncOptions build() { + return new CustomerSyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } + + /** + * Returns an instance of this class to be used in the superclass's generic methods. Please see + * the JavaDoc in the overridden method for further details. + * + * @return an instance of this class. + */ + @Override + protected CustomerSyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/customers/helpers/CustomerBatchValidator.java b/src/main/java/com/commercetools/sync/customers/helpers/CustomerBatchValidator.java index 9bab5bb30d..8839b3c90b 100644 --- a/src/main/java/com/commercetools/sync/customers/helpers/CustomerBatchValidator.java +++ b/src/main/java/com/commercetools/sync/customers/helpers/CustomerBatchValidator.java @@ -1,229 +1,256 @@ package com.commercetools.sync.customers.helpers; +import static java.lang.String.format; +import static java.util.stream.Collectors.toSet; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.helpers.BaseBatchValidator; import com.commercetools.sync.customers.CustomerSyncOptions; import io.sphere.sdk.customers.CustomerDraft; import io.sphere.sdk.models.Address; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.Predicate; - +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.apache.commons.lang3.tuple.ImmutablePair; -import static java.lang.String.format; -import static java.util.stream.Collectors.toSet; -import static org.apache.commons.lang3.StringUtils.isBlank; - public class CustomerBatchValidator extends BaseBatchValidator { - public static final String CUSTOMER_DRAFT_IS_NULL = "CustomerDraft is null."; - public static final String CUSTOMER_DRAFT_KEY_NOT_SET = "CustomerDraft with email: %s doesn't have a key. " - + "Please make sure all customer drafts have keys."; - public static final String CUSTOMER_DRAFT_EMAIL_NOT_SET = "CustomerDraft with key: %s doesn't have an email. " - + "Please make sure all customer drafts have emails."; - static final String CUSTOMER_DRAFT_PASSWORD_NOT_SET = "CustomerDraft with key: %s doesn't have a password. " - + "Please make sure all customer drafts have passwords."; - - static final String ADDRESSES_ARE_NULL = "CustomerDraft with key: '%s' has null addresses on indexes: '%s'. " - + "Please make sure each address is set in the addresses list."; - static final String ADDRESSES_THAT_KEYS_NOT_SET = "CustomerDraft with key: '%s' has blank address keys on indexes: " - + "'%s'. Please make sure each address has a key in the addresses list"; - static final String ADDRESSES_THAT_KEYS_NOT_UNIQUE = "CustomerDraft with key: '%s' has duplicate address keys on " - + "indexes: '%s'. Please make sure each address key is unique in the addresses list."; - - static final String BILLING_ADDRESSES_ARE_NOT_VALID = "CustomerDraft with key: '%s' does not contain indices: '%s' " - + "of the 'billingAddresses' in the addresses list. " - + "Please make sure all customer drafts have valid index values for the 'billingAddresses'."; - - static final String SHIPPING_ADDRESSES_ARE_NOT_VALID = "CustomerDraft with key: '%s' does not contain indices: '%s'" - + " of the 'shippingAddresses' in the addresses list. " - + "Please make sure all customer drafts have valid index values for the 'shippingAddresses'."; - - public CustomerBatchValidator(@Nonnull final CustomerSyncOptions syncOptions, - @Nonnull final CustomerSyncStatistics syncStatistics) { - super(syncOptions, syncStatistics); - } - - /** - * Given the {@link List}<{@link CustomerDraft}> of drafts this method attempts to validate - * drafts and return an {@link ImmutablePair}<{@link Set}<{@link CustomerDraft}>, - * {@link CustomerBatchValidator.ReferencedKeys}> which contains the {@link Set} of valid drafts and - * referenced keys within a wrapper. - * - *

A valid customer draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a key which is not blank (null/empty)
  4. - *
  5. It has a email which is not blank (null/empty)
  6. - *
  7. Each address in the addresses list satisfies the following conditions: - *
      - *
    1. It is not null
    2. - *
    3. It has a key which is not blank (null/empty)
    4. - *
    5. It has a unique key
    6. - *
    - *
  8. - *
  9. Each address index in the 'billing' and 'shipping' addresses list are valid and contained - * in the addresses list. - *
- * - * - * @param customerDrafts the customer drafts to validate and collect referenced keys. - * @return {@link ImmutablePair}<{@link Set}<{@link CustomerDraft}>, - * {@link CustomerBatchValidator.ReferencedKeys}> which contains the {@link Set} of valid drafts and - * referenced keys within a wrapper. - */ - @Override - public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( - @Nonnull final List customerDrafts) { - - final ReferencedKeys referencedKeys = new ReferencedKeys(); - final Set validDrafts = customerDrafts - .stream() + public static final String CUSTOMER_DRAFT_IS_NULL = "CustomerDraft is null."; + public static final String CUSTOMER_DRAFT_KEY_NOT_SET = + "CustomerDraft with email: %s doesn't have a key. " + + "Please make sure all customer drafts have keys."; + public static final String CUSTOMER_DRAFT_EMAIL_NOT_SET = + "CustomerDraft with key: %s doesn't have an email. " + + "Please make sure all customer drafts have emails."; + static final String CUSTOMER_DRAFT_PASSWORD_NOT_SET = + "CustomerDraft with key: %s doesn't have a password. " + + "Please make sure all customer drafts have passwords."; + + static final String ADDRESSES_ARE_NULL = + "CustomerDraft with key: '%s' has null addresses on indexes: '%s'. " + + "Please make sure each address is set in the addresses list."; + static final String ADDRESSES_THAT_KEYS_NOT_SET = + "CustomerDraft with key: '%s' has blank address keys on indexes: " + + "'%s'. Please make sure each address has a key in the addresses list"; + static final String ADDRESSES_THAT_KEYS_NOT_UNIQUE = + "CustomerDraft with key: '%s' has duplicate address keys on " + + "indexes: '%s'. Please make sure each address key is unique in the addresses list."; + + static final String BILLING_ADDRESSES_ARE_NOT_VALID = + "CustomerDraft with key: '%s' does not contain indices: '%s' " + + "of the 'billingAddresses' in the addresses list. " + + "Please make sure all customer drafts have valid index values for the 'billingAddresses'."; + + static final String SHIPPING_ADDRESSES_ARE_NOT_VALID = + "CustomerDraft with key: '%s' does not contain indices: '%s'" + + " of the 'shippingAddresses' in the addresses list. " + + "Please make sure all customer drafts have valid index values for the 'shippingAddresses'."; + + public CustomerBatchValidator( + @Nonnull final CustomerSyncOptions syncOptions, + @Nonnull final CustomerSyncStatistics syncStatistics) { + super(syncOptions, syncStatistics); + } + + /** + * Given the {@link List}<{@link CustomerDraft}> of drafts this method attempts to validate + * drafts and return an {@link ImmutablePair}<{@link Set}<{@link CustomerDraft}>, {@link + * CustomerBatchValidator.ReferencedKeys}> which contains the {@link Set} of valid drafts and + * referenced keys within a wrapper. + * + *

A valid customer draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a key which is not blank (null/empty) + *
  3. It has a email which is not blank (null/empty) + *
  4. Each address in the addresses list satisfies the following conditions: + *
      + *
    1. It is not null + *
    2. It has a key which is not blank (null/empty) + *
    3. It has a unique key + *
    + *
  5. Each address index in the 'billing' and 'shipping' addresses list are valid and + * contained in the addresses list. + *
+ * + * @param customerDrafts the customer drafts to validate and collect referenced keys. + * @return {@link ImmutablePair}<{@link Set}<{@link CustomerDraft}>, {@link + * CustomerBatchValidator.ReferencedKeys}> which contains the {@link Set} of valid drafts + * and referenced keys within a wrapper. + */ + @Override + public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( + @Nonnull final List customerDrafts) { + + final ReferencedKeys referencedKeys = new ReferencedKeys(); + final Set validDrafts = + customerDrafts.stream() .filter(this::isValidCustomerDraft) .peek(customerDraft -> collectReferencedKeys(referencedKeys, customerDraft)) .collect(toSet()); - return ImmutablePair.of(validDrafts, referencedKeys); + return ImmutablePair.of(validDrafts, referencedKeys); + } + + private boolean isValidCustomerDraft(@Nullable final CustomerDraft customerDraft) { + + if (customerDraft == null) { + handleError(CUSTOMER_DRAFT_IS_NULL); + } else if (isBlank(customerDraft.getKey())) { + handleError(format(CUSTOMER_DRAFT_KEY_NOT_SET, customerDraft.getEmail())); + } else if (isBlank(customerDraft.getEmail())) { + handleError(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, customerDraft.getKey())); + } else if (isBlank(customerDraft.getPassword())) { + handleError(format(CUSTOMER_DRAFT_PASSWORD_NOT_SET, customerDraft.getKey())); + } else if (hasValidAddresses(customerDraft)) { + return hasValidBillingAndShippingAddresses(customerDraft); } + return false; + } - private boolean isValidCustomerDraft(@Nullable final CustomerDraft customerDraft) { - - if (customerDraft == null) { - handleError(CUSTOMER_DRAFT_IS_NULL); - } else if (isBlank(customerDraft.getKey())) { - handleError(format(CUSTOMER_DRAFT_KEY_NOT_SET, customerDraft.getEmail())); - } else if (isBlank(customerDraft.getEmail())) { - handleError(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, customerDraft.getKey())); - } else if (isBlank(customerDraft.getPassword())) { - handleError(format(CUSTOMER_DRAFT_PASSWORD_NOT_SET, customerDraft.getKey())); - } else if (hasValidAddresses(customerDraft)) { - return hasValidBillingAndShippingAddresses(customerDraft); - } - return false; - } - - private boolean hasValidAddresses(@Nonnull final CustomerDraft customerDraft) { - - final List
addressList = customerDraft.getAddresses(); - if (addressList == null || addressList.isEmpty()) { - return true; - } + private boolean hasValidAddresses(@Nonnull final CustomerDraft customerDraft) { - final List indexesOfNullAddresses = getIndexes(addressList, Objects::isNull); - if (!indexesOfNullAddresses.isEmpty()) { - handleError(format(ADDRESSES_ARE_NULL, customerDraft.getKey(), indexesOfNullAddresses)); - return false; - } - - final List indexesOfBlankAddressKeys = getIndexes(addressList, address -> isBlank(address.getKey())); - if (!indexesOfBlankAddressKeys.isEmpty()) { - handleError(format(ADDRESSES_THAT_KEYS_NOT_SET, customerDraft.getKey(), indexesOfBlankAddressKeys)); - return false; - } + final List
addressList = customerDraft.getAddresses(); + if (addressList == null || addressList.isEmpty()) { + return true; + } - final Predicate
searchDuplicateKeys = (Address address) -> - addressList.stream().filter(a -> Objects.equals(a.getKey(), address.getKey())).count() > 1; + final List indexesOfNullAddresses = getIndexes(addressList, Objects::isNull); + if (!indexesOfNullAddresses.isEmpty()) { + handleError(format(ADDRESSES_ARE_NULL, customerDraft.getKey(), indexesOfNullAddresses)); + return false; + } - final List indexesOfDuplicateAddressKeys = getIndexes(addressList, searchDuplicateKeys); - if (!indexesOfDuplicateAddressKeys.isEmpty()) { - handleError(format(ADDRESSES_THAT_KEYS_NOT_UNIQUE, customerDraft.getKey(), indexesOfDuplicateAddressKeys)); - return false; - } + final List indexesOfBlankAddressKeys = + getIndexes(addressList, address -> isBlank(address.getKey())); + if (!indexesOfBlankAddressKeys.isEmpty()) { + handleError( + format(ADDRESSES_THAT_KEYS_NOT_SET, customerDraft.getKey(), indexesOfBlankAddressKeys)); + return false; + } - return true; + final Predicate
searchDuplicateKeys = + (Address address) -> + addressList.stream().filter(a -> Objects.equals(a.getKey(), address.getKey())).count() + > 1; + + final List indexesOfDuplicateAddressKeys = + getIndexes(addressList, searchDuplicateKeys); + if (!indexesOfDuplicateAddressKeys.isEmpty()) { + handleError( + format( + ADDRESSES_THAT_KEYS_NOT_UNIQUE, + customerDraft.getKey(), + indexesOfDuplicateAddressKeys)); + return false; } - @Nonnull - private List getIndexes(@Nonnull final List
list, @Nonnull final Predicate
predicate) { + return true; + } - final List indexes = new ArrayList<>(); - for (int i = 0; i < list.size(); i++) { - if (predicate.test(list.get(i))) { - indexes.add(i); - } - } - return indexes; - } + @Nonnull + private List getIndexes( + @Nonnull final List
list, @Nonnull final Predicate
predicate) { - private boolean hasValidBillingAndShippingAddresses(@Nonnull final CustomerDraft customerDraft) { - /* An example error response from the API, when the indexes are not valid: + final List indexes = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + if (predicate.test(list.get(i))) { + indexes.add(i); + } + } + return indexes; + } + + private boolean hasValidBillingAndShippingAddresses(@Nonnull final CustomerDraft customerDraft) { + /* An example error response from the API, when the indexes are not valid: + { + "statusCode": 400, + "message": "Customer does not contain an address at index '1'.", + "errors": [{ + "code": "InvalidOperation", + "message": "Customer does not contain an address at index '-1'." + }, { - "statusCode": 400, - "message": "Customer does not contain an address at index '1'.", - "errors": [{ - "code": "InvalidOperation", - "message": "Customer does not contain an address at index '-1'." - }, - { - "code": "InvalidOperation", - "message": "Customer does not contain an address at index '1'." - }] - } - */ - - final List
addressList = customerDraft.getAddresses(); - final Predicate isInvalidIndex = index -> index == null || addressList == null - || addressList.isEmpty() || index < 0 || index > addressList.size() - 1; - - if (customerDraft.getBillingAddresses() != null && !customerDraft.getBillingAddresses().isEmpty()) { - final List invalidIndexes = getIndexValues(customerDraft.getBillingAddresses(), isInvalidIndex); - if (!invalidIndexes.isEmpty()) { - handleError(format(BILLING_ADDRESSES_ARE_NOT_VALID, customerDraft.getKey(), invalidIndexes)); - return false; - } - } - - if (customerDraft.getShippingAddresses() != null && !customerDraft.getShippingAddresses().isEmpty()) { - final List invalidIndexes = getIndexValues(customerDraft.getShippingAddresses(), isInvalidIndex); - if (!invalidIndexes.isEmpty()) { - handleError(format(SHIPPING_ADDRESSES_ARE_NOT_VALID, customerDraft.getKey(), invalidIndexes)); - return false; - } - } - - return true; + "code": "InvalidOperation", + "message": "Customer does not contain an address at index '1'." + }] + } + */ + + final List
addressList = customerDraft.getAddresses(); + final Predicate isInvalidIndex = + index -> + index == null + || addressList == null + || addressList.isEmpty() + || index < 0 + || index > addressList.size() - 1; + + if (customerDraft.getBillingAddresses() != null + && !customerDraft.getBillingAddresses().isEmpty()) { + final List invalidIndexes = + getIndexValues(customerDraft.getBillingAddresses(), isInvalidIndex); + if (!invalidIndexes.isEmpty()) { + handleError( + format(BILLING_ADDRESSES_ARE_NOT_VALID, customerDraft.getKey(), invalidIndexes)); + return false; + } } - @Nonnull - private List getIndexValues(@Nonnull final List list, - @Nonnull final Predicate predicate) { - - final List indexes = new ArrayList<>(); - for (Integer integer : list) { - if (predicate.test(integer)) { - indexes.add(integer); - } - } - return indexes; + if (customerDraft.getShippingAddresses() != null + && !customerDraft.getShippingAddresses().isEmpty()) { + final List invalidIndexes = + getIndexValues(customerDraft.getShippingAddresses(), isInvalidIndex); + if (!invalidIndexes.isEmpty()) { + handleError( + format(SHIPPING_ADDRESSES_ARE_NOT_VALID, customerDraft.getKey(), invalidIndexes)); + return false; + } } - private void collectReferencedKeys( - @Nonnull final CustomerBatchValidator.ReferencedKeys referencedKeys, - @Nonnull final CustomerDraft customerDraft) { + return true; + } - collectReferencedKeyFromResourceIdentifier(customerDraft.getCustomerGroup(), - referencedKeys.customerGroupKeys::add); - collectReferencedKeyFromCustomFieldsDraft(customerDraft.getCustom(), - referencedKeys.typeKeys::add); + @Nonnull + private List getIndexValues( + @Nonnull final List list, @Nonnull final Predicate predicate) { + + final List indexes = new ArrayList<>(); + for (Integer integer : list) { + if (predicate.test(integer)) { + indexes.add(integer); + } } + return indexes; + } + + private void collectReferencedKeys( + @Nonnull final CustomerBatchValidator.ReferencedKeys referencedKeys, + @Nonnull final CustomerDraft customerDraft) { - public static class ReferencedKeys { - private final Set customerGroupKeys = new HashSet<>(); - private final Set typeKeys = new HashSet<>(); + collectReferencedKeyFromResourceIdentifier( + customerDraft.getCustomerGroup(), referencedKeys.customerGroupKeys::add); + collectReferencedKeyFromCustomFieldsDraft( + customerDraft.getCustom(), referencedKeys.typeKeys::add); + } - public Set getTypeKeys() { - return typeKeys; - } + public static class ReferencedKeys { + private final Set customerGroupKeys = new HashSet<>(); + private final Set typeKeys = new HashSet<>(); + + public Set getTypeKeys() { + return typeKeys; + } - public Set getCustomerGroupKeys() { - return customerGroupKeys; - } + public Set getCustomerGroupKeys() { + return customerGroupKeys; } + } } diff --git a/src/main/java/com/commercetools/sync/customers/helpers/CustomerReferenceResolver.java b/src/main/java/com/commercetools/sync/customers/helpers/CustomerReferenceResolver.java index 5d7078fe1a..41d2437326 100644 --- a/src/main/java/com/commercetools/sync/customers/helpers/CustomerReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/customers/helpers/CustomerReferenceResolver.java @@ -1,5 +1,11 @@ package com.commercetools.sync.customers.helpers; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.CustomReferenceResolver; import com.commercetools.sync.customers.CustomerSyncOptions; @@ -10,194 +16,212 @@ import io.sphere.sdk.customers.CustomerDraftBuilder; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.stores.Store; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; public final class CustomerReferenceResolver extends CustomReferenceResolver { - public static final String FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE = - "Failed to resolve customer group resource identifier on CustomerDraft with key:'%s'. Reason: %s"; - public static final String FAILED_TO_RESOLVE_STORE_REFERENCE = - "Failed to resolve store resource identifier on CustomerDraft with key:'%s'. Reason: %s"; - public static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type reference on " - + "CustomerDraft with key:'%s'."; - public static final String CUSTOMER_GROUP_DOES_NOT_EXIST = "CustomerGroup with key '%s' doesn't exist."; - - private final TypeService typeService; - private final CustomerGroupService customerGroupService; - - /** - * Takes a {@link CustomerSyncOptions} instance, a {@link TypeService} and a {@link CustomerGroupService} to - * instantiate a {@link CustomerReferenceResolver} instance that could be used to resolve the customer drafts in the - * CTP project specified in the injected {@link CustomerSyncOptions} instance. - * - * @param options the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - * @param typeService the service to fetch the custom types for reference resolution. - * @param customerGroupService the service to fetch the customer groups for reference resolution. - */ - public CustomerReferenceResolver( - @Nonnull final CustomerSyncOptions options, - @Nonnull final TypeService typeService, - @Nonnull final CustomerGroupService customerGroupService) { - super(options, typeService); - this.typeService = typeService; - this.customerGroupService = customerGroupService; + public static final String FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE = + "Failed to resolve customer group resource identifier on CustomerDraft with key:'%s'. Reason: %s"; + public static final String FAILED_TO_RESOLVE_STORE_REFERENCE = + "Failed to resolve store resource identifier on CustomerDraft with key:'%s'. Reason: %s"; + public static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = + "Failed to resolve custom type reference on " + "CustomerDraft with key:'%s'."; + public static final String CUSTOMER_GROUP_DOES_NOT_EXIST = + "CustomerGroup with key '%s' doesn't exist."; + + private final TypeService typeService; + private final CustomerGroupService customerGroupService; + + /** + * Takes a {@link CustomerSyncOptions} instance, a {@link TypeService} and a {@link + * CustomerGroupService} to instantiate a {@link CustomerReferenceResolver} instance that could be + * used to resolve the customer drafts in the CTP project specified in the injected {@link + * CustomerSyncOptions} instance. + * + * @param options the container of all the options of the sync process including the CTP project + * client and/or configuration and other sync-specific options. + * @param typeService the service to fetch the custom types for reference resolution. + * @param customerGroupService the service to fetch the customer groups for reference resolution. + */ + public CustomerReferenceResolver( + @Nonnull final CustomerSyncOptions options, + @Nonnull final TypeService typeService, + @Nonnull final CustomerGroupService customerGroupService) { + super(options, typeService); + this.typeService = typeService; + this.customerGroupService = customerGroupService; + } + + /** + * Given a {@link CustomerDraft} this method attempts to resolve the stores, customer group and + * custom type references to return a {@link CompletionStage} which contains a new instance of the + * draft with the resolved references or, in case an error occurs during reference resolution, a + * {@link ReferenceResolutionException}. + * + * @param customerDraft the draft to resolve its references. + * @return a {@link CompletionStage} that contains as a result a new CustomerDraft instance with + * resolved custom type reference or, in case an error occurs during reference resolution, a + * {@link ReferenceResolutionException}. + */ + @Override + @Nonnull + public CompletionStage resolveReferences( + @Nonnull final CustomerDraft customerDraft) { + return resolveCustomTypeReference(CustomerDraftBuilder.of(customerDraft)) + .thenCompose(this::resolveCustomerGroupReference) + .thenCompose(this::resolveStoreReferences) + .thenApply(CustomerDraftBuilder::build); + } + + @Override + protected CompletionStage resolveCustomTypeReference( + @Nonnull final CustomerDraftBuilder draftBuilder) { + + return resolveCustomTypeReference( + draftBuilder, + CustomerDraftBuilder::getCustom, + CustomerDraftBuilder::custom, + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getKey())); + } + + /** + * Given a {@link CustomerDraftBuilder} this method attempts to resolve the customer group to + * return a {@link CompletionStage} which contains a new instance of the builder with the resolved + * customer group reference. + * + * @param draftBuilder the customerDraft to resolve its customer group reference. + * @return a {@link CompletionStage} that contains as a result a new builder instance with + * resolved customer group reference or, in case an error occurs during reference resolution, + * a {@link ReferenceResolutionException}. + */ + @Nonnull + public CompletionStage resolveCustomerGroupReference( + @Nonnull final CustomerDraftBuilder draftBuilder) { + + final ResourceIdentifier customerGroupResourceIdentifier = + draftBuilder.getCustomerGroup(); + if (customerGroupResourceIdentifier != null + && customerGroupResourceIdentifier.getId() == null) { + String customerGroupKey; + try { + customerGroupKey = getKeyFromResourceIdentifier(customerGroupResourceIdentifier); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, + draftBuilder.getKey(), + referenceResolutionException.getMessage()))); + } + + return fetchAndResolveCustomerGroupReference(draftBuilder, customerGroupKey); } - - /** - * Given a {@link CustomerDraft} this method attempts to resolve the stores, customer group and custom type - * references to return a {@link CompletionStage} which contains a new instance of the draft with the resolved - * references or, in case an error occurs during reference resolution, a {@link ReferenceResolutionException}. - * - * @param customerDraft the draft to resolve its references. - * @return a {@link CompletionStage} that contains as a result a new CustomerDraft instance with resolved - * custom type reference or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Override - @Nonnull - public CompletionStage resolveReferences(@Nonnull final CustomerDraft customerDraft) { - return resolveCustomTypeReference(CustomerDraftBuilder.of(customerDraft)) - .thenCompose(this::resolveCustomerGroupReference) - .thenCompose(this::resolveStoreReferences) - .thenApply(CustomerDraftBuilder::build); - } - - @Override - protected CompletionStage resolveCustomTypeReference( - @Nonnull final CustomerDraftBuilder draftBuilder) { - - return resolveCustomTypeReference(draftBuilder, - CustomerDraftBuilder::getCustom, - CustomerDraftBuilder::custom, - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getKey())); + return completedFuture(draftBuilder); + } + + @Nonnull + private CompletionStage fetchAndResolveCustomerGroupReference( + @Nonnull final CustomerDraftBuilder draftBuilder, @Nonnull final String customerGroupKey) { + + return customerGroupService + .fetchCachedCustomerGroupId(customerGroupKey) + .thenCompose( + resolvedCustomerGroupIdOptional -> + resolvedCustomerGroupIdOptional + .map( + resolvedCustomerGroupId -> + completedFuture( + draftBuilder.customerGroup( + CustomerGroup.referenceOfId(resolvedCustomerGroupId) + .toResourceIdentifier()))) + .orElseGet( + () -> { + final String errorMessage = + format(CUSTOMER_GROUP_DOES_NOT_EXIST, customerGroupKey); + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, + draftBuilder.getKey(), + errorMessage))); + })); + } + + /** + * Given a {@link CustomerDraftBuilder} this method attempts to resolve the stores and return a + * {@link CompletionStage} which contains a new instance of the builder with the resolved + * references. + * + * @param draftBuilder the customer draft to resolve its store references. + * @return a {@link CompletionStage} that contains as a result a new builder instance with + * resolved references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Nonnull + public CompletionStage resolveStoreReferences( + @Nonnull final CustomerDraftBuilder draftBuilder) { + + final List> storeResourceIdentifiers = draftBuilder.getStores(); + if (storeResourceIdentifiers == null || storeResourceIdentifiers.isEmpty()) { + return completedFuture(draftBuilder); } - - /** - * Given a {@link CustomerDraftBuilder} this method attempts to resolve the customer group to return a - * {@link CompletionStage} which contains a new instance of the builder with the resolved customer group reference. - * - * @param draftBuilder the customerDraft to resolve its customer group reference. - * @return a {@link CompletionStage} that contains as a result a new builder instance with resolved customer group - * reference or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Nonnull - public CompletionStage resolveCustomerGroupReference( - @Nonnull final CustomerDraftBuilder draftBuilder) { - - final ResourceIdentifier customerGroupResourceIdentifier = draftBuilder.getCustomerGroup(); - if (customerGroupResourceIdentifier != null && customerGroupResourceIdentifier.getId() == null) { - String customerGroupKey; - try { - customerGroupKey = getKeyFromResourceIdentifier(customerGroupResourceIdentifier); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, draftBuilder.getKey(), + final List> resolvedReferences = new ArrayList<>(); + for (ResourceIdentifier storeResourceIdentifier : storeResourceIdentifiers) { + if (storeResourceIdentifier != null) { + if (storeResourceIdentifier.getId() == null) { + try { + final String storeKey = getKeyFromResourceIdentifier(storeResourceIdentifier); + resolvedReferences.add(ResourceIdentifier.ofKey(storeKey)); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_STORE_REFERENCE, + draftBuilder.getKey(), referenceResolutionException.getMessage()))); - } - - return fetchAndResolveCustomerGroupReference(draftBuilder, customerGroupKey); + } + } else { + resolvedReferences.add(ResourceIdentifier.ofId(storeResourceIdentifier.getId())); } - return completedFuture(draftBuilder); + } } - - @Nonnull - private CompletionStage fetchAndResolveCustomerGroupReference( - @Nonnull final CustomerDraftBuilder draftBuilder, - @Nonnull final String customerGroupKey) { - - return customerGroupService - .fetchCachedCustomerGroupId(customerGroupKey) - .thenCompose(resolvedCustomerGroupIdOptional -> resolvedCustomerGroupIdOptional - .map(resolvedCustomerGroupId -> - completedFuture(draftBuilder.customerGroup( - CustomerGroup.referenceOfId(resolvedCustomerGroupId).toResourceIdentifier()))) - .orElseGet(() -> { - final String errorMessage = format(CUSTOMER_GROUP_DOES_NOT_EXIST, customerGroupKey); - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, draftBuilder.getKey(), - errorMessage))); - })); + return completedFuture(draftBuilder.stores(resolvedReferences)); + } + + /** + * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys (i.e custom + * type, customer group) from the commercetools to populate caches for the reference resolution. + * + *

Note: This method is meant be only used internally by the library to improve performance. + * + * @param referencedKeys a wrapper for the product references to fetch and cache the id's for. + * @return {@link CompletionStage}<{@link Map}<{@link String}>{@link String}>> in + * which the results of it's completions contains a map of requested references keys -> ids + * of customer references. + */ + @Nonnull + public CompletableFuture>> populateKeyToIdCachesForReferencedKeys( + @Nonnull final CustomerBatchValidator.ReferencedKeys referencedKeys) { + + final List>> futures = new ArrayList<>(); + + final Set typeKeys = referencedKeys.getTypeKeys(); + if (!typeKeys.isEmpty()) { + futures.add(typeService.cacheKeysToIds(typeKeys)); } - /** - * Given a {@link CustomerDraftBuilder} this method attempts to resolve the stores and return - * a {@link CompletionStage} which contains a new instance of the builder with the resolved references. - * - * @param draftBuilder the customer draft to resolve its store references. - * @return a {@link CompletionStage} that contains as a result a new builder instance with resolved references or, - * in case an error occurs during reference resolution, a {@link ReferenceResolutionException}. - */ - @Nonnull - public CompletionStage resolveStoreReferences( - @Nonnull final CustomerDraftBuilder draftBuilder) { - - final List> storeResourceIdentifiers = draftBuilder.getStores(); - if (storeResourceIdentifiers == null || storeResourceIdentifiers.isEmpty()) { - return completedFuture(draftBuilder); - } - final List> resolvedReferences = new ArrayList<>(); - for (ResourceIdentifier storeResourceIdentifier : storeResourceIdentifiers) { - if (storeResourceIdentifier != null) { - if (storeResourceIdentifier.getId() == null) { - try { - final String storeKey = getKeyFromResourceIdentifier(storeResourceIdentifier); - resolvedReferences.add(ResourceIdentifier.ofKey(storeKey)); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture( - new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_STORE_REFERENCE, - draftBuilder.getKey(), referenceResolutionException.getMessage()))); - } - } else { - resolvedReferences.add(ResourceIdentifier.ofId(storeResourceIdentifier.getId())); - } - } - } - return completedFuture(draftBuilder.stores(resolvedReferences)); + final Set customerGroupKeys = referencedKeys.getCustomerGroupKeys(); + if (!customerGroupKeys.isEmpty()) { + futures.add(customerGroupService.cacheKeysToIds(customerGroupKeys)); } - /** - * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys - * (i.e custom type, customer group) from the commercetools to populate caches for the reference resolution. - * - *

Note: This method is meant be only used internally by the library to improve performance. - * - * @param referencedKeys a wrapper for the product references to fetch and cache the id's for. - * @return {@link CompletionStage}<{@link Map}<{@link String}>{@link String}>> in which the results - * of it's completions contains a map of requested references keys -> ids of customer references. - */ - @Nonnull - public CompletableFuture>> populateKeyToIdCachesForReferencedKeys( - @Nonnull final CustomerBatchValidator.ReferencedKeys referencedKeys) { - - final List>> futures = new ArrayList<>(); - - final Set typeKeys = referencedKeys.getTypeKeys(); - if (!typeKeys.isEmpty()) { - futures.add(typeService.cacheKeysToIds(typeKeys)); - } - - final Set customerGroupKeys = referencedKeys.getCustomerGroupKeys(); - if (!customerGroupKeys.isEmpty()) { - futures.add(customerGroupService.cacheKeysToIds(customerGroupKeys)); - } - - return collectionOfFuturesToFutureOfCollection(futures, toList()); - } + return collectionOfFuturesToFutureOfCollection(futures, toList()); + } } diff --git a/src/main/java/com/commercetools/sync/customers/helpers/CustomerSyncStatistics.java b/src/main/java/com/commercetools/sync/customers/helpers/CustomerSyncStatistics.java index c1a4fa93df..f99a8272cf 100644 --- a/src/main/java/com/commercetools/sync/customers/helpers/CustomerSyncStatistics.java +++ b/src/main/java/com/commercetools/sync/customers/helpers/CustomerSyncStatistics.java @@ -4,15 +4,17 @@ public class CustomerSyncStatistics extends BaseSyncStatistics { - /** - * Builds a summary of the customer sync statistics instance that looks like the following example: - * - *

"Summary: 2 customers have been processed in total (0 created, 0 updated and 0 failed to sync)." - * - * @return a summary message of the customer sync statistics instance. - */ - @Override - public String getReportMessage() { - return getDefaultReportMessageForResource("customers"); - } + /** + * Builds a summary of the customer sync statistics instance that looks like the following + * example: + * + *

"Summary: 2 customers have been processed in total (0 created, 0 updated and 0 failed to + * sync)." + * + * @return a summary message of the customer sync statistics instance. + */ + @Override + public String getReportMessage() { + return getDefaultReportMessageForResource("customers"); + } } diff --git a/src/main/java/com/commercetools/sync/customers/utils/CustomerCustomActionBuilder.java b/src/main/java/com/commercetools/sync/customers/utils/CustomerCustomActionBuilder.java index 7ccfe93184..1df4ea3c34 100644 --- a/src/main/java/com/commercetools/sync/customers/utils/CustomerCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/customers/utils/CustomerCustomActionBuilder.java @@ -6,52 +6,50 @@ import io.sphere.sdk.customers.Customer; import io.sphere.sdk.customers.commands.updateactions.SetCustomField; import io.sphere.sdk.customers.commands.updateactions.SetCustomType; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; public final class CustomerCustomActionBuilder implements GenericCustomActionBuilder { - private static final CustomerCustomActionBuilder builder = new CustomerCustomActionBuilder(); - - private CustomerCustomActionBuilder() { - super(); - } - - @Nonnull - public static CustomerCustomActionBuilder of() { - return builder; - } - - @Nonnull - @Override - public UpdateAction buildRemoveCustomTypeAction( - @Nullable final Integer variantId, - @Nullable final String objectId) { - - return SetCustomType.ofRemoveType(); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomTypeAction( - @Nullable final Integer variantId, - @Nullable final String objectId, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - - return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomFieldAction( - @Nullable final Integer variantId, - @Nullable final String objectId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - - return SetCustomField.ofJson(customFieldName, customFieldValue); - } + private static final CustomerCustomActionBuilder builder = new CustomerCustomActionBuilder(); + + private CustomerCustomActionBuilder() { + super(); + } + + @Nonnull + public static CustomerCustomActionBuilder of() { + return builder; + } + + @Nonnull + @Override + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String objectId) { + + return SetCustomType.ofRemoveType(); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + + return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + + return SetCustomField.ofJson(customFieldName, customFieldValue); + } } diff --git a/src/main/java/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtils.java index 1df8d20e7f..5ca0ccd4fe 100644 --- a/src/main/java/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtils.java @@ -1,5 +1,10 @@ package com.commercetools.sync.customers.utils; +import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; +import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; +import static java.util.stream.Collectors.toList; +import static org.apache.http.util.TextUtils.isBlank; + import io.sphere.sdk.customergroups.CustomerGroup; import io.sphere.sdk.customers.Customer; import io.sphere.sdk.customers.CustomerDraft; @@ -13,168 +18,161 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.stores.Store; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Set; - -import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; -import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; -import static java.util.stream.Collectors.toList; -import static org.apache.http.util.TextUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class CustomerReferenceResolutionUtils { - /** - * Returns a {@link List}<{@link CustomerDraft}> consisting of the results of applying the - * mapping from {@link Customer} to {@link CustomerDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
customerGroup{@link Reference}<{@link CustomerGroup}>{@link ResourceIdentifier}<{@link CustomerGroup}>
stores{@link Set}<{@link KeyReference}<{@link Store}>>{@link Set}<{@link ResourceIdentifier}<{@link Store}>>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
- * - *

Note: The {@link CustomerGroup} and {@link Type} references should be expanded with a key. - * Any reference that is not expanded will have its id in place and not replaced by the key will be - * considered as existing resources on the target commercetools project and - * the library will issues an update/create API request without reference resolution. - * - * @param customers the customers with expanded references. - * @return a {@link List} of {@link CustomerDraft} built from the supplied {@link List} of {@link Customer}. - */ - @Nonnull - public static List mapToCustomerDrafts( - @Nonnull final List customers) { - return customers - .stream() - .map(CustomerReferenceResolutionUtils::mapToCustomerDraft) - .collect(toList()); - } - - @Nonnull - private static CustomerDraft mapToCustomerDraft(@Nonnull final Customer customer) { - return CustomerDraftBuilder - .of(customer.getEmail(), customer.getPassword()) - .customerNumber(customer.getCustomerNumber()) - .key(customer.getKey()) - .firstName(customer.getFirstName()) - .lastName(customer.getLastName()) - .middleName(customer.getMiddleName()) - .title(customer.getTitle()) - .externalId(customer.getExternalId()) - .companyName(customer.getCompanyName()) - .customerGroup(getResourceIdentifierWithKey(customer.getCustomerGroup())) - .dateOfBirth(customer.getDateOfBirth()) - .isEmailVerified(customer.isEmailVerified()) - .vatId(customer.getVatId()) - .addresses(customer.getAddresses()) - .defaultBillingAddress(getAddressIndex(customer.getAddresses(), customer.getDefaultBillingAddressId())) - .billingAddresses(getAddressIndexList(customer.getAddresses(), customer.getBillingAddressIds())) - .defaultShippingAddress(getAddressIndex(customer.getAddresses(), customer.getDefaultShippingAddressId())) - .shippingAddresses(getAddressIndexList(customer.getAddresses(), customer.getShippingAddressIds())) - .custom(mapToCustomFieldsDraft(customer)) - .locale(customer.getLocale()) - .salutation(customer.getSalutation()) - .stores(mapToStores(customer)) - .build(); - } + /** + * Returns a {@link List}<{@link CustomerDraft}> consisting of the results of applying the + * mapping from {@link Customer} to {@link CustomerDraft} with considering reference resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
customerGroup{@link Reference}<{@link CustomerGroup}>{@link ResourceIdentifier}<{@link CustomerGroup}>
stores{@link Set}<{@link KeyReference}<{@link Store}>>{@link Set}<{@link ResourceIdentifier}<{@link Store}>>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
+ * + *

Note: The {@link CustomerGroup} and {@link Type} references should be expanded with a + * key. Any reference that is not expanded will have its id in place and not replaced by the key + * will be considered as existing resources on the target commercetools project and the library + * will issues an update/create API request without reference resolution. + * + * @param customers the customers with expanded references. + * @return a {@link List} of {@link CustomerDraft} built from the supplied {@link List} of {@link + * Customer}. + */ + @Nonnull + public static List mapToCustomerDrafts(@Nonnull final List customers) { + return customers.stream() + .map(CustomerReferenceResolutionUtils::mapToCustomerDraft) + .collect(toList()); + } + @Nonnull + private static CustomerDraft mapToCustomerDraft(@Nonnull final Customer customer) { + return CustomerDraftBuilder.of(customer.getEmail(), customer.getPassword()) + .customerNumber(customer.getCustomerNumber()) + .key(customer.getKey()) + .firstName(customer.getFirstName()) + .lastName(customer.getLastName()) + .middleName(customer.getMiddleName()) + .title(customer.getTitle()) + .externalId(customer.getExternalId()) + .companyName(customer.getCompanyName()) + .customerGroup(getResourceIdentifierWithKey(customer.getCustomerGroup())) + .dateOfBirth(customer.getDateOfBirth()) + .isEmailVerified(customer.isEmailVerified()) + .vatId(customer.getVatId()) + .addresses(customer.getAddresses()) + .defaultBillingAddress( + getAddressIndex(customer.getAddresses(), customer.getDefaultBillingAddressId())) + .billingAddresses( + getAddressIndexList(customer.getAddresses(), customer.getBillingAddressIds())) + .defaultShippingAddress( + getAddressIndex(customer.getAddresses(), customer.getDefaultShippingAddressId())) + .shippingAddresses( + getAddressIndexList(customer.getAddresses(), customer.getShippingAddressIds())) + .custom(mapToCustomFieldsDraft(customer)) + .locale(customer.getLocale()) + .salutation(customer.getSalutation()) + .stores(mapToStores(customer)) + .build(); + } - @Nullable - private static Integer getAddressIndex( - @Nullable final List

allAddresses, - @Nullable final String addressId) { + @Nullable + private static Integer getAddressIndex( + @Nullable final List
allAddresses, @Nullable final String addressId) { - if (allAddresses == null) { - return null; - } - if (isBlank(addressId)) { - return null; - } - for (int i = 0; i < allAddresses.size(); i++) { - String id = allAddresses.get(i).getId(); - if (id != null && id.equals(addressId)) { - return i; - } - } - return null; + if (allAddresses == null) { + return null; } - - @Nullable - private static List getAddressIndexList( - @Nullable final List
allAddresses, - @Nullable final List addressIds) { - if (allAddresses == null || addressIds == null) { - return null; - } - final List indexes = new ArrayList<>(); - for (String addressId : addressIds) { - indexes.add(getAddressIndex(allAddresses, addressId)); - } - return indexes; + if (isBlank(addressId)) { + return null; } - - @Nullable - private static List> mapToStores(@Nonnull final Customer customer) { - final List> storeReferences = customer.getStores(); - if (storeReferences != null) { - return storeReferences - .stream() - .map(storeKeyReference -> ResourceIdentifier.ofKey(storeKeyReference.getKey())) - .collect(toList()); - } - return null; + for (int i = 0; i < allAddresses.size(); i++) { + String id = allAddresses.get(i).getId(); + if (id != null && id.equals(addressId)) { + return i; + } } + return null; + } - /** - * Builds a {@link CustomerQuery} for fetching customers from a source CTP project with all the needed - * references expanded for the sync: - *
    - *
  • Custom Type
  • - *
  • CustomerGroup
  • - *
- * - *

Note: Please only use this util if you desire to sync all the aforementioned references from - * a source commercetools project. Otherwise, it is more efficient to build the query without expansions, if they - * are not needed, to avoid unnecessarily bigger payloads fetched from the source project. - * - * @return the query for fetching customers from the source CTP project with all the aforementioned references - * expanded. - */ - public static CustomerQuery buildCustomerQuery() { - return CustomerQuery.of() - .withExpansionPaths(CustomerExpansionModel::customerGroup) - .plusExpansionPaths(ExpansionPath.of("custom.type")); + @Nullable + private static List getAddressIndexList( + @Nullable final List

allAddresses, @Nullable final List addressIds) { + if (allAddresses == null || addressIds == null) { + return null; + } + final List indexes = new ArrayList<>(); + for (String addressId : addressIds) { + indexes.add(getAddressIndex(allAddresses, addressId)); } + return indexes; + } - private CustomerReferenceResolutionUtils() { + @Nullable + private static List> mapToStores(@Nonnull final Customer customer) { + final List> storeReferences = customer.getStores(); + if (storeReferences != null) { + return storeReferences.stream() + .map(storeKeyReference -> ResourceIdentifier.ofKey(storeKeyReference.getKey())) + .collect(toList()); } + return null; + } + + /** + * Builds a {@link CustomerQuery} for fetching customers from a source CTP project with all the + * needed references expanded for the sync: + * + *
    + *
  • Custom Type + *
  • CustomerGroup + *
+ * + *

Note: Please only use this util if you desire to sync all the aforementioned references from + * a source commercetools project. Otherwise, it is more efficient to build the query without + * expansions, if they are not needed, to avoid unnecessarily bigger payloads fetched from the + * source project. + * + * @return the query for fetching customers from the source CTP project with all the + * aforementioned references expanded. + */ + public static CustomerQuery buildCustomerQuery() { + return CustomerQuery.of() + .withExpansionPaths(CustomerExpansionModel::customerGroup) + .plusExpansionPaths(ExpansionPath.of("custom.type")); + } + + private CustomerReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/customers/utils/CustomerSyncUtils.java b/src/main/java/com/commercetools/sync/customers/utils/CustomerSyncUtils.java index 97ea1e287a..efc056de13 100644 --- a/src/main/java/com/commercetools/sync/customers/utils/CustomerSyncUtils.java +++ b/src/main/java/com/commercetools/sync/customers/utils/CustomerSyncUtils.java @@ -1,13 +1,5 @@ package com.commercetools.sync.customers.utils; -import com.commercetools.sync.customers.CustomerSyncOptions; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.customers.Customer; -import io.sphere.sdk.customers.CustomerDraft; - -import javax.annotation.Nonnull; -import java.util.List; - import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildPrimaryResourceCustomUpdateActions; import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildAllAddressUpdateActions; @@ -26,31 +18,40 @@ import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetVatIdUpdateAction; import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildStoreUpdateActions; +import com.commercetools.sync.customers.CustomerSyncOptions; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.customers.Customer; +import io.sphere.sdk.customers.CustomerDraft; +import java.util.List; +import javax.annotation.Nonnull; + public final class CustomerSyncUtils { - private static final CustomerCustomActionBuilder customerCustomActionBuilder = CustomerCustomActionBuilder.of(); + private static final CustomerCustomActionBuilder customerCustomActionBuilder = + CustomerCustomActionBuilder.of(); - /** - * Compares all the fields of a {@link Customer} and a {@link CustomerDraft}. It returns a {@link List} of - * {@link UpdateAction}<{@link Customer}> as a result. If no update action is needed, for example in - * case where both the {@link CustomerDraft} and the {@link CustomerDraft} have the same fields, an empty - * {@link List} is returned. - * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new data. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied - * by the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link CustomerSyncOptions} - * for more info. - * @return A list of customer specific update actions. - */ - @Nonnull - public static List> buildActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer, - @Nonnull final CustomerSyncOptions syncOptions) { + /** + * Compares all the fields of a {@link Customer} and a {@link CustomerDraft}. It returns a {@link + * List} of {@link UpdateAction}<{@link Customer}> as a result. If no update action is + * needed, for example in case where both the {@link CustomerDraft} and the {@link CustomerDraft} + * have the same fields, an empty {@link List} is returned. + * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new data. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * CustomerSyncOptions} for more info. + * @return A list of customer specific update actions. + */ + @Nonnull + public static List> buildActions( + @Nonnull final Customer oldCustomer, + @Nonnull final CustomerDraft newCustomer, + @Nonnull final CustomerSyncOptions syncOptions) { - final List> updateActions = filterEmptyOptionals( + final List> updateActions = + filterEmptyOptionals( buildChangeEmailUpdateAction(oldCustomer, newCustomer), buildSetFirstNameUpdateAction(oldCustomer, newCustomer), buildSetLastNameUpdateAction(oldCustomer, newCustomer), @@ -63,30 +64,26 @@ public static List> buildActions( buildSetCompanyNameUpdateAction(oldCustomer, newCustomer), buildSetDateOfBirthUpdateAction(oldCustomer, newCustomer), buildSetVatIdUpdateAction(oldCustomer, newCustomer), - buildSetLocaleUpdateAction(oldCustomer, newCustomer) - ); + buildSetLocaleUpdateAction(oldCustomer, newCustomer)); - final List> addressUpdateActions = - buildAllAddressUpdateActions(oldCustomer, newCustomer); + final List> addressUpdateActions = + buildAllAddressUpdateActions(oldCustomer, newCustomer); - updateActions.addAll(addressUpdateActions); + updateActions.addAll(addressUpdateActions); - final List> customerCustomUpdateActions = - buildPrimaryResourceCustomUpdateActions(oldCustomer, - newCustomer, - customerCustomActionBuilder, - syncOptions); + final List> customerCustomUpdateActions = + buildPrimaryResourceCustomUpdateActions( + oldCustomer, newCustomer, customerCustomActionBuilder, syncOptions); - updateActions.addAll(customerCustomUpdateActions); + updateActions.addAll(customerCustomUpdateActions); - final List> buildStoreUpdateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + final List> buildStoreUpdateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - updateActions.addAll(buildStoreUpdateActions); + updateActions.addAll(buildStoreUpdateActions); - return updateActions; - } + return updateActions; + } - private CustomerSyncUtils() { - } + private CustomerSyncUtils() {} } diff --git a/src/main/java/com/commercetools/sync/customers/utils/CustomerUpdateActionUtils.java b/src/main/java/com/commercetools/sync/customers/utils/CustomerUpdateActionUtils.java index 050ed22902..21cf5c70f9 100644 --- a/src/main/java/com/commercetools/sync/customers/utils/CustomerUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/customers/utils/CustomerUpdateActionUtils.java @@ -1,5 +1,15 @@ package com.commercetools.sync.customers.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.customers.CustomerSyncOptions; import io.sphere.sdk.commands.UpdateAction; @@ -38,9 +48,6 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.models.ResourceImpl; import io.sphere.sdk.stores.Store; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -49,1008 +56,1010 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class CustomerUpdateActionUtils { - public static final String CUSTOMER_NUMBER_EXISTS_WARNING = "Customer with key: \"%s\" has " - + "already a customer number: \"%s\", once it's set it cannot be changed. " - + "Hereby, the update action is not created."; - - private CustomerUpdateActionUtils() { + public static final String CUSTOMER_NUMBER_EXISTS_WARNING = + "Customer with key: \"%s\" has " + + "already a customer number: \"%s\", once it's set it cannot be changed. " + + "Hereby, the update action is not created."; + + private CustomerUpdateActionUtils() {} + + /** + * Compares the {@code email} values of a {@link Customer} and a {@link CustomerDraft} and returns + * an {@link Optional} of update action, which would contain the {@code "changeEmail"} {@link + * UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code email} + * values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new email. + * @return optional containing update action or empty optional if emails are identical. + */ + @Nonnull + public static Optional> buildChangeEmailUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getEmail(), + newCustomer.getEmail(), + () -> ChangeEmail.of(newCustomer.getEmail())); + } + + /** + * Compares the {@code firstName} values of a {@link Customer} and a {@link CustomerDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "setFirstName"} + * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code + * firstName} values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new first name. + * @return optional containing update action or empty optional if first names are identical. + */ + @Nonnull + public static Optional> buildSetFirstNameUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getFirstName(), + newCustomer.getFirstName(), + () -> SetFirstName.of(newCustomer.getFirstName())); + } + + /** + * Compares the {@code lastName} values of a {@link Customer} and a {@link CustomerDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "setLastName"} + * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code + * lastName} values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new last name. + * @return optional containing update action or empty optional if last names are identical. + */ + @Nonnull + public static Optional> buildSetLastNameUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getLastName(), + newCustomer.getLastName(), + () -> SetLastName.of(newCustomer.getLastName())); + } + + /** + * Compares the {@code middleName} values of a {@link Customer} and a {@link CustomerDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "setMiddleName"} + * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code + * middleName} values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new middle name. + * @return optional containing update action or empty optional if middle names are identical. + */ + @Nonnull + public static Optional> buildSetMiddleNameUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getMiddleName(), + newCustomer.getMiddleName(), + () -> SetMiddleName.of(newCustomer.getMiddleName())); + } + + /** + * Compares the {@code title} values of a {@link Customer} and a {@link CustomerDraft} and returns + * an {@link Optional} of update action, which would contain the {@code "setTitle"} {@link + * UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code title} + * values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new title. + * @return optional containing update action or empty optional if titles are identical. + */ + @Nonnull + public static Optional> buildSetTitleUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getTitle(), newCustomer.getTitle(), () -> SetTitle.of(newCustomer.getTitle())); + } + + /** + * Compares the {@code salutation} values of a {@link Customer} and a {@link CustomerDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "SetSalutation"} + * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code + * salutation} values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the Customer that should be updated. + * @param newCustomer the Customer draft that contains the new salutation. + * @return optional containing update action or empty optional if salutations are identical. + */ + @Nonnull + public static Optional> buildSetSalutationUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getSalutation(), + newCustomer.getSalutation(), + () -> SetSalutation.of(newCustomer.getSalutation())); + } + + /** + * Compares the {@code customerNumber} values of a {@link Customer} and a {@link CustomerDraft} + * and returns an {@link Optional} of update action, which would contain the {@code + * "setCustomerNumber"} {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} + * have the same {@code customerNumber} values, then no update action is needed and empty optional + * will be returned. + * + *

Note: Customer number should be unique across a project. Once it's set it cannot be changed. + * For this case, warning callback will be triggered and an empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new customer number. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the warning callback when trying to change an existing customer number. + * @return optional containing update action or empty optional if customer numbers are identical. + */ + @Nonnull + public static Optional> buildSetCustomerNumberUpdateAction( + @Nonnull final Customer oldCustomer, + @Nonnull final CustomerDraft newCustomer, + @Nonnull final CustomerSyncOptions syncOptions) { + + final Optional> setCustomerNumberAction = + buildUpdateAction( + oldCustomer.getCustomerNumber(), + newCustomer.getCustomerNumber(), + () -> SetCustomerNumber.of(newCustomer.getCustomerNumber())); + + if (setCustomerNumberAction.isPresent() && !isBlank(oldCustomer.getCustomerNumber())) { + + syncOptions.applyWarningCallback( + new SyncException( + format( + CUSTOMER_NUMBER_EXISTS_WARNING, + oldCustomer.getKey(), + oldCustomer.getCustomerNumber())), + oldCustomer, + newCustomer); + + return Optional.empty(); } - /** - * Compares the {@code email} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeEmail"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code email} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new email. - * @return optional containing update action or empty optional if emails are identical. - */ - @Nonnull - public static Optional> buildChangeEmailUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getEmail(), newCustomer.getEmail(), - () -> ChangeEmail.of(newCustomer.getEmail())); + return setCustomerNumberAction; + } + + /** + * Compares the {@code externalId} values of a {@link Customer} and a {@link CustomerDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "setExternalId"} + * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code + * externalId} values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new external id. + * @return optional containing update action or empty optional if external ids are identical. + */ + @Nonnull + public static Optional> buildSetExternalIdUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getExternalId(), + newCustomer.getExternalId(), + () -> SetExternalId.of(newCustomer.getExternalId())); + } + + /** + * Compares the {@code companyName} values of a {@link Customer} and a {@link CustomerDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "setCompanyName"} + * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code + * companyName} values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new company name. + * @return optional containing update action or empty optional if company names are identical. + */ + @Nonnull + public static Optional> buildSetCompanyNameUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getCompanyName(), + newCustomer.getCompanyName(), + () -> SetCompanyName.of(newCustomer.getCompanyName())); + } + + /** + * Compares the {@code dateOfBirth} values of a {@link Customer} and a {@link CustomerDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "setDateOfBirth"} + * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code + * dateOfBirth} values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new date of birth. + * @return optional containing update action or empty optional if dates of birth are identical. + */ + @Nonnull + public static Optional> buildSetDateOfBirthUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getDateOfBirth(), + newCustomer.getDateOfBirth(), + () -> SetDateOfBirth.of(newCustomer.getDateOfBirth())); + } + + /** + * Compares the {@code vatId} values of a {@link Customer} and a {@link CustomerDraft} and returns + * an {@link Optional} of update action, which would contain the {@code "setVatId"} {@link + * UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code vatId} + * values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new vat id. + * @return optional containing update action or empty optional if vat ids are identical. + */ + @Nonnull + public static Optional> buildSetVatIdUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getVatId(), newCustomer.getVatId(), () -> SetVatId.of(newCustomer.getVatId())); + } + + /** + * Compares the {@code locale} values of a {@link Customer} and a {@link CustomerDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "setLocale"} + * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same {@code + * locale} values, then no update action is needed and empty optional will be returned. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft that contains the new locale. + * @return optional containing update action or empty optional if locales are identical. + */ + @Nonnull + public static Optional> buildSetLocaleUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateAction( + oldCustomer.getLocale(), + newCustomer.getLocale(), + () -> SetLocale.of(newCustomer.getLocale())); + } + + /** + * Compares the {@link CustomerGroup} references of an old {@link Customer} and new {@link + * CustomerDraft}. If they are different - return {@link SetCustomerGroup} update action. + * + *

If the old value is set, but the new one is empty - the command will unset the customer + * group. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft with new {@link CustomerGroup} reference. + * @return An optional with {@link SetCustomerGroup} update action. + */ + @Nonnull + public static Optional> buildSetCustomerGroupUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + return buildUpdateActionForReferences( + oldCustomer.getCustomerGroup(), + newCustomer.getCustomerGroup(), + () -> + SetCustomerGroup.of( + mapResourceIdentifierToReferenceable(newCustomer.getCustomerGroup()))); + } + + @Nullable + private static Referenceable mapResourceIdentifierToReferenceable( + @Nullable final ResourceIdentifier resourceIdentifier) { + + if (resourceIdentifier == null) { + return null; // unset } - /** - * Compares the {@code firstName} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setFirstName"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code firstName} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new first name. - * @return optional containing update action or empty optional if first names are identical. - */ - @Nonnull - public static Optional> buildSetFirstNameUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getFirstName(), newCustomer.getFirstName(), - () -> SetFirstName.of(newCustomer.getFirstName())); - } + // TODO (JVM-SDK), see: SUPPORT-10261 SetCustomerGroup could be created with a + // ResourceIdentifier + // https://github.com/commercetools/commercetools-jvm-sdk/issues/2072 + return new ResourceImpl(null, null, null, null) { + @Override + public Reference toReference() { + return Reference.of(CustomerGroup.referenceTypeId(), resourceIdentifier.getId()); + } + }; + } + + /** + * Compares the stores of a {@link Customer} and a {@link CustomerDraft}. It returns a {@link + * List} of {@link UpdateAction}<{@link Customer}> as a result. If no update action is + * needed, for example in case where both the {@link Customer} and the {@link CustomerDraft} have + * the identical stores, an empty {@link List} is returned. + * + *

Note: Null values of the stores are filtered out. + * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new data. + * @return A list of customer store-related update actions. + */ + @Nonnull + public static List> buildStoreUpdateActions( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + final List> oldStores = oldCustomer.getStores(); + final List> newStores = newCustomer.getStores(); + + return buildSetStoreUpdateAction(oldStores, newStores) + .map(Collections::singletonList) + .orElseGet(() -> prepareStoreActions(oldStores, newStores)); + } + + private static List> prepareStoreActions( + @Nullable final List> oldStores, + @Nullable final List> newStores) { + + if (oldStores != null && newStores != null) { + final List> removeStoreUpdateActions = + buildRemoveStoreUpdateActions(oldStores, newStores); + + final List> addStoreUpdateActions = + buildAddStoreUpdateActions(oldStores, newStores); + + if (!removeStoreUpdateActions.isEmpty() && !addStoreUpdateActions.isEmpty()) { + return buildSetStoreUpdateAction(newStores) + .map(Collections::singletonList) + .orElseGet(Collections::emptyList); + } - /** - * Compares the {@code lastName} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setLastName"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code lastName} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new last name. - * @return optional containing update action or empty optional if last names are identical. - */ - @Nonnull - public static Optional> buildSetLastNameUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getLastName(), newCustomer.getLastName(), - () -> SetLastName.of(newCustomer.getLastName())); + return removeStoreUpdateActions.isEmpty() ? addStoreUpdateActions : removeStoreUpdateActions; } - /** - * Compares the {@code middleName} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setMiddleName"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code middleName} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new middle name. - * @return optional containing update action or empty optional if middle names are identical. - */ - @Nonnull - public static Optional> buildSetMiddleNameUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getMiddleName(), newCustomer.getMiddleName(), - () -> SetMiddleName.of(newCustomer.getMiddleName())); + return emptyList(); + } + + /** + * Compares the {@link List} of {@link Store} {@link KeyReference}s and {@link Store} {@link + * ResourceIdentifier}s of a {@link CustomerDraft} and a {@link Customer}. It returns a {@link + * SetStores} update action as a result. If both the {@link Customer} and the {@link + * CustomerDraft} have the same set of stores, then no update actions are needed and hence an + * empty {@link List} is returned. + * + *

Note: Null values of the stores are filtered out. + * + * @param oldStores the stores which should be updated. + * @param newStores the stores where we get the new store. + * @return A list containing the update actions or an empty list if the store references are + * identical. + */ + @Nonnull + private static Optional> buildSetStoreUpdateAction( + @Nullable final List> oldStores, + @Nullable final List> newStores) { + + if (oldStores != null && !oldStores.isEmpty()) { + if (newStores == null || newStores.isEmpty()) { + return Optional.of(SetStores.of(emptyList())); + } + } else if (newStores != null && !newStores.isEmpty()) { + return buildSetStoreUpdateAction(newStores); } - /** - * Compares the {@code title} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setTitle"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code title} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new title. - * @return optional containing update action or empty optional if titles are identical. - */ - @Nonnull - public static Optional> buildSetTitleUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getTitle(), newCustomer.getTitle(), - () -> SetTitle.of(newCustomer.getTitle())); - } + return Optional.empty(); + } - /** - * Compares the {@code salutation} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "SetSalutation"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code salutation} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the Customer that should be updated. - * @param newCustomer the Customer draft that contains the new salutation. - * @return optional containing update action or empty optional if salutations are identical. - */ - @Nonnull - public static Optional> buildSetSalutationUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getSalutation(), newCustomer.getSalutation(), - () -> SetSalutation.of(newCustomer.getSalutation())); - } + private static Optional> buildSetStoreUpdateAction( + @Nonnull final List> newStores) { - /** - * Compares the {@code customerNumber} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setCustomerNumber"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code customerNumber} values, then no update action is needed and empty optional will be returned. - * - *

Note: Customer number should be unique across a project. Once it's set it cannot be changed. For this case, - * warning callback will be triggered and an empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new customer number. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the warning callback when trying to change an existing customer number. - * @return optional containing update action or empty optional if customer numbers are identical. - */ - @Nonnull - public static Optional> buildSetCustomerNumberUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer, - @Nonnull final CustomerSyncOptions syncOptions) { - - final Optional> setCustomerNumberAction = - buildUpdateAction(oldCustomer.getCustomerNumber(), newCustomer.getCustomerNumber(), - () -> SetCustomerNumber.of(newCustomer.getCustomerNumber())); - - if (setCustomerNumberAction.isPresent() && !isBlank(oldCustomer.getCustomerNumber())) { - - syncOptions.applyWarningCallback( - new SyncException(format(CUSTOMER_NUMBER_EXISTS_WARNING, oldCustomer.getKey(), - oldCustomer.getCustomerNumber())), oldCustomer, newCustomer); - - return Optional.empty(); - } - - return setCustomerNumberAction; - } + final List> stores = + newStores.stream().filter(Objects::nonNull).collect(toList()); - /** - * Compares the {@code externalId} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setExternalId"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code externalId} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new external id. - * @return optional containing update action or empty optional if external ids are identical. - */ - @Nonnull - public static Optional> buildSetExternalIdUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getExternalId(), newCustomer.getExternalId(), - () -> SetExternalId.of(newCustomer.getExternalId())); + if (!stores.isEmpty()) { + return Optional.of(SetStores.of(stores)); } - /** - * Compares the {@code companyName} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setCompanyName"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code companyName} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new company name. - * @return optional containing update action or empty optional if company names are identical. - */ - @Nonnull - public static Optional> buildSetCompanyNameUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getCompanyName(), newCustomer.getCompanyName(), - () -> SetCompanyName.of(newCustomer.getCompanyName())); - } + return Optional.empty(); + } + + /** + * Compares the {@link List} of {@link Store} {@link KeyReference}s and {@link Store} {@link + * ResourceIdentifier}s of a {@link CustomerDraft} and a {@link Customer}. It returns a {@link + * List} of {@link RemoveStore} update actions as a result, if the old store needs to be removed + * from a customer to have the same set of stores as the new customer. If both the {@link + * Customer} and the {@link CustomerDraft} have the same set of stores, then no update actions are + * needed and hence an empty {@link List} is returned. + * + *

Note: Null values of the stores are filtered out. + * + * @param oldStores the stores which should be updated. + * @param newStores the stores where we get the new store. + * @return A list containing the update actions or an empty list if the store references are + * identical. + */ + @Nonnull + public static List> buildRemoveStoreUpdateActions( + @Nonnull final List> oldStores, + @Nonnull final List> newStores) { + + final Map> newStoreKeyToStoreMap = + newStores.stream() + .filter(Objects::nonNull) + .filter(storeResourceIdentifier -> storeResourceIdentifier.getKey() != null) + .collect(toMap(ResourceIdentifier::getKey, identity())); + + return oldStores.stream() + .filter(Objects::nonNull) + .filter(storeKeyReference -> !newStoreKeyToStoreMap.containsKey(storeKeyReference.getKey())) + .map( + storeKeyReference -> + RemoveStore.of(ResourceIdentifier.ofKey(storeKeyReference.getKey()))) + .collect(toList()); + } + + /** + * Compares the {@link List} of {@link Store} {@link KeyReference}s and {@link Store} {@link + * ResourceIdentifier}s of a {@link CustomerDraft} and a {@link Customer}. It returns a {@link + * List} of {@link AddStore} update actions as a result, if the old store needs to be added to a + * customer to have the same set of stores as the new customer. If both the {@link Customer} and + * the {@link CustomerDraft} have the same set of stores, then no update actions are needed and + * hence an empty {@link List} is returned. + * + *

Note: Null values of the stores are filtered out. + * + * @param oldStores the stores which should be updated. + * @param newStores the stores where we get the new store. + * @return A list containing the update actions or an empty list if the store references are + * identical. + */ + @Nonnull + public static List> buildAddStoreUpdateActions( + @Nonnull final List> oldStores, + @Nonnull final List> newStores) { + + final Map> oldStoreKeyToStoreMap = + oldStores.stream() + .filter(Objects::nonNull) + .collect(toMap(KeyReference::getKey, identity())); + + return newStores.stream() + .filter(Objects::nonNull) + .filter( + storeResourceIdentifier -> + !oldStoreKeyToStoreMap.containsKey(storeResourceIdentifier.getKey())) + .map(AddStore::of) + .collect(toList()); + } + + /** + * Compares the addresses of a {@link Customer} and a {@link CustomerDraft}. It returns a {@link + * List} of {@link UpdateAction}<{@link Customer}> as a result. If both the {@link Customer} + * and the {@link CustomerDraft} have the same set of addresses, then no update actions are needed + * and hence an empty {@link List} is returned. + * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new data. + * @return A list of customer address-related update actions. + */ + @Nonnull + public static List> buildAllAddressUpdateActions( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + final List> addressActions = new ArrayList<>(); + + final List> removeAddressActions = + buildRemoveAddressUpdateActions(oldCustomer, newCustomer); + + addressActions.addAll(removeAddressActions); + addressActions.addAll(buildChangeAddressUpdateActions(oldCustomer, newCustomer)); + addressActions.addAll(buildAddAddressUpdateActions(oldCustomer, newCustomer)); + + addressActions.addAll( + collectAndFilterRemoveShippingAndBillingActions( + removeAddressActions, oldCustomer, newCustomer)); + + buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer) + .ifPresent(addressActions::add); + buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer) + .ifPresent(addressActions::add); + + addressActions.addAll(buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)); + addressActions.addAll(buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)); + + return addressActions; + } + + @Nonnull + private static List> collectAndFilterRemoveShippingAndBillingActions( + @Nonnull final List> removeAddressActions, + @Nonnull final Customer oldCustomer, + @Nonnull final CustomerDraft newCustomer) { + + /* An action combination like below will cause a bad request error in API, so we need to filter out + to avoid such cases: + + { + "version": 1, + "actions": [ + { + "action" : "removeAddress", + "addressId": "-FWSGZNy" + }, + { + "action" : "removeBillingAddressId", + "addressId" : "-FWSGZNy" + } + ] + } + + { + "statusCode": 400, + "message": "The customers billingAddressIds don't contain id '-FWSGZNy'.", + "errors": [ + { + "code": "InvalidOperation", + "message": "The customers billingAddressIds don't contain id '-FWSGZNy'.", + "action": { + "action": "removeBillingAddressId", + "addressId": "-FWSGZNy" + }, + "actionIndex": 2 + } + ] + } + */ + final Set addressIdsToRemove = + removeAddressActions.stream() + .map(customerUpdateAction -> (RemoveAddress) customerUpdateAction) + .map(RemoveAddress::getAddressId) + .collect(toSet()); + + final List> removeActions = new ArrayList<>(); + + removeActions.addAll( + buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer).stream() + .map(customerUpdateAction -> (RemoveShippingAddressId) customerUpdateAction) + .filter(action -> !addressIdsToRemove.contains(action.getAddressId())) + .collect(toList())); - /** - * Compares the {@code dateOfBirth} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setDateOfBirth"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code dateOfBirth} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new date of birth. - * @return optional containing update action or empty optional if dates of birth are identical. - */ - @Nonnull - public static Optional> buildSetDateOfBirthUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getDateOfBirth(), newCustomer.getDateOfBirth(), - () -> SetDateOfBirth.of(newCustomer.getDateOfBirth())); - } + removeActions.addAll( + buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer).stream() + .map(customerUpdateAction -> (RemoveBillingAddressId) customerUpdateAction) + .filter(action -> !addressIdsToRemove.contains(action.getAddressId())) + .collect(toList())); - /** - * Compares the {@code vatId} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setVatId"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code vatId} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new vat id. - * @return optional containing update action or empty optional if vat ids are identical. - */ - @Nonnull - public static Optional> buildSetVatIdUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getVatId(), newCustomer.getVatId(), - () -> SetVatId.of(newCustomer.getVatId())); + return removeActions; + } + + /** + * Compares the {@link List} of a {@link CustomerDraft#getAddresses()} and a {@link + * Customer#getAddresses()}. It returns a {@link List} of {@link RemoveAddress} update actions as + * a result, if the old address needs to be removed from the {@code oldCustomer} to have the same + * set of addresses as the {@code newCustomer}. If both the {@link Customer} and the {@link + * CustomerDraft} have the same set of addresses, then no update actions are needed and hence an + * empty {@link List} is returned. + * + *

Notes: + * + *

    + *
  • Addresses are matching by their keys. + *
  • Null values of the new addresses are filtered out. + *
  • Address values without keys are filtered out. + *
+ * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new addresses. + * @return A list containing the update actions or an empty list if the addresses are identical. + */ + @Nonnull + public static List> buildRemoveAddressUpdateActions( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + if (oldCustomer.getAddresses().isEmpty()) { + return Collections.emptyList(); } - /** - * Compares the {@code locale} values of a {@link Customer} and a {@link CustomerDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setLocale"} - * {@link UpdateAction}. If both {@link Customer} and {@link CustomerDraft} have the same - * {@code locale} values, then no update action is needed and empty optional will be returned. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft that contains the new locale. - * @return optional containing update action or empty optional if locales are identical. - */ - @Nonnull - public static Optional> buildSetLocaleUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateAction(oldCustomer.getLocale(), newCustomer.getLocale(), - () -> SetLocale.of(newCustomer.getLocale())); - } + if (newCustomer.getAddresses() == null || newCustomer.getAddresses().isEmpty()) { - /** - * Compares the {@link CustomerGroup} references of an old {@link Customer} and - * new {@link CustomerDraft}. If they are different - return {@link SetCustomerGroup} update action. - * - *

If the old value is set, but the new one is empty - the command will unset the customer group. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft with new {@link CustomerGroup} reference. - * @return An optional with {@link SetCustomerGroup} update action. - */ - @Nonnull - public static Optional> buildSetCustomerGroupUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - return buildUpdateActionForReferences(oldCustomer.getCustomerGroup(), newCustomer.getCustomerGroup(), - () -> SetCustomerGroup.of(mapResourceIdentifierToReferenceable(newCustomer.getCustomerGroup()))); + return oldCustomer.getAddresses().stream() + .map(address -> RemoveAddress.of(address.getId())) + .collect(Collectors.toList()); } - @Nullable - private static Referenceable mapResourceIdentifierToReferenceable( - @Nullable final ResourceIdentifier resourceIdentifier) { - - if (resourceIdentifier == null) { - return null; // unset - } - - // TODO (JVM-SDK), see: SUPPORT-10261 SetCustomerGroup could be created with a ResourceIdentifier - // https://github.com/commercetools/commercetools-jvm-sdk/issues/2072 - return new ResourceImpl(null, null, null, null) { - @Override - public Reference toReference() { - return Reference.of(CustomerGroup.referenceTypeId(), resourceIdentifier.getId()); - } - }; + final Set newAddressKeys = + newCustomer.getAddresses().stream() + .filter(Objects::nonNull) + .filter(newAddress -> !isBlank(newAddress.getKey())) + .map(Address::getKey) + .collect(toSet()); + + return oldCustomer.getAddresses().stream() + .filter( + oldAddress -> + isBlank(oldAddress.getKey()) || !newAddressKeys.contains(oldAddress.getKey())) + .map(RemoveAddress::of) + .collect(toList()); + } + + /** + * Compares the {@link List} of a {@link CustomerDraft#getAddresses()} and a {@link + * Customer#getAddresses()}. It returns a {@link List} of {@link ChangeAddress} update actions as + * a result, if the old address needs to be changed/updated from the {@code oldCustomer} to have + * the same set of addresses as the {@code newCustomer}. If both the {@link Customer} and the + * {@link CustomerDraft} have the same set of addresses, then no update actions are needed and + * hence an empty {@link List} is returned. + * + *

Notes: + * + *

    + *
  • Addresses are matching by their keys. + *
  • Null values of the new addresses are filtered out. + *
  • Address values without keys are filtered out. + *
+ * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new addresses. + * @return A list containing the update actions or an empty list if the addresses are identical. + */ + @Nonnull + public static List> buildChangeAddressUpdateActions( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + if (newCustomer.getAddresses() == null || newCustomer.getAddresses().isEmpty()) { + return Collections.emptyList(); } - /** - * Compares the stores of a {@link Customer} and a {@link CustomerDraft}. It returns a {@link List} of - * {@link UpdateAction}<{@link Customer}> as a result. If no update action is needed, for example in - * case where both the {@link Customer} and the {@link CustomerDraft} have the identical stores, an empty - * {@link List} is returned. - * - *

Note: Null values of the stores are filtered out. - * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new data. - * @return A list of customer store-related update actions. - */ - @Nonnull - public static List> buildStoreUpdateActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - final List> oldStores = oldCustomer.getStores(); - final List> newStores = newCustomer.getStores(); - - return buildSetStoreUpdateAction(oldStores, newStores) - .map(Collections::singletonList) - .orElseGet(() -> prepareStoreActions(oldStores, newStores)); + final Map oldAddressKeyToAddressMap = + oldCustomer.getAddresses().stream() + .filter(address -> !isBlank(address.getKey())) + .collect(toMap(Address::getKey, identity())); + + return newCustomer.getAddresses().stream() + .filter(Objects::nonNull) + .filter(newAddress -> !isBlank(newAddress.getKey())) + .filter(newAddress -> oldAddressKeyToAddressMap.containsKey(newAddress.getKey())) + .map( + newAddress -> { + final Address oldAddress = oldAddressKeyToAddressMap.get(newAddress.getKey()); + if (!newAddress.equalsIgnoreId(oldAddress)) { + return ChangeAddress.of(oldAddress.getId(), newAddress); + } + return null; + }) + .filter(Objects::nonNull) + .collect(toList()); + } + + /** + * Compares the {@link List} of a {@link CustomerDraft#getAddresses()} and a {@link + * Customer#getAddresses()}. It returns a {@link List} of {@link AddAddress} update actions as a + * result, if the new address needs to be added to have the same set of addresses as the {@code + * newCustomer}. If both the {@link Customer} and the {@link CustomerDraft} have the same set of + * addresses, then no update actions are needed and hence an empty {@link List} is returned. + * + *

Notes: + * + *

    + *
  • Addresses are matching by their keys. + *
  • Null values of the new addresses are filtered out. + *
  • Address values without keys are filtered out. + *
+ * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new addresses. + * @return A list containing the update actions or an empty list if the addresses are identical. + */ + @Nonnull + public static List> buildAddAddressUpdateActions( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + if (newCustomer.getAddresses() == null || newCustomer.getAddresses().isEmpty()) { + return Collections.emptyList(); } - private static List> prepareStoreActions( - @Nullable final List> oldStores, - @Nullable final List> newStores) { - - if (oldStores != null && newStores != null) { - final List> removeStoreUpdateActions = - buildRemoveStoreUpdateActions(oldStores, newStores); - - final List> addStoreUpdateActions = - buildAddStoreUpdateActions(oldStores, newStores); - - if (!removeStoreUpdateActions.isEmpty() && !addStoreUpdateActions.isEmpty()) { - return buildSetStoreUpdateAction(newStores) - .map(Collections::singletonList) - .orElseGet(Collections::emptyList); - } - - return removeStoreUpdateActions.isEmpty() ? addStoreUpdateActions : removeStoreUpdateActions; - } - - return emptyList(); + final Map oldAddressKeyToAddressMap = + oldCustomer.getAddresses().stream() + .filter(address -> !isBlank(address.getKey())) + .collect(toMap(Address::getKey, identity())); + + return newCustomer.getAddresses().stream() + .filter(Objects::nonNull) + .filter(newAddress -> !isBlank(newAddress.getKey())) + .filter(newAddress -> !oldAddressKeyToAddressMap.containsKey(newAddress.getKey())) + .map(AddAddress::of) + .collect(toList()); + } + + /** + * Compares the {@link Customer#getDefaultShippingAddress()} and {@link + * CustomerDraft#getDefaultShippingAddress()}. If they are different - return {@link + * SetDefaultShippingAddress} update action. If the old shipping address is set, but the new one + * is empty - the command will unset the default shipping address. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft with new default shipping address. + * @return An optional with {@link SetDefaultShippingAddress} update action. + */ + @Nonnull + public static Optional> buildSetDefaultShippingAddressUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + final Address oldAddress = oldCustomer.getDefaultShippingAddress(); + final String newAddressKey = + getAddressKeyAt(newCustomer.getAddresses(), newCustomer.getDefaultShippingAddress()); + + if (newAddressKey != null) { + if (oldAddress == null || !Objects.equals(oldAddress.getKey(), newAddressKey)) { + return Optional.of(SetDefaultShippingAddress.ofKey(newAddressKey)); + } + } else if (oldAddress != null) { // unset + return Optional.of(SetDefaultShippingAddress.ofKey(null)); } - /** - * Compares the {@link List} of {@link Store} {@link KeyReference}s and {@link Store} {@link ResourceIdentifier}s - * of a {@link CustomerDraft} and a {@link Customer}. It returns a {@link SetStores} update action as a result. - * If both the {@link Customer} and the {@link CustomerDraft} have the same set of stores, then no update actions - * are needed and hence an empty {@link List} is returned. - * - *

Note: Null values of the stores are filtered out. - * - * @param oldStores the stores which should be updated. - * @param newStores the stores where we get the new store. - * @return A list containing the update actions or an empty list if the store references are identical. - */ - @Nonnull - private static Optional> buildSetStoreUpdateAction( - @Nullable final List> oldStores, - @Nullable final List> newStores) { - - if (oldStores != null && !oldStores.isEmpty()) { - if (newStores == null || newStores.isEmpty()) { - return Optional.of(SetStores.of(emptyList())); - } - } else if (newStores != null && !newStores.isEmpty()) { - return buildSetStoreUpdateAction(newStores); - } - - return Optional.empty(); + return Optional.empty(); + } + + /** + * Compares the {@link Customer#getDefaultBillingAddress()} and {@link + * CustomerDraft#getDefaultBillingAddress()}. If they are different - return {@link + * SetDefaultBillingAddress} update action. If the old billing address id value is set, but the + * new one is empty - the command will unset the default billing address. + * + * @param oldCustomer the customer that should be updated. + * @param newCustomer the customer draft with new default billing address. + * @return An optional with {@link SetDefaultBillingAddress} update action. + */ + @Nonnull + public static Optional> buildSetDefaultBillingAddressUpdateAction( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + final Address oldAddress = oldCustomer.getDefaultBillingAddress(); + final String newAddressKey = + getAddressKeyAt(newCustomer.getAddresses(), newCustomer.getDefaultBillingAddress()); + + if (newAddressKey != null) { + if (oldAddress == null || !Objects.equals(oldAddress.getKey(), newAddressKey)) { + return Optional.of(SetDefaultBillingAddress.ofKey(newAddressKey)); + } + } else if (oldAddress != null) { // unset + return Optional.of(SetDefaultBillingAddress.ofKey(null)); } - private static Optional> buildSetStoreUpdateAction( - @Nonnull final List> newStores) { - - final List> stores = - newStores.stream() - .filter(Objects::nonNull) - .collect(toList()); + return Optional.empty(); + } - if (!stores.isEmpty()) { - return Optional.of(SetStores.of(stores)); - } - - return Optional.empty(); - } + @Nullable + private static String getAddressKeyAt( + @Nullable final List

addressList, @Nullable final Integer index) { - /** - * Compares the {@link List} of {@link Store} {@link KeyReference}s and {@link Store} {@link ResourceIdentifier}s - * of a {@link CustomerDraft} and a {@link Customer}. It returns a {@link List} of {@link RemoveStore} update - * actions as a result, if the old store needs to be removed from a customer to have the same set of stores as - * the new customer. If both the {@link Customer} and the {@link CustomerDraft} have the same set of stores, - * then no update actions are needed and hence an empty {@link List} is returned. - * - *

Note: Null values of the stores are filtered out. - * - * @param oldStores the stores which should be updated. - * @param newStores the stores where we get the new store. - * @return A list containing the update actions or an empty list if the store references are identical. - */ - @Nonnull - public static List> buildRemoveStoreUpdateActions( - @Nonnull final List> oldStores, - @Nonnull final List> newStores) { - - final Map> newStoreKeyToStoreMap = - newStores.stream() - .filter(Objects::nonNull) - .filter(storeResourceIdentifier -> storeResourceIdentifier.getKey() != null) - .collect(toMap(ResourceIdentifier::getKey, identity())); - - return oldStores - .stream() - .filter(Objects::nonNull) - .filter(storeKeyReference -> !newStoreKeyToStoreMap.containsKey(storeKeyReference.getKey())) - .map(storeKeyReference -> RemoveStore.of(ResourceIdentifier.ofKey(storeKeyReference.getKey()))) - .collect(toList()); - } - - /** - * Compares the {@link List} of {@link Store} {@link KeyReference}s and {@link Store} {@link ResourceIdentifier}s - * of a {@link CustomerDraft} and a {@link Customer}. It returns a {@link List} of {@link AddStore} update actions - * as a result, if the old store needs to be added to a customer to have the same set of stores as the new customer. - * If both the {@link Customer} and the {@link CustomerDraft} have the same set of stores, then no update actions - * are needed and hence an empty {@link List} is returned. - * - *

Note: Null values of the stores are filtered out. - * - * @param oldStores the stores which should be updated. - * @param newStores the stores where we get the new store. - * @return A list containing the update actions or an empty list if the store references are identical. - */ - @Nonnull - public static List> buildAddStoreUpdateActions( - @Nonnull final List> oldStores, - @Nonnull final List> newStores) { - - final Map> oldStoreKeyToStoreMap = - oldStores.stream() - .filter(Objects::nonNull) - .collect(toMap(KeyReference::getKey, identity())); - - return newStores - .stream() - .filter(Objects::nonNull) - .filter(storeResourceIdentifier -> !oldStoreKeyToStoreMap.containsKey(storeResourceIdentifier.getKey())) - .map(AddStore::of) - .collect(toList()); + if (index == null) { + return null; } - /** - * Compares the addresses of a {@link Customer} and a {@link CustomerDraft}. It returns a {@link List} of - * {@link UpdateAction}<{@link Customer}> as a result. If both the {@link Customer} and the - * {@link CustomerDraft} have the same set of addresses, then no update actions are needed and hence an empty - * {@link List} is returned. - * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new data. - * @return A list of customer address-related update actions. - */ - @Nonnull - public static List> buildAllAddressUpdateActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - final List> addressActions = new ArrayList<>(); - - final List> removeAddressActions = - buildRemoveAddressUpdateActions(oldCustomer, newCustomer); - - addressActions.addAll(removeAddressActions); - addressActions.addAll(buildChangeAddressUpdateActions(oldCustomer, newCustomer)); - addressActions.addAll(buildAddAddressUpdateActions(oldCustomer, newCustomer)); - - addressActions.addAll( - collectAndFilterRemoveShippingAndBillingActions(removeAddressActions, oldCustomer, newCustomer)); - - buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer).ifPresent(addressActions::add); - buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer).ifPresent(addressActions::add); - - addressActions.addAll(buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)); - addressActions.addAll(buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)); - - return addressActions; + if (addressList == null || index < 0 || index >= addressList.size()) { + throw new IllegalArgumentException( + format("Addresses list does not contain an address at the index: %s", index)); } - @Nonnull - private static List> collectAndFilterRemoveShippingAndBillingActions( - @Nonnull final List> removeAddressActions, - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - /* An action combination like below will cause a bad request error in API, so we need to filter out - to avoid such cases: - - { - "version": 1, - "actions": [ - { - "action" : "removeAddress", - "addressId": "-FWSGZNy" - }, - { - "action" : "removeBillingAddressId", - "addressId" : "-FWSGZNy" - } - ] - } - - { - "statusCode": 400, - "message": "The customers billingAddressIds don't contain id '-FWSGZNy'.", - "errors": [ - { - "code": "InvalidOperation", - "message": "The customers billingAddressIds don't contain id '-FWSGZNy'.", - "action": { - "action": "removeBillingAddressId", - "addressId": "-FWSGZNy" - }, - "actionIndex": 2 - } - ] - } - */ - final Set addressIdsToRemove = - removeAddressActions.stream() - .map(customerUpdateAction -> (RemoveAddress)customerUpdateAction) - .map(RemoveAddress::getAddressId) - .collect(toSet()); - - - final List> removeActions = new ArrayList<>(); - - removeActions.addAll(buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer) - .stream() - .map(customerUpdateAction -> (RemoveShippingAddressId)customerUpdateAction) - .filter(action -> !addressIdsToRemove.contains(action.getAddressId())) - .collect(toList())); - - removeActions.addAll(buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer) - .stream() - .map(customerUpdateAction -> (RemoveBillingAddressId)customerUpdateAction) - .filter(action -> !addressIdsToRemove.contains(action.getAddressId())) - .collect(toList())); - - return removeActions; + final Address address = addressList.get(index); + if (address == null) { + throw new IllegalArgumentException( + format("Address is null at the index: %s of the addresses list.", index)); + } else if (isBlank(address.getKey())) { + throw new IllegalArgumentException( + format("Address does not have a key at the index: %s of the addresses list.", index)); + } else { + return address.getKey(); } - - /** - * Compares the {@link List} of a {@link CustomerDraft#getAddresses()} and a {@link Customer#getAddresses()}. - * It returns a {@link List} of {@link RemoveAddress} update actions as a result, if the old address needs to be - * removed from the {@code oldCustomer} to have the same set of addresses as the {@code newCustomer}. - * If both the {@link Customer} and the {@link CustomerDraft} have the same set of addresses, - * then no update actions are needed and hence an empty {@link List} is returned. - * - *

Notes: - *

    - *
  • Addresses are matching by their keys.
  • - *
  • Null values of the new addresses are filtered out.
  • - *
  • Address values without keys are filtered out.
  • - *
- * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new addresses. - * @return A list containing the update actions or an empty list if the addresses are identical. - */ - @Nonnull - public static List> buildRemoveAddressUpdateActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - if (oldCustomer.getAddresses().isEmpty()) { - return Collections.emptyList(); - } - - if (newCustomer.getAddresses() == null || newCustomer.getAddresses().isEmpty()) { - - return oldCustomer.getAddresses() - .stream() - .map(address -> RemoveAddress.of(address.getId())) - .collect(Collectors.toList()); - } - - final Set newAddressKeys = - newCustomer.getAddresses() - .stream() - .filter(Objects::nonNull) - .filter(newAddress -> !isBlank(newAddress.getKey())) - .map(Address::getKey) - .collect(toSet()); - - return oldCustomer.getAddresses() - .stream() - .filter(oldAddress -> isBlank(oldAddress.getKey()) - || !newAddressKeys.contains(oldAddress.getKey())) - .map(RemoveAddress::of) - .collect(toList()); + } + + /** + * Compares the {@link List} of a {@link Customer#getShippingAddresses()} and a {@link + * CustomerDraft#getShippingAddresses()}. It returns a {@link List} of {@link + * AddShippingAddressId} update actions as a result, if the new shipping address needs to be added + * to have the same set of addresses as the {@code newCustomer}. If both the {@link Customer} and + * the {@link CustomerDraft} have the same set of shipping addresses, then no update actions are + * needed and hence an empty {@link List} is returned. + * + *

Notes: + * + *

    + *
  • Addresses are matching by their keys. + *
  • Old address values without keys are filtered out. + *
  • Each address in the new addresses list satisfies the following conditions: + *
      + *
    1. It is not null + *
    2. It has a key which is not blank (null/empty) + *
    + * Otherwise, a {@link IllegalArgumentException} will be thrown. + *
+ * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new shipping addresses. + * @return A list containing the update actions or an empty list if the shipping addresses are + * identical. + */ + @Nonnull + public static List> buildAddShippingAddressUpdateActions( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + if (newCustomer.getShippingAddresses() == null + || newCustomer.getShippingAddresses().isEmpty()) { + return Collections.emptyList(); } - /** - * Compares the {@link List} of a {@link CustomerDraft#getAddresses()} and a {@link Customer#getAddresses()}. - * It returns a {@link List} of {@link ChangeAddress} update actions as a result, if the old address needs to be - * changed/updated from the {@code oldCustomer} to have the same set of addresses as the {@code newCustomer}. - * If both the {@link Customer} and the {@link CustomerDraft} have the same set of addresses, - * then no update actions are needed and hence an empty {@link List} is returned. - * - *

Notes: - *

    - *
  • Addresses are matching by their keys.
  • - *
  • Null values of the new addresses are filtered out.
  • - *
  • Address values without keys are filtered out.
  • - *
- * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new addresses. - * @return A list containing the update actions or an empty list if the addresses are identical. - */ - @Nonnull - public static List> buildChangeAddressUpdateActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - if (newCustomer.getAddresses() == null || newCustomer.getAddresses().isEmpty()) { - return Collections.emptyList(); - } - - final Map oldAddressKeyToAddressMap = - oldCustomer.getAddresses() - .stream() - .filter(address -> !isBlank(address.getKey())) - .collect(toMap(Address::getKey, identity())); - - return newCustomer.getAddresses() - .stream() - .filter(Objects::nonNull) - .filter(newAddress -> !isBlank(newAddress.getKey())) - .filter(newAddress -> oldAddressKeyToAddressMap.containsKey(newAddress.getKey())) - .map(newAddress -> { - final Address oldAddress = oldAddressKeyToAddressMap.get(newAddress.getKey()); - if (!newAddress.equalsIgnoreId(oldAddress)) { - return ChangeAddress.of(oldAddress.getId(), newAddress); - } - return null; - }) - .filter(Objects::nonNull) - .collect(toList()); + final Map oldAddressKeyToAddressMap = + oldCustomer.getShippingAddresses().stream() + .filter(address -> !isBlank(address.getKey())) + .collect(toMap(Address::getKey, identity())); + + final Set newAddressKeys = + newCustomer.getShippingAddresses().stream() + .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) + .collect(toSet()); + + return newAddressKeys.stream() + .filter(newAddressKey -> !oldAddressKeyToAddressMap.containsKey(newAddressKey)) + .map(AddShippingAddressId::ofKey) + .collect(toList()); + } + + /** + * Compares the {@link List} of a {@link Customer#getShippingAddresses()} and a {@link + * CustomerDraft#getShippingAddresses()}. It returns a {@link List} of {@link + * RemoveShippingAddressId} update actions as a result, if the old shipping address needs to be + * removed to have the same set of addresses as the {@code newCustomer}. If both the {@link + * Customer} and the {@link CustomerDraft} have the same set of shipping addresses, then no update + * actions are needed and hence an empty {@link List} is returned. + * + *

Notes: + * + *

    + *
  • Addresses are matching by their keys. + *
  • Old shipping addresses without keys will be removed. + *
  • Each address in the new addresses list satisfies the following conditions: + *
      + *
    1. It exists in the given index. + *
    2. It has a key which is not blank (null/empty) + *
    + * Otherwise, a {@link IllegalArgumentException} will be thrown. + *
+ * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new shipping addresses. + * @return A list containing the update actions or an empty list if the shipping addresses are + * identical. + */ + @Nonnull + public static List> buildRemoveShippingAddressUpdateActions( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + if ((oldCustomer.getShippingAddresses() == null + || oldCustomer.getShippingAddresses().isEmpty())) { + + return Collections.emptyList(); } - /** - * Compares the {@link List} of a {@link CustomerDraft#getAddresses()} and a {@link Customer#getAddresses()}. - * It returns a {@link List} of {@link AddAddress} update actions as a result, if the new address needs to be - * added to have the same set of addresses as the {@code newCustomer}. - * If both the {@link Customer} and the {@link CustomerDraft} have the same set of addresses, - * then no update actions are needed and hence an empty {@link List} is returned. - * - *

Notes: - *

    - *
  • Addresses are matching by their keys.
  • - *
  • Null values of the new addresses are filtered out.
  • - *
  • Address values without keys are filtered out.
  • - *
- * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new addresses. - * @return A list containing the update actions or an empty list if the addresses are identical. - */ - @Nonnull - public static List> buildAddAddressUpdateActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - if (newCustomer.getAddresses() == null || newCustomer.getAddresses().isEmpty()) { - return Collections.emptyList(); - } - - final Map oldAddressKeyToAddressMap = - oldCustomer.getAddresses() - .stream() - .filter(address -> !isBlank(address.getKey())) - .collect(toMap(Address::getKey, identity())); - - return newCustomer.getAddresses() - .stream() - .filter(Objects::nonNull) - .filter(newAddress -> !isBlank(newAddress.getKey())) - .filter(newAddress -> !oldAddressKeyToAddressMap.containsKey(newAddress.getKey())) - .map(AddAddress::of) - .collect(toList()); - } + if (newCustomer.getShippingAddresses() == null + || newCustomer.getShippingAddresses().isEmpty()) { - /** - * Compares the {@link Customer#getDefaultShippingAddress()} and {@link CustomerDraft#getDefaultShippingAddress()}. - * If they are different - return {@link SetDefaultShippingAddress} update action. If the old shipping - * address is set, but the new one is empty - the command will unset the default shipping address. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft with new default shipping address. - * @return An optional with {@link SetDefaultShippingAddress} update action. - */ - @Nonnull - public static Optional> buildSetDefaultShippingAddressUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - final Address oldAddress = oldCustomer.getDefaultShippingAddress(); - final String newAddressKey = - getAddressKeyAt(newCustomer.getAddresses(), newCustomer.getDefaultShippingAddress()); - - if (newAddressKey != null) { - if (oldAddress == null || !Objects.equals(oldAddress.getKey(), newAddressKey)) { - return Optional.of(SetDefaultShippingAddress.ofKey(newAddressKey)); - } - } else if (oldAddress != null) { // unset - return Optional.of(SetDefaultShippingAddress.ofKey(null)); - } - - return Optional.empty(); + return oldCustomer.getShippingAddresses().stream() + .map(address -> RemoveShippingAddressId.of(address.getId())) + .collect(Collectors.toList()); } - /** - * Compares the {@link Customer#getDefaultBillingAddress()} and {@link CustomerDraft#getDefaultBillingAddress()}. - * If they are different - return {@link SetDefaultBillingAddress} update action. If the old billing address - * id value is set, but the new one is empty - the command will unset the default billing address. - * - * @param oldCustomer the customer that should be updated. - * @param newCustomer the customer draft with new default billing address. - * @return An optional with {@link SetDefaultBillingAddress} update action. - */ - @Nonnull - public static Optional> buildSetDefaultBillingAddressUpdateAction( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - final Address oldAddress = oldCustomer.getDefaultBillingAddress(); - final String newAddressKey = - getAddressKeyAt(newCustomer.getAddresses(), newCustomer.getDefaultBillingAddress()); - - if (newAddressKey != null) { - if (oldAddress == null || !Objects.equals(oldAddress.getKey(), newAddressKey)) { - return Optional.of(SetDefaultBillingAddress.ofKey(newAddressKey)); - } - } else if (oldAddress != null) { // unset - return Optional.of(SetDefaultBillingAddress.ofKey(null)); - } - - return Optional.empty(); + final Set newAddressKeys = + newCustomer.getShippingAddresses().stream() + .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) + .collect(toSet()); + + return oldCustomer.getShippingAddresses().stream() + .filter(address -> isBlank(address.getKey()) || !newAddressKeys.contains(address.getKey())) + .map(address -> RemoveShippingAddressId.of(address.getId())) + .collect(toList()); + } + + /** + * Compares the {@link List} of a {@link Customer#getBillingAddresses()} and a {@link + * CustomerDraft#getBillingAddresses()}. It returns a {@link List} of {@link AddBillingAddressId} + * update actions as a result, if the new billing address needs to be added to have the same set + * of addresses as the {@code newCustomer}. If both the {@link Customer} and the {@link + * CustomerDraft} have the same set of billing addresses, then no update actions are needed and + * hence an empty {@link List} is returned. + * + *

Notes: + * + *

    + *
  • Addresses are matching by their keys. + *
  • Old address values without keys are filtered out. + *
  • Each address in the new addresses list satisfies the following conditions: + *
      + *
    1. It is not null + *
    2. It has a key which is not blank (null/empty) + *
    + * Otherwise, a {@link IllegalArgumentException} will be thrown. + *
+ * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new billing addresses. + * @return A list containing the update actions or an empty list if the billing addresses are + * identical. + */ + @Nonnull + public static List> buildAddBillingAddressUpdateActions( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + if (newCustomer.getBillingAddresses() == null || newCustomer.getBillingAddresses().isEmpty()) { + return Collections.emptyList(); } - @Nullable - private static String getAddressKeyAt( - @Nullable final List
addressList, - @Nullable final Integer index) { - - if (index == null) { - return null; - } - - if (addressList == null || index < 0 || index >= addressList.size()) { - throw new IllegalArgumentException( - format("Addresses list does not contain an address at the index: %s", index)); - } - - final Address address = addressList.get(index); - if (address == null) { - throw new IllegalArgumentException( - format("Address is null at the index: %s of the addresses list.", index)); - } else if (isBlank(address.getKey())) { - throw new IllegalArgumentException( - format("Address does not have a key at the index: %s of the addresses list.", index)); - } else { - return address.getKey(); - } + final Map oldAddressKeyToAddressMap = + oldCustomer.getBillingAddresses().stream() + .filter(address -> !isBlank(address.getKey())) + .collect(toMap(Address::getKey, identity())); + + final Set newAddressKeys = + newCustomer.getBillingAddresses().stream() + .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) + .collect(toSet()); + + return newAddressKeys.stream() + .filter(newAddressKey -> !oldAddressKeyToAddressMap.containsKey(newAddressKey)) + .map(AddBillingAddressId::ofKey) + .collect(toList()); + } + + /** + * Compares the {@link List} of a {@link Customer#getBillingAddresses()} and a {@link + * CustomerDraft#getBillingAddresses()}. It returns a {@link List} of {@link + * RemoveBillingAddressId} update actions as a result, if the old billing address needs to be + * removed to have the same set of addresses as the {@code newCustomer}. If both the {@link + * Customer} and the {@link CustomerDraft} have the same set of billing addresses, then no update + * actions are needed and hence an empty {@link List} is returned. + * + *

Notes: + * + *

    + *
  • Addresses are matching by their keys. + *
  • Null values of the old addresses are filtered out. + *
  • Old shipping address values without keys are filtered out. + *
  • Each address in the new addresses list satisfies the following conditions: + *
      + *
    1. It exists in the given index. + *
    2. It has a key which is not blank (null/empty) + *
    + * Otherwise, a {@link IllegalArgumentException} will be thrown. + *
+ * + * @param oldCustomer the customer which should be updated. + * @param newCustomer the customer draft where we get the new shipping addresses. + * @return A list containing the update actions or an empty list if the shipping addresses are + * identical. + */ + @Nonnull + public static List> buildRemoveBillingAddressUpdateActions( + @Nonnull final Customer oldCustomer, @Nonnull final CustomerDraft newCustomer) { + + if ((oldCustomer.getBillingAddresses() == null + || oldCustomer.getBillingAddresses().isEmpty())) { + + return Collections.emptyList(); } - /** - * Compares the {@link List} of a {@link Customer#getShippingAddresses()} and a - * {@link CustomerDraft#getShippingAddresses()}. It returns a {@link List} of {@link AddShippingAddressId} - * update actions as a result, if the new shipping address needs to be added to have the same set of addresses as - * the {@code newCustomer}. If both the {@link Customer} and the {@link CustomerDraft} have the same set of - * shipping addresses, then no update actions are needed and hence an empty {@link List} is returned. - * - *

Notes: - *

    - *
  • Addresses are matching by their keys.
  • - *
  • Old address values without keys are filtered out.
  • - *
  • Each address in the new addresses list satisfies the following conditions: - *
      - *
    1. It is not null
    2. - *
    3. It has a key which is not blank (null/empty)
    4. - *
    - * Otherwise, a {@link IllegalArgumentException} will be thrown. - *
  • - *
- * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new shipping addresses. - * @return A list containing the update actions or an empty list if the shipping addresses are identical. - */ - @Nonnull - public static List> buildAddShippingAddressUpdateActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - if (newCustomer.getShippingAddresses() == null - || newCustomer.getShippingAddresses().isEmpty()) { - return Collections.emptyList(); - } - - final Map oldAddressKeyToAddressMap = - oldCustomer.getShippingAddresses() - .stream() - .filter(address -> !isBlank(address.getKey())) - .collect(toMap(Address::getKey, identity())); - - final Set newAddressKeys = - newCustomer.getShippingAddresses() - .stream() - .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) - .collect(toSet()); - - return newAddressKeys - .stream() - .filter(newAddressKey -> !oldAddressKeyToAddressMap.containsKey(newAddressKey)) - .map(AddShippingAddressId::ofKey) - .collect(toList()); - } + if (newCustomer.getBillingAddresses() == null || newCustomer.getBillingAddresses().isEmpty()) { - /** - * Compares the {@link List} of a {@link Customer#getShippingAddresses()} and a - * {@link CustomerDraft#getShippingAddresses()}. It returns a {@link List} of {@link RemoveShippingAddressId} - * update actions as a result, if the old shipping address needs to be removed to have the same set of addresses as - * the {@code newCustomer}. If both the {@link Customer} and the {@link CustomerDraft} have the same set of - * shipping addresses, then no update actions are needed and hence an empty {@link List} is returned. - * - * - *

Notes: - *

    - *
  • Addresses are matching by their keys.
  • - *
  • Old shipping addresses without keys will be removed.
  • - *
  • Each address in the new addresses list satisfies the following conditions: - *
      - *
    1. It exists in the given index.
    2. - *
    3. It has a key which is not blank (null/empty)
    4. - *
    - * Otherwise, a {@link IllegalArgumentException} will be thrown. - *
  • - *
- * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new shipping addresses. - * @return A list containing the update actions or an empty list if the shipping addresses are identical. - */ - @Nonnull - public static List> buildRemoveShippingAddressUpdateActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - if ((oldCustomer.getShippingAddresses() == null - || oldCustomer.getShippingAddresses().isEmpty())) { - - return Collections.emptyList(); - } - - if (newCustomer.getShippingAddresses() == null - || newCustomer.getShippingAddresses().isEmpty()) { - - return oldCustomer.getShippingAddresses() - .stream() - .map(address -> RemoveShippingAddressId.of(address.getId())) - .collect(Collectors.toList()); - } - - final Set newAddressKeys = - newCustomer.getShippingAddresses() - .stream() - .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) - .collect(toSet()); - - return oldCustomer.getShippingAddresses() - .stream() - .filter(address -> isBlank(address.getKey()) || !newAddressKeys.contains(address.getKey())) - .map(address -> RemoveShippingAddressId.of(address.getId())) - .collect(toList()); + return oldCustomer.getBillingAddresses().stream() + .map(address -> RemoveBillingAddressId.of(address.getId())) + .collect(Collectors.toList()); } - /** - * Compares the {@link List} of a {@link Customer#getBillingAddresses()} and a - * {@link CustomerDraft#getBillingAddresses()}. It returns a {@link List} of {@link AddBillingAddressId} - * update actions as a result, if the new billing address needs to be added to have the same set of addresses as - * the {@code newCustomer}. If both the {@link Customer} and the {@link CustomerDraft} have the same set of - * billing addresses, then no update actions are needed and hence an empty {@link List} is returned. - * - *

Notes: - *

    - *
  • Addresses are matching by their keys.
  • - *
  • Old address values without keys are filtered out.
  • - *
  • Each address in the new addresses list satisfies the following conditions: - *
      - *
    1. It is not null
    2. - *
    3. It has a key which is not blank (null/empty)
    4. - *
    - * Otherwise, a {@link IllegalArgumentException} will be thrown. - *
  • - *
- * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new billing addresses. - * @return A list containing the update actions or an empty list if the billing addresses are identical. - */ - @Nonnull - public static List> buildAddBillingAddressUpdateActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - if (newCustomer.getBillingAddresses() == null - || newCustomer.getBillingAddresses().isEmpty()) { - return Collections.emptyList(); - } - - final Map oldAddressKeyToAddressMap = - oldCustomer.getBillingAddresses() - .stream() - .filter(address -> !isBlank(address.getKey())) - .collect(toMap(Address::getKey, identity())); - - - final Set newAddressKeys = - newCustomer.getBillingAddresses() - .stream() - .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) - .collect(toSet()); - - return newAddressKeys - .stream() - .filter(newAddressKey -> !oldAddressKeyToAddressMap.containsKey(newAddressKey)) - .map(AddBillingAddressId::ofKey) - .collect(toList()); - } + final Set newAddressKeys = + newCustomer.getBillingAddresses().stream() + .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) + .collect(toSet()); - /** - * Compares the {@link List} of a {@link Customer#getBillingAddresses()} and a - * {@link CustomerDraft#getBillingAddresses()}. It returns a {@link List} of {@link RemoveBillingAddressId} - * update actions as a result, if the old billing address needs to be removed to have the same set of addresses as - * the {@code newCustomer}. If both the {@link Customer} and the {@link CustomerDraft} have the same set of - * billing addresses, then no update actions are needed and hence an empty {@link List} is returned. - * - *

Notes: - *

    - *
  • Addresses are matching by their keys.
  • - *
  • Null values of the old addresses are filtered out.
  • - *
  • Old shipping address values without keys are filtered out.
  • - *
  • Each address in the new addresses list satisfies the following conditions: - *
      - *
    1. It exists in the given index.
    2. - *
    3. It has a key which is not blank (null/empty)
    4. - *
    - * Otherwise, a {@link IllegalArgumentException} will be thrown. - *
  • - *
- * - * @param oldCustomer the customer which should be updated. - * @param newCustomer the customer draft where we get the new shipping addresses. - * @return A list containing the update actions or an empty list if the shipping addresses are identical. - */ - @Nonnull - public static List> buildRemoveBillingAddressUpdateActions( - @Nonnull final Customer oldCustomer, - @Nonnull final CustomerDraft newCustomer) { - - if ((oldCustomer.getBillingAddresses() == null - || oldCustomer.getBillingAddresses().isEmpty())) { - - return Collections.emptyList(); - } - - if (newCustomer.getBillingAddresses() == null - || newCustomer.getBillingAddresses().isEmpty()) { - - return oldCustomer.getBillingAddresses() - .stream() - .map(address -> RemoveBillingAddressId.of(address.getId())) - .collect(Collectors.toList()); - } - - final Set newAddressKeys = - newCustomer.getBillingAddresses() - .stream() - .map(index -> getAddressKeyAt(newCustomer.getAddresses(), index)) - .collect(toSet()); - - return oldCustomer.getBillingAddresses() - .stream() - .filter(address -> isBlank(address.getKey()) || !newAddressKeys.contains(address.getKey())) - .map(address -> RemoveBillingAddressId.of(address.getId())) - .collect(toList()); - } + return oldCustomer.getBillingAddresses().stream() + .filter(address -> isBlank(address.getKey()) || !newAddressKeys.contains(address.getKey())) + .map(address -> RemoveBillingAddressId.of(address.getId())) + .collect(toList()); + } } diff --git a/src/main/java/com/commercetools/sync/customobjects/CustomObjectSync.java b/src/main/java/com/commercetools/sync/customobjects/CustomObjectSync.java index c7962dc1ba..29f32f0e60 100644 --- a/src/main/java/com/commercetools/sync/customobjects/CustomObjectSync.java +++ b/src/main/java/com/commercetools/sync/customobjects/CustomObjectSync.java @@ -1,5 +1,12 @@ package com.commercetools.sync.customobjects; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.customobjects.helpers.CustomObjectBatchValidator; @@ -11,315 +18,349 @@ import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.CustomObjectDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; /** * This class syncs custom object drafts with the corresponding custom objects in the CTP project. */ -public class CustomObjectSync extends BaseSync, - CustomObjectSyncStatistics, CustomObjectSyncOptions> { +public class CustomObjectSync + extends BaseSync< + CustomObjectDraft, CustomObjectSyncStatistics, CustomObjectSyncOptions> { - private static final String CTP_CUSTOM_OBJECT_FETCH_FAILED = - "Failed to fetch existing custom objects with keys: '%s'."; - private static final String CTP_CUSTOM_OBJECT_UPDATE_FAILED = - "Failed to update custom object with key: '%s'. Reason: %s"; - private static final String CTP_CUSTOM_OBJECT_CREATE_FAILED = - "Failed to create custom object with key: '%s'. Reason: %s"; + private static final String CTP_CUSTOM_OBJECT_FETCH_FAILED = + "Failed to fetch existing custom objects with keys: '%s'."; + private static final String CTP_CUSTOM_OBJECT_UPDATE_FAILED = + "Failed to update custom object with key: '%s'. Reason: %s"; + private static final String CTP_CUSTOM_OBJECT_CREATE_FAILED = + "Failed to create custom object with key: '%s'. Reason: %s"; - private final CustomObjectService customObjectService; - private final CustomObjectBatchValidator batchValidator; + private final CustomObjectService customObjectService; + private final CustomObjectBatchValidator batchValidator; - public CustomObjectSync(@Nonnull final CustomObjectSyncOptions syncOptions) { - this(syncOptions, new CustomObjectServiceImpl(syncOptions)); - } + public CustomObjectSync(@Nonnull final CustomObjectSyncOptions syncOptions) { + this(syncOptions, new CustomObjectServiceImpl(syncOptions)); + } - /** - * Takes a {@link CustomObjectSyncOptions} and a {@link CustomObjectService} instances to instantiate - * a new {@link CustomObjectSync} instance that could be used to sync customObject drafts in the CTP project - * specified in the injected {@link CustomObjectSyncOptions} instance. - * - *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and passed to. - * - * @param syncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param customObjectService the custom object service which is responsible for fetching/caching the - */ - CustomObjectSync( - @Nonnull final CustomObjectSyncOptions syncOptions, - @Nonnull final CustomObjectService customObjectService) { + /** + * Takes a {@link CustomObjectSyncOptions} and a {@link CustomObjectService} instances to + * instantiate a new {@link CustomObjectSync} instance that could be used to sync customObject + * drafts in the CTP project specified in the injected {@link CustomObjectSyncOptions} instance. + * + *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and + * passed to. + * + * @param syncOptions the container of all the options of the sync process including the CTP + * project client and/or configuration and other sync-specific options. + * @param customObjectService the custom object service which is responsible for fetching/caching + * the + */ + CustomObjectSync( + @Nonnull final CustomObjectSyncOptions syncOptions, + @Nonnull final CustomObjectService customObjectService) { - super(new CustomObjectSyncStatistics(), syncOptions); - this.customObjectService = customObjectService; - this.batchValidator = new CustomObjectBatchValidator(getSyncOptions(), getStatistics()); - } + super(new CustomObjectSyncStatistics(), syncOptions); + this.customObjectService = customObjectService; + this.batchValidator = new CustomObjectBatchValidator(getSyncOptions(), getStatistics()); + } - /** - * Iterates through the whole {@code customObjectDrafts} list and accumulates its valid drafts to batches. - * Every batch is then processed by {@link CustomObjectSync#processBatch(List)}. - * - *

Inherited doc: - * {@inheritDoc} - * - * @param customObjectDrafts {@link List} of {@link CustomObjectDraft}'s that would be synced into CTP project. - * @return {@link CompletionStage} with {@link CustomObjectSyncStatistics} holding statistics of all sync - * processes performed by this sync instance. - */ - protected CompletionStage process( - @Nonnull final List> customObjectDrafts) { - final List>> batches = batchElements( - customObjectDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, CompletableFuture.completedFuture(statistics)); - } + /** + * Iterates through the whole {@code customObjectDrafts} list and accumulates its valid drafts to + * batches. Every batch is then processed by {@link CustomObjectSync#processBatch(List)}. + * + *

Inherited doc: {@inheritDoc} + * + * @param customObjectDrafts {@link List} of {@link CustomObjectDraft}'s that would be synced into + * CTP project. + * @return {@link CompletionStage} with {@link CustomObjectSyncStatistics} holding statistics of + * all sync processes performed by this sync instance. + */ + protected CompletionStage process( + @Nonnull final List> customObjectDrafts) { + final List>> batches = + batchElements(customObjectDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, CompletableFuture.completedFuture(statistics)); + } - /** - * This method first creates a new {@link Set} of valid {@link CustomObjectDraft} elements. For more on the rules of - * validation, check: {@link CustomObjectBatchValidator#validateAndCollectReferencedKeys(List)}. Using the resulting - * set of {@code validCustomObjectDrafts}, the matching custom objects in the target CTP project are fetched then - * the method {@link CustomObjectSync#syncBatch(Set, Set)} is called to perform the sync (update or - * create requests accordingly) on the target project. - * - *

In case of error during of fetching of existing custom objects, the error callback will be triggered. - * And the sync process would stop for the given batch. - *

- * - * @param batch batch of drafts that need to be synced - * @return a {@link CompletionStage} containing an instance - * of {@link CustomObjectSyncStatistics} which contains information about the result of syncing the supplied - * batch to the target project. - */ - protected CompletionStage processBatch( - @Nonnull final List> batch) { + /** + * This method first creates a new {@link Set} of valid {@link CustomObjectDraft} elements. For + * more on the rules of validation, check: {@link + * CustomObjectBatchValidator#validateAndCollectReferencedKeys(List)}. Using the resulting set of + * {@code validCustomObjectDrafts}, the matching custom objects in the target CTP project are + * fetched then the method {@link CustomObjectSync#syncBatch(Set, Set)} is called to perform the + * sync (update or create requests accordingly) on the target project. + * + *

In case of error during of fetching of existing custom objects, the error callback will be + * triggered. And the sync process would stop for the given batch. + * + * @param batch batch of drafts that need to be synced + * @return a {@link CompletionStage} containing an instance of {@link CustomObjectSyncStatistics} + * which contains information about the result of syncing the supplied batch to the target + * project. + */ + protected CompletionStage processBatch( + @Nonnull final List> batch) { - final ImmutablePair>, Set> result = - batchValidator.validateAndCollectReferencedKeys(batch); + final ImmutablePair>, Set> + result = batchValidator.validateAndCollectReferencedKeys(batch); - final Set> validDrafts = result.getLeft(); - if (validDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return CompletableFuture.completedFuture(statistics); - } - final Set validIdentifiers = result.getRight(); + final Set> validDrafts = result.getLeft(); + if (validDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return CompletableFuture.completedFuture(statistics); + } + final Set validIdentifiers = result.getRight(); - return customObjectService - .fetchMatchingCustomObjects(validIdentifiers) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Set> fetchedCustomObjects = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); + return customObjectService + .fetchMatchingCustomObjects(validIdentifiers) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Set> fetchedCustomObjects = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); - if (exception != null) { - final String errorMessage = format(CTP_CUSTOM_OBJECT_FETCH_FAILED, validIdentifiers); - handleError(errorMessage, exception, validIdentifiers.size()); - return CompletableFuture.completedFuture(null); - } else { - return syncBatch(fetchedCustomObjects, validDrafts); - } + if (exception != null) { + final String errorMessage = + format(CTP_CUSTOM_OBJECT_FETCH_FAILED, validIdentifiers); + handleError(errorMessage, exception, validIdentifiers.size()); + return CompletableFuture.completedFuture(null); + } else { + return syncBatch(fetchedCustomObjects, validDrafts); + } }) - .thenApply(ignored -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignored -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); - } + } - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed custom objects to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - * @param exception The exception that called caused the failure, if any. - * @param failedTimes The number of times that the failed custom objects counter is incremented. - */ - private void handleError(@Nonnull final String errorMessage, @Nonnull final Throwable exception, - final int failedTimes) { - SyncException syncException = new SyncException(errorMessage, exception); - syncOptions.applyErrorCallback(syncException); - statistics.incrementFailed(failedTimes); - } + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed custom objects to sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + * @param exception The exception that called caused the failure, if any. + * @param failedTimes The number of times that the failed custom objects counter is incremented. + */ + private void handleError( + @Nonnull final String errorMessage, + @Nonnull final Throwable exception, + final int failedTimes) { + SyncException syncException = new SyncException(errorMessage, exception); + syncOptions.applyErrorCallback(syncException); + statistics.incrementFailed(failedTimes); + } - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed custom objects to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - * @param exception The exception that called caused the failure, if any. - * @param failedTimes The number of times that the failed custom objects counter is incremented. - * @param oldCustomObject existing custom object that could be updated. - * @param newCustomObjectDraft draft containing data that could differ from data in {@code oldCustomObject}. - */ - private void handleError(@Nonnull final String errorMessage, @Nullable final Throwable exception, - final int failedTimes, @Nullable final CustomObject oldCustomObject, - @Nullable final CustomObjectDraft newCustomObjectDraft) { + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed custom objects to sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + * @param exception The exception that called caused the failure, if any. + * @param failedTimes The number of times that the failed custom objects counter is incremented. + * @param oldCustomObject existing custom object that could be updated. + * @param newCustomObjectDraft draft containing data that could differ from data in {@code + * oldCustomObject}. + */ + private void handleError( + @Nonnull final String errorMessage, + @Nullable final Throwable exception, + final int failedTimes, + @Nullable final CustomObject oldCustomObject, + @Nullable final CustomObjectDraft newCustomObjectDraft) { - SyncException syncException = exception != null ? new SyncException(errorMessage, exception) + SyncException syncException = + exception != null + ? new SyncException(errorMessage, exception) : new SyncException(errorMessage); - syncOptions.applyErrorCallback(syncException, oldCustomObject, newCustomObjectDraft, null); - statistics.incrementFailed(failedTimes); - } + syncOptions.applyErrorCallback(syncException, oldCustomObject, newCustomObjectDraft, null); + statistics.incrementFailed(failedTimes); + } - /** - * Given a set of custom object drafts, attempts to sync the drafts with the existing custom objects in the CTP - * project. The custom object and the draft are considered to match if they have the same key and container. - * - * @param oldCustomObjects old custom objects. - * @param newCustomObjectDrafts drafts that need to be synced. - * @return a {@link CompletionStage} which contains an empty result after execution of the update - */ - @Nonnull - private CompletionStage syncBatch( - @Nonnull final Set> oldCustomObjects, - @Nonnull final Set> newCustomObjectDrafts) { + /** + * Given a set of custom object drafts, attempts to sync the drafts with the existing custom + * objects in the CTP project. The custom object and the draft are considered to match if they + * have the same key and container. + * + * @param oldCustomObjects old custom objects. + * @param newCustomObjectDrafts drafts that need to be synced. + * @return a {@link CompletionStage} which contains an empty result after execution of the update + */ + @Nonnull + private CompletionStage syncBatch( + @Nonnull final Set> oldCustomObjects, + @Nonnull final Set> newCustomObjectDrafts) { - final Map> oldCustomObjectMap = - oldCustomObjects.stream().collect( - toMap(customObject -> CustomObjectCompositeIdentifier.of( - customObject.getKey(), customObject.getContainer()).toString(), identity())); + final Map> oldCustomObjectMap = + oldCustomObjects.stream() + .collect( + toMap( + customObject -> + CustomObjectCompositeIdentifier.of( + customObject.getKey(), customObject.getContainer()) + .toString(), + identity())); - return CompletableFuture.allOf(newCustomObjectDrafts - .stream() - .map(newCustomObjectDraft -> { - final CustomObject oldCustomObject = oldCustomObjectMap.get( - CustomObjectCompositeIdentifier.of(newCustomObjectDraft).toString()); - return ofNullable(oldCustomObject) - .map(customObject -> updateCustomObject(oldCustomObject, newCustomObjectDraft)) - .orElseGet(() -> applyCallbackAndCreate(newCustomObjectDraft)); - }) + return CompletableFuture.allOf( + newCustomObjectDrafts.stream() + .map( + newCustomObjectDraft -> { + final CustomObject oldCustomObject = + oldCustomObjectMap.get( + CustomObjectCompositeIdentifier.of(newCustomObjectDraft).toString()); + return ofNullable(oldCustomObject) + .map( + customObject -> updateCustomObject(oldCustomObject, newCustomObjectDraft)) + .orElseGet(() -> applyCallbackAndCreate(newCustomObjectDraft)); + }) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); - } + } - /** - * Given a custom object draft, this method applies the beforeCreateCallback and then issues a create request to the - * CTP project to create the corresponding CustomObject. - * - * @param customObjectDraft the custom object draft to create the custom object from. - * @return a {@link CompletionStage} which contains created custom object after success execution of the create. - * Otherwise it contains an empty result in case of failure. - */ - @Nonnull - private CompletionStage>> applyCallbackAndCreate( - @Nonnull final CustomObjectDraft customObjectDraft) { + /** + * Given a custom object draft, this method applies the beforeCreateCallback and then issues a + * create request to the CTP project to create the corresponding CustomObject. + * + * @param customObjectDraft the custom object draft to create the custom object from. + * @return a {@link CompletionStage} which contains created custom object after success execution + * of the create. Otherwise it contains an empty result in case of failure. + */ + @Nonnull + private CompletionStage>> applyCallbackAndCreate( + @Nonnull final CustomObjectDraft customObjectDraft) { - return syncOptions - .applyBeforeCreateCallback(customObjectDraft) - .map(draft -> customObjectService + return syncOptions + .applyBeforeCreateCallback(customObjectDraft) + .map( + draft -> + customObjectService .upsertCustomObject(draft) - .thenApply(customObjectOptional -> { - if (customObjectOptional.isPresent()) { + .thenApply( + customObjectOptional -> { + if (customObjectOptional.isPresent()) { statistics.incrementCreated(); - } else { + } else { statistics.incrementFailed(); - } - return customObjectOptional; - }).exceptionally(sphereException -> { - final String errorMessage = - format(CTP_CUSTOM_OBJECT_CREATE_FAILED, - CustomObjectCompositeIdentifier.of(customObjectDraft).toString(), - sphereException.getMessage()); - handleError(errorMessage, sphereException, 1, - null, customObjectDraft); - return Optional.empty(); - }) - ).orElse(completedFuture(Optional.empty())); - } - - /** - * Given an existing {@link CustomObject} and a new {@link CustomObjectDraft}, the method first checks whether - * existing {@link CustomObject} and a new {@link CustomObjectDraft} are identical. If so, the method aborts update - * the new draft and return an empty result, otherwise a request is made to CTP to update the existing custom - * object. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP,the error handling method - * is called. - * - * @param oldCustomObject existing custom object that could be updated. - * @param newCustomObject draft containing data that could differ from data in {@code oldCustomObject}. - * @return a {@link CompletionStage} which contains an empty result after execution of the update. - */ - @Nonnull - private CompletionStage>> updateCustomObject( - @Nonnull final CustomObject oldCustomObject, - @Nonnull final CustomObjectDraft newCustomObject) { + } + return customObjectOptional; + }) + .exceptionally( + sphereException -> { + final String errorMessage = + format( + CTP_CUSTOM_OBJECT_CREATE_FAILED, + CustomObjectCompositeIdentifier.of(customObjectDraft).toString(), + sphereException.getMessage()); + handleError(errorMessage, sphereException, 1, null, customObjectDraft); + return Optional.empty(); + })) + .orElse(completedFuture(Optional.empty())); + } - if (!CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObject)) { - return customObjectService - .upsertCustomObject(newCustomObject) - .handle(ImmutablePair::new) - .thenCompose(updatedResponseEntry -> { - final Optional> updateCustomObjectOptional = updatedResponseEntry.getKey(); - final Throwable sphereException = updatedResponseEntry.getValue(); - if (sphereException != null) { - return executeSupplierIfConcurrentModificationException(sphereException, - () -> fetchAndUpdate(oldCustomObject, newCustomObject), - () -> { - final String errorMessage = - format(CTP_CUSTOM_OBJECT_UPDATE_FAILED, - CustomObjectCompositeIdentifier.of(newCustomObject).toString(), - sphereException.getMessage()); - handleError(errorMessage, sphereException, 1, - oldCustomObject, newCustomObject); - return CompletableFuture.completedFuture(Optional.empty()); - }); - } else { - statistics.incrementUpdated(); - return CompletableFuture.completedFuture(Optional.of(updateCustomObjectOptional.get())); - } + /** + * Given an existing {@link CustomObject} and a new {@link CustomObjectDraft}, the method first + * checks whether existing {@link CustomObject} and a new {@link CustomObjectDraft} are identical. + * If so, the method aborts update the new draft and return an empty result, otherwise a request + * is made to CTP to update the existing custom object. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP,the + * error handling method is called. + * + * @param oldCustomObject existing custom object that could be updated. + * @param newCustomObject draft containing data that could differ from data in {@code + * oldCustomObject}. + * @return a {@link CompletionStage} which contains an empty result after execution of the update. + */ + @Nonnull + private CompletionStage>> updateCustomObject( + @Nonnull final CustomObject oldCustomObject, + @Nonnull final CustomObjectDraft newCustomObject) { - }); - } - return completedFuture(Optional.empty()); + if (!CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObject)) { + return customObjectService + .upsertCustomObject(newCustomObject) + .handle(ImmutablePair::new) + .thenCompose( + updatedResponseEntry -> { + final Optional> updateCustomObjectOptional = + updatedResponseEntry.getKey(); + final Throwable sphereException = updatedResponseEntry.getValue(); + if (sphereException != null) { + return executeSupplierIfConcurrentModificationException( + sphereException, + () -> fetchAndUpdate(oldCustomObject, newCustomObject), + () -> { + final String errorMessage = + format( + CTP_CUSTOM_OBJECT_UPDATE_FAILED, + CustomObjectCompositeIdentifier.of(newCustomObject).toString(), + sphereException.getMessage()); + handleError( + errorMessage, sphereException, 1, oldCustomObject, newCustomObject); + return CompletableFuture.completedFuture(Optional.empty()); + }); + } else { + statistics.incrementUpdated(); + return CompletableFuture.completedFuture( + Optional.of(updateCustomObjectOptional.get())); + } + }); } + return completedFuture(Optional.empty()); + } - @Nonnull - private CompletionStage>> fetchAndUpdate( - @Nonnull final CustomObject oldCustomObject, - @Nonnull final CustomObjectDraft customObjectDraft) { + @Nonnull + private CompletionStage>> fetchAndUpdate( + @Nonnull final CustomObject oldCustomObject, + @Nonnull final CustomObjectDraft customObjectDraft) { - final CustomObjectCompositeIdentifier identifier = CustomObjectCompositeIdentifier.of(oldCustomObject); + final CustomObjectCompositeIdentifier identifier = + CustomObjectCompositeIdentifier.of(oldCustomObject); - return customObjectService - .fetchCustomObject(identifier) - .handle(ImmutablePair::new) - .thenCompose(fetchedResponseEntry -> { - final Optional> fetchedCustomObjectOptional = fetchedResponseEntry.getKey(); - final Throwable exception = fetchedResponseEntry.getValue(); + return customObjectService + .fetchCustomObject(identifier) + .handle(ImmutablePair::new) + .thenCompose( + fetchedResponseEntry -> { + final Optional> fetchedCustomObjectOptional = + fetchedResponseEntry.getKey(); + final Throwable exception = fetchedResponseEntry.getValue(); - if (exception != null) { - final String errorMessage = format(CTP_CUSTOM_OBJECT_UPDATE_FAILED, identifier.toString(), + if (exception != null) { + final String errorMessage = + format( + CTP_CUSTOM_OBJECT_UPDATE_FAILED, + identifier.toString(), "Failed to fetch from CTP while retrying after concurrency modification."); - handleError(errorMessage, exception, 1, oldCustomObject, customObjectDraft); - return CompletableFuture.completedFuture(Optional.empty()); - } - return fetchedCustomObjectOptional - .map(fetchedCustomObject -> updateCustomObject(fetchedCustomObject, customObjectDraft)) - .orElseGet(() -> { + handleError(errorMessage, exception, 1, oldCustomObject, customObjectDraft); + return CompletableFuture.completedFuture(Optional.empty()); + } + return fetchedCustomObjectOptional + .map( + fetchedCustomObject -> + updateCustomObject(fetchedCustomObject, customObjectDraft)) + .orElseGet( + () -> { final String errorMessage = - format(CTP_CUSTOM_OBJECT_UPDATE_FAILED, identifier.toString(), + format( + CTP_CUSTOM_OBJECT_UPDATE_FAILED, + identifier.toString(), "Not found when attempting to fetch while retrying " + "after concurrency modification."); - handleError(errorMessage, null, 1, - oldCustomObject, customObjectDraft); + handleError(errorMessage, null, 1, oldCustomObject, customObjectDraft); return CompletableFuture.completedFuture(null); - }); - + }); }); - } + } } diff --git a/src/main/java/com/commercetools/sync/customobjects/CustomObjectSyncOptions.java b/src/main/java/com/commercetools/sync/customobjects/CustomObjectSyncOptions.java index bd13895fdb..abcc558443 100644 --- a/src/main/java/com/commercetools/sync/customobjects/CustomObjectSyncOptions.java +++ b/src/main/java/com/commercetools/sync/customobjects/CustomObjectSyncOptions.java @@ -10,36 +10,49 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.CustomObjectDraft; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -public final class CustomObjectSyncOptions extends BaseSyncOptions, - CustomObjectDraft> { +public final class CustomObjectSyncOptions + extends BaseSyncOptions, CustomObjectDraft> { - CustomObjectSyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer>, - Optional>, - List>>> errorCallBack, - @Nullable final TriConsumer>, - Optional>> - warningCallBack, - final int batchSize, - @Nullable final TriFunction>>, CustomObjectDraft, - CustomObject, - List>>> beforeUpdateCallback, - @Nullable final Function, CustomObjectDraft> beforeCreateCallback, - final long cacheSize) { - super( - ctpClient, - errorCallBack, - warningCallBack, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize); - } + CustomObjectSyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, + Optional>, + Optional>, + List>>> + errorCallBack, + @Nullable + final TriConsumer< + SyncException, + Optional>, + Optional>> + warningCallBack, + final int batchSize, + @Nullable + final TriFunction< + List>>, + CustomObjectDraft, + CustomObject, + List>>> + beforeUpdateCallback, + @Nullable + final Function, CustomObjectDraft> + beforeCreateCallback, + final long cacheSize) { + super( + ctpClient, + errorCallBack, + warningCallBack, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } } diff --git a/src/main/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsBuilder.java index 64c639350b..8e59ed8d92 100644 --- a/src/main/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsBuilder.java @@ -5,57 +5,60 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.CustomObjectDraft; - import javax.annotation.Nonnull; -public final class CustomObjectSyncOptionsBuilder extends BaseSyncOptionsBuilder, CustomObjectDraft> { - - public static final int BATCH_SIZE_DEFAULT = 50; +public final class CustomObjectSyncOptionsBuilder + extends BaseSyncOptionsBuilder< + CustomObjectSyncOptionsBuilder, + CustomObjectSyncOptions, + CustomObject, + CustomObjectDraft> { - private CustomObjectSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } + public static final int BATCH_SIZE_DEFAULT = 50; - /** - * Creates a new instance of {@link CustomObjectSyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 50). - * - * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link CustomObjectSyncOptionsBuilder} - */ - public static CustomObjectSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new CustomObjectSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); - } + private CustomObjectSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } - /** - * Creates new instance of {@link CustomObjectSyncOptions} enriched with all attributes provided to {@code this} - * builder. - * - * @return new instance of {@link CustomObjectSyncOptions} - */ - @Override - public CustomObjectSyncOptions build() { - return new CustomObjectSyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } + /** + * Creates a new instance of {@link CustomObjectSyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 50). + * + * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the + * target CTP project. + * @return new instance of {@link CustomObjectSyncOptionsBuilder} + */ + public static CustomObjectSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new CustomObjectSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } - /** - * Returns an instance of this class to be used in the superclass's generic methods. Please see the JavaDoc in the - * overridden method for further details. - * - * @return an instance of this class. - */ - @Override - protected CustomObjectSyncOptionsBuilder getThis() { - return this; - } + /** + * Creates new instance of {@link CustomObjectSyncOptions} enriched with all attributes provided + * to {@code this} builder. + * + * @return new instance of {@link CustomObjectSyncOptions} + */ + @Override + public CustomObjectSyncOptions build() { + return new CustomObjectSyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } + /** + * Returns an instance of this class to be used in the superclass's generic methods. Please see + * the JavaDoc in the overridden method for further details. + * + * @return an instance of this class. + */ + @Override + protected CustomObjectSyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectBatchValidator.java b/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectBatchValidator.java index 7aec4bd37a..eb446c037c 100644 --- a/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectBatchValidator.java +++ b/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectBatchValidator.java @@ -1,71 +1,74 @@ package com.commercetools.sync.customobjects.helpers; +import static java.util.stream.Collectors.toSet; + import com.commercetools.sync.commons.helpers.BaseBatchValidator; import com.commercetools.sync.customobjects.CustomObjectSyncOptions; import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.customobjects.CustomObjectDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Set; import java.util.stream.Collectors; - -import static java.util.stream.Collectors.toSet; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class CustomObjectBatchValidator - extends BaseBatchValidator, CustomObjectSyncOptions, CustomObjectSyncStatistics> { + extends BaseBatchValidator< + CustomObjectDraft, CustomObjectSyncOptions, CustomObjectSyncStatistics> { - static final String CUSTOM_OBJECT_DRAFT_IS_NULL = "CustomObjectDraft is null."; + static final String CUSTOM_OBJECT_DRAFT_IS_NULL = "CustomObjectDraft is null."; - public CustomObjectBatchValidator(@Nonnull final CustomObjectSyncOptions syncOptions, - @Nonnull final CustomObjectSyncStatistics syncStatistics) { - super(syncOptions, syncStatistics); - } + public CustomObjectBatchValidator( + @Nonnull final CustomObjectSyncOptions syncOptions, + @Nonnull final CustomObjectSyncStatistics syncStatistics) { + super(syncOptions, syncStatistics); + } - /** - * Given the {@link List}<{@link CustomObjectDraft}> of drafts this method attempts to validate - * drafts and return an {@link ImmutablePair}<{@link Set}<{@link CustomObjectDraft}>, - * {@link Set}<{@link CustomObjectCompositeIdentifier} >> which contains the {@link Set} of valid drafts - * and valid custom object identifiers (container with key). - * - *

A valid custom object draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
- * - * @param customObjectDrafts the custom object drafts to validate and collect valid custom object identifiers. - * @return {@link ImmutablePair}<{@link Set}<{@link CustomObjectDraft}>, - * {@link Set}<{@link CustomObjectCompositeIdentifier}>> which contains the {@link Set} of - * valid drafts and valid custom object identifiers (container with key). - */ - @Override - public ImmutablePair>, Set> - validateAndCollectReferencedKeys(@Nonnull final List> customObjectDrafts) { + /** + * Given the {@link List}<{@link CustomObjectDraft}> of drafts this method attempts to + * validate drafts and return an {@link ImmutablePair}<{@link Set}<{@link + * CustomObjectDraft}>, {@link Set}<{@link CustomObjectCompositeIdentifier} >> which + * contains the {@link Set} of valid drafts and valid custom object identifiers (container with + * key). + * + *

A valid custom object draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
+ * + * @param customObjectDrafts the custom object drafts to validate and collect valid custom object + * identifiers. + * @return {@link ImmutablePair}<{@link Set}<{@link CustomObjectDraft}>, {@link + * Set}<{@link CustomObjectCompositeIdentifier}>> which contains the {@link Set} of + * valid drafts and valid custom object identifiers (container with key). + */ + @Override + public ImmutablePair>, Set> + validateAndCollectReferencedKeys( + @Nonnull final List> customObjectDrafts) { - final Set> validDrafts = customObjectDrafts - .stream() + final Set> validDrafts = + customObjectDrafts.stream() .filter(this::isValidCustomObjectDraft) .collect(Collectors.toSet()); - final Set validIdentifiers = validDrafts - .stream() - .map(CustomObjectCompositeIdentifier::of) - .collect(toSet()); + final Set validIdentifiers = + validDrafts.stream().map(CustomObjectCompositeIdentifier::of).collect(toSet()); - return ImmutablePair.of(validDrafts, validIdentifiers); - } - - private boolean isValidCustomObjectDraft( - @Nullable final CustomObjectDraft customObjectDraft) { + return ImmutablePair.of(validDrafts, validIdentifiers); + } - if (customObjectDraft == null) { - handleError(CUSTOM_OBJECT_DRAFT_IS_NULL); - } else { - return true; - } + private boolean isValidCustomObjectDraft( + @Nullable final CustomObjectDraft customObjectDraft) { - return false; + if (customObjectDraft == null) { + handleError(CUSTOM_OBJECT_DRAFT_IS_NULL); + } else { + return true; } + + return false; + } } diff --git a/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectCompositeIdentifier.java b/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectCompositeIdentifier.java index 0f58c8f4b2..a7e752c2e0 100644 --- a/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectCompositeIdentifier.java +++ b/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectCompositeIdentifier.java @@ -1,123 +1,133 @@ package com.commercetools.sync.customobjects.helpers; +import static java.lang.String.format; + import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.CustomObjectDraft; - -import javax.annotation.Nonnull; import java.util.Objects; - -import static java.lang.String.format; +import javax.annotation.Nonnull; /** * This class is only meant for the internal use of the commercetools-sync-java library. * - *

Since there is no one unique identifier for customObjects on the commercetools API, we uniquely identify - * a customObject as a composite id of the following fields: key, container. This class models such composite key. + *

Since there is no one unique identifier for customObjects on the commercetools API, we + * uniquely identify a customObject as a composite id of the following fields: key, container. This + * class models such composite key. */ public final class CustomObjectCompositeIdentifier { - private final String key; - private final String container; - private static final String WRONG_FORMAT_ERROR_MESSAGE = "The custom object identifier value: \"%s\" does not have " - + "the correct format. The correct format must have a vertical bar \"|\" character " - + "between the container and key."; + private final String key; + private final String container; + private static final String WRONG_FORMAT_ERROR_MESSAGE = + "The custom object identifier value: \"%s\" does not have " + + "the correct format. The correct format must have a vertical bar \"|\" character " + + "between the container and key."; - private CustomObjectCompositeIdentifier(@Nonnull final String key, - @Nonnull final String container) { - this.key = key; - this.container = container; - } + private CustomObjectCompositeIdentifier( + @Nonnull final String key, @Nonnull final String container) { + this.key = key; + this.container = container; + } - /** - * Given a {@link CustomObjectDraft}, creates a {@link CustomObjectCompositeIdentifier} using the following fields - * from the supplied {@link CustomObjectDraft}: - *

    - *
  1. {@link CustomObjectCompositeIdentifier#key}: key of {@link CustomObjectDraft#getKey()}
  2. - *
  3. {@link CustomObjectCompositeIdentifier#container}: container of {@link CustomObjectDraft#getContainer()}
  4. - * - *
- * - * @param customObjectDraft a composite id is built using its fields. - * @return a composite id comprised of the fields of the supplied {@link CustomObjectDraft}. - */ - @Nonnull - public static CustomObjectCompositeIdentifier of(@Nonnull final CustomObjectDraft customObjectDraft) { - return new CustomObjectCompositeIdentifier(customObjectDraft.getKey(), customObjectDraft.getContainer()); - } + /** + * Given a {@link CustomObjectDraft}, creates a {@link CustomObjectCompositeIdentifier} using the + * following fields from the supplied {@link CustomObjectDraft}: + * + *
    + *
  1. {@link CustomObjectCompositeIdentifier#key}: key of {@link CustomObjectDraft#getKey()} + *
  2. {@link CustomObjectCompositeIdentifier#container}: container of {@link + * CustomObjectDraft#getContainer()} + *
+ * + * @param customObjectDraft a composite id is built using its fields. + * @return a composite id comprised of the fields of the supplied {@link CustomObjectDraft}. + */ + @Nonnull + public static CustomObjectCompositeIdentifier of( + @Nonnull final CustomObjectDraft customObjectDraft) { + return new CustomObjectCompositeIdentifier( + customObjectDraft.getKey(), customObjectDraft.getContainer()); + } - /** - * Given a {@link CustomObjectDraft}, creates a {@link CustomObjectCompositeIdentifier} using the following fields - * from the supplied {@link CustomObject}: - *
    - *
  1. {@link CustomObjectCompositeIdentifier#key}: key of {@link CustomObject#getKey()}
  2. - *
  3. {@link CustomObjectCompositeIdentifier#container}: container of {@link CustomObject#getContainer()}
  4. - *
- * - * @param customObject a composite id is built using its fields. - * @return a composite id comprised of the fields of the supplied {@link CustomObject}. - */ - @Nonnull - public static CustomObjectCompositeIdentifier of(@Nonnull final CustomObject customObject) { - return new CustomObjectCompositeIdentifier(customObject.getKey(), customObject.getContainer()); - } + /** + * Given a {@link CustomObjectDraft}, creates a {@link CustomObjectCompositeIdentifier} using the + * following fields from the supplied {@link CustomObject}: + * + *
    + *
  1. {@link CustomObjectCompositeIdentifier#key}: key of {@link CustomObject#getKey()} + *
  2. {@link CustomObjectCompositeIdentifier#container}: container of {@link + * CustomObject#getContainer()} + *
+ * + * @param customObject a composite id is built using its fields. + * @return a composite id comprised of the fields of the supplied {@link CustomObject}. + */ + @Nonnull + public static CustomObjectCompositeIdentifier of(@Nonnull final CustomObject customObject) { + return new CustomObjectCompositeIdentifier(customObject.getKey(), customObject.getContainer()); + } - /** - * Given a {@link String} and {@link String}, creates a {@link CustomObjectCompositeIdentifier} using the following - * fields from the supplied attributes. - * - * @param key key of CustomerObject to build composite Id. - * @param container container of CustomerObject to build composite Id. - * @return a composite id comprised of the fields of the supplied {@code key} and {@code container}. - */ - @Nonnull - public static CustomObjectCompositeIdentifier of(@Nonnull final String key, @Nonnull final String container) { - return CustomObjectCompositeIdentifier.of( - CustomObjectDraft.ofUnversionedUpsert(container, key, null)); - } + /** + * Given a {@link String} and {@link String}, creates a {@link CustomObjectCompositeIdentifier} + * using the following fields from the supplied attributes. + * + * @param key key of CustomerObject to build composite Id. + * @param container container of CustomerObject to build composite Id. + * @return a composite id comprised of the fields of the supplied {@code key} and {@code + * container}. + */ + @Nonnull + public static CustomObjectCompositeIdentifier of( + @Nonnull final String key, @Nonnull final String container) { + return CustomObjectCompositeIdentifier.of( + CustomObjectDraft.ofUnversionedUpsert(container, key, null)); + } - /** - * Given a {@link String} with a format "container|key", creates a {@link CustomObjectCompositeIdentifier} using - * the following fields from the supplied identifier as string. - * - * @param identifierAsString string format of the identifier to build composite Id. - * @return a composite id comprised of the fields of the supplied {@code key} and {@code container}. - */ - @Nonnull - public static CustomObjectCompositeIdentifier of(@Nonnull final String identifierAsString) { - final String[] containerAndKey = identifierAsString.split("\\|"); - if (containerAndKey.length == 2) { - return of(containerAndKey[1], containerAndKey[0]); - } - throw new IllegalArgumentException(format(WRONG_FORMAT_ERROR_MESSAGE, identifierAsString)); + /** + * Given a {@link String} with a format "container|key", creates a {@link + * CustomObjectCompositeIdentifier} using the following fields from the supplied identifier as + * string. + * + * @param identifierAsString string format of the identifier to build composite Id. + * @return a composite id comprised of the fields of the supplied {@code key} and {@code + * container}. + */ + @Nonnull + public static CustomObjectCompositeIdentifier of(@Nonnull final String identifierAsString) { + final String[] containerAndKey = identifierAsString.split("\\|"); + if (containerAndKey.length == 2) { + return of(containerAndKey[1], containerAndKey[0]); } + throw new IllegalArgumentException(format(WRONG_FORMAT_ERROR_MESSAGE, identifierAsString)); + } - public String getKey() { - return this.key; - } + public String getKey() { + return this.key; + } - public String getContainer() { - return this.container; - } + public String getContainer() { + return this.container; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof CustomObjectCompositeIdentifier)) { - return false; - } - final CustomObjectCompositeIdentifier that = (CustomObjectCompositeIdentifier) obj; - return key.equals(that.key) && container.equals(that.container); + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; } - - @Override - public int hashCode() { - return Objects.hash(key, container); + if (!(obj instanceof CustomObjectCompositeIdentifier)) { + return false; } + final CustomObjectCompositeIdentifier that = (CustomObjectCompositeIdentifier) obj; + return key.equals(that.key) && container.equals(that.container); + } - @Override - public String toString() { - return format("%s|%s", container, key); - } + @Override + public int hashCode() { + return Objects.hash(key, container); + } + + @Override + public String toString() { + return format("%s|%s", container, key); + } } diff --git a/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectSyncStatistics.java b/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectSyncStatistics.java index 0374dc47b1..85f7b5ea78 100644 --- a/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectSyncStatistics.java +++ b/src/main/java/com/commercetools/sync/customobjects/helpers/CustomObjectSyncStatistics.java @@ -3,15 +3,17 @@ import com.commercetools.sync.commons.helpers.BaseSyncStatistics; public class CustomObjectSyncStatistics extends BaseSyncStatistics { - /** - * Builds a summary of the custom object sync statistics instance that looks like the following example: - * - *

"Summary: 2 custom objects were processed in total (0 created, 0 updated and 0 failed to sync)." - * - * @return a summary message of the custom objects sync statistics instance. - */ - @Override - public String getReportMessage() { - return getDefaultReportMessageForResource("custom objects"); - } + /** + * Builds a summary of the custom object sync statistics instance that looks like the following + * example: + * + *

"Summary: 2 custom objects were processed in total (0 created, 0 updated and 0 failed to + * sync)." + * + * @return a summary message of the custom objects sync statistics instance. + */ + @Override + public String getReportMessage() { + return getDefaultReportMessageForResource("custom objects"); + } } diff --git a/src/main/java/com/commercetools/sync/customobjects/utils/CustomObjectSyncUtils.java b/src/main/java/com/commercetools/sync/customobjects/utils/CustomObjectSyncUtils.java index 498da3941c..05db236878 100644 --- a/src/main/java/com/commercetools/sync/customobjects/utils/CustomObjectSyncUtils.java +++ b/src/main/java/com/commercetools/sync/customobjects/utils/CustomObjectSyncUtils.java @@ -3,26 +3,25 @@ import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.CustomObjectDraft; - import javax.annotation.Nonnull; public class CustomObjectSyncUtils { - /** - * Compares the value of a {@link CustomObject} to the value of a {@link CustomObjectDraft}. - * It returns a boolean whether the values are identical or not. - * - * @param oldCustomObject the {@link CustomObject} which should be synced. - * @param newCustomObject the {@link CustomObjectDraft} with the new data. - * @return A boolean whether the value of the CustomObject and CustomObjectDraft is identical or not. - */ - - public static boolean hasIdenticalValue( - @Nonnull final CustomObject oldCustomObject, - @Nonnull final CustomObjectDraft newCustomObject) { - JsonNode oldValue = oldCustomObject.getValue(); - JsonNode newValue = newCustomObject.getValue(); + /** + * Compares the value of a {@link CustomObject} to the value of a {@link CustomObjectDraft}. It + * returns a boolean whether the values are identical or not. + * + * @param oldCustomObject the {@link CustomObject} which should be synced. + * @param newCustomObject the {@link CustomObjectDraft} with the new data. + * @return A boolean whether the value of the CustomObject and CustomObjectDraft is identical or + * not. + */ + public static boolean hasIdenticalValue( + @Nonnull final CustomObject oldCustomObject, + @Nonnull final CustomObjectDraft newCustomObject) { + JsonNode oldValue = oldCustomObject.getValue(); + JsonNode newValue = newCustomObject.getValue(); - return oldValue.equals(newValue); - } -} \ No newline at end of file + return oldValue.equals(newValue); + } +} diff --git a/src/main/java/com/commercetools/sync/internals/helpers/PriceCompositeId.java b/src/main/java/com/commercetools/sync/internals/helpers/PriceCompositeId.java index 9a7469c210..374a3fb3a1 100644 --- a/src/main/java/com/commercetools/sync/internals/helpers/PriceCompositeId.java +++ b/src/main/java/com/commercetools/sync/internals/helpers/PriceCompositeId.java @@ -1,139 +1,146 @@ package com.commercetools.sync.internals.helpers; +import static java.util.Optional.ofNullable; + import com.neovisionaries.i18n.CountryCode; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.Price; import io.sphere.sdk.products.PriceDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.time.ZonedDateTime; import java.util.Objects; - -import static java.util.Optional.ofNullable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * This class is only meant for the internal use of the commercetools-sync-java library. * - *

Since there is no one unique identifier for prices on the commercetools API, we uniquely identify a price as a - * composite id of the following fields: countryCode, currencyCode, channelId, customerGroupId, validFrom and - * validUntil. This class models such composite key. + *

Since there is no one unique identifier for prices on the commercetools API, we uniquely + * identify a price as a composite id of the following fields: countryCode, currencyCode, channelId, + * customerGroupId, validFrom and validUntil. This class models such composite key. */ public final class PriceCompositeId { - private final CountryCode countryCode; - private final String currencyCode; - private final String channelId; - private final String customerGroupId; - private final ZonedDateTime validFrom; - private final ZonedDateTime validUntil; - - private PriceCompositeId(@Nonnull final String currencyCode, - @Nullable final CountryCode countryCode, - @Nullable final String channelId, - @Nullable final String customerGroupId, - @Nullable final ZonedDateTime validFrom, - @Nullable final ZonedDateTime validUntil) { - this.currencyCode = currencyCode; - this.countryCode = countryCode; - this.channelId = channelId; - this.customerGroupId = customerGroupId; - this.validFrom = validFrom; - this.validUntil = validUntil; - } - - - /** - * Given a {@link PriceDraft}, creates a {@link PriceCompositeId} using the following fields from the - * supplied {@link PriceDraft}: - *

    - *
  1. {@link PriceCompositeId#currencyCode}: CurrencyCode of {@link PriceDraft#getValue()}
  2. - *
  3. {@link PriceCompositeId#countryCode}: {@link PriceDraft#getCountry()}
  4. - *
  5. {@link PriceCompositeId#channelId}: id of {@link PriceDraft#getChannel()}
  6. - *
  7. {@link PriceCompositeId#customerGroupId}: id of {@link PriceDraft#getCustomerGroup()}
  8. - *
  9. {@link PriceCompositeId#validFrom}: {@link PriceDraft#getValidFrom()}
  10. - *
  11. {@link PriceCompositeId#validUntil}: {@link PriceDraft#getValidUntil()}
  12. - *
- * - * @param priceDraft a composite id is built using its fields. - * @return a composite id comprised of the fields of the supplied {@link PriceDraft}. - */ - @Nonnull - public static PriceCompositeId of(@Nonnull final PriceDraft priceDraft) { - return new PriceCompositeId(priceDraft.getValue().getCurrency().getCurrencyCode(), - priceDraft.getCountry(), ofNullable(priceDraft.getChannel()).map(ResourceIdentifier::getId) - .orElse(null), - ofNullable(priceDraft.getCustomerGroup()).map(Reference::getId).orElse(null), - priceDraft.getValidFrom(), priceDraft.getValidUntil()); - } - - /** - * Given a {@link Price}, creates a {@link PriceCompositeId} using the following fields from the - * supplied {@link Price}: - *
    - *
  1. {@link PriceCompositeId#currencyCode}: CurrencyCode of {@link Price#getValue()}
  2. - *
  3. {@link PriceCompositeId#countryCode}: {@link Price#getCountry()}
  4. - *
  5. {@link PriceCompositeId#channelId}: id of {@link Price#getChannel()}
  6. - *
  7. {@link PriceCompositeId#customerGroupId}: id of {@link Price#getCustomerGroup()}
  8. - *
  9. {@link PriceCompositeId#validFrom}: {@link Price#getValidFrom()}
  10. - *
  11. {@link PriceCompositeId#validUntil}: {@link Price#getValidUntil()}
  12. - *
- * - * @param price a composite id is built using its fields. - * @return a composite id comprised of the fields of the supplied {@link Price}. - */ - @Nonnull - public static PriceCompositeId of(@Nonnull final Price price) { - return new PriceCompositeId(price.getValue().getCurrency().getCurrencyCode(), - price.getCountry(), ofNullable(price.getChannel()).map(Reference::getId).orElse(null), - ofNullable(price.getCustomerGroup()).map(Reference::getId).orElse(null), price.getValidFrom(), - price.getValidUntil()); + private final CountryCode countryCode; + private final String currencyCode; + private final String channelId; + private final String customerGroupId; + private final ZonedDateTime validFrom; + private final ZonedDateTime validUntil; + + private PriceCompositeId( + @Nonnull final String currencyCode, + @Nullable final CountryCode countryCode, + @Nullable final String channelId, + @Nullable final String customerGroupId, + @Nullable final ZonedDateTime validFrom, + @Nullable final ZonedDateTime validUntil) { + this.currencyCode = currencyCode; + this.countryCode = countryCode; + this.channelId = channelId; + this.customerGroupId = customerGroupId; + this.validFrom = validFrom; + this.validUntil = validUntil; + } + + /** + * Given a {@link PriceDraft}, creates a {@link PriceCompositeId} using the following fields from + * the supplied {@link PriceDraft}: + * + *
    + *
  1. {@link PriceCompositeId#currencyCode}: CurrencyCode of {@link PriceDraft#getValue()} + *
  2. {@link PriceCompositeId#countryCode}: {@link PriceDraft#getCountry()} + *
  3. {@link PriceCompositeId#channelId}: id of {@link PriceDraft#getChannel()} + *
  4. {@link PriceCompositeId#customerGroupId}: id of {@link PriceDraft#getCustomerGroup()} + *
  5. {@link PriceCompositeId#validFrom}: {@link PriceDraft#getValidFrom()} + *
  6. {@link PriceCompositeId#validUntil}: {@link PriceDraft#getValidUntil()} + *
+ * + * @param priceDraft a composite id is built using its fields. + * @return a composite id comprised of the fields of the supplied {@link PriceDraft}. + */ + @Nonnull + public static PriceCompositeId of(@Nonnull final PriceDraft priceDraft) { + return new PriceCompositeId( + priceDraft.getValue().getCurrency().getCurrencyCode(), + priceDraft.getCountry(), + ofNullable(priceDraft.getChannel()).map(ResourceIdentifier::getId).orElse(null), + ofNullable(priceDraft.getCustomerGroup()).map(Reference::getId).orElse(null), + priceDraft.getValidFrom(), + priceDraft.getValidUntil()); + } + + /** + * Given a {@link Price}, creates a {@link PriceCompositeId} using the following fields from the + * supplied {@link Price}: + * + *
    + *
  1. {@link PriceCompositeId#currencyCode}: CurrencyCode of {@link Price#getValue()} + *
  2. {@link PriceCompositeId#countryCode}: {@link Price#getCountry()} + *
  3. {@link PriceCompositeId#channelId}: id of {@link Price#getChannel()} + *
  4. {@link PriceCompositeId#customerGroupId}: id of {@link Price#getCustomerGroup()} + *
  5. {@link PriceCompositeId#validFrom}: {@link Price#getValidFrom()} + *
  6. {@link PriceCompositeId#validUntil}: {@link Price#getValidUntil()} + *
+ * + * @param price a composite id is built using its fields. + * @return a composite id comprised of the fields of the supplied {@link Price}. + */ + @Nonnull + public static PriceCompositeId of(@Nonnull final Price price) { + return new PriceCompositeId( + price.getValue().getCurrency().getCurrencyCode(), + price.getCountry(), + ofNullable(price.getChannel()).map(Reference::getId).orElse(null), + ofNullable(price.getCustomerGroup()).map(Reference::getId).orElse(null), + price.getValidFrom(), + price.getValidUntil()); + } + + public CountryCode getCountryCode() { + return countryCode; + } + + public String getCurrencyCode() { + return currencyCode; + } + + public String getChannelId() { + return channelId; + } + + public String getCustomerGroupId() { + return customerGroupId; + } + + public ZonedDateTime getValidFrom() { + return validFrom; + } + + public ZonedDateTime getValidUntil() { + return validUntil; + } + + @Override + public boolean equals(final Object otherObject) { + if (this == otherObject) { + return true; } - - public CountryCode getCountryCode() { - return countryCode; - } - - public String getCurrencyCode() { - return currencyCode; - } - - public String getChannelId() { - return channelId; - } - - public String getCustomerGroupId() { - return customerGroupId; - } - - public ZonedDateTime getValidFrom() { - return validFrom; - } - - public ZonedDateTime getValidUntil() { - return validUntil; - } - - @Override - public boolean equals(final Object otherObject) { - if (this == otherObject) { - return true; - } - if (!(otherObject instanceof PriceCompositeId)) { - return false; - } - final PriceCompositeId that = (PriceCompositeId) otherObject; - return countryCode == that.countryCode - && Objects.equals(currencyCode, that.currencyCode) - && Objects.equals(channelId, that.channelId) - && Objects.equals(customerGroupId, that.customerGroupId) - && Objects.equals(validFrom, that.validFrom) - && Objects.equals(validUntil, that.validUntil); - } - - @Override - public int hashCode() { - return Objects.hash(countryCode, currencyCode, channelId, customerGroupId, validFrom, validUntil); + if (!(otherObject instanceof PriceCompositeId)) { + return false; } + final PriceCompositeId that = (PriceCompositeId) otherObject; + return countryCode == that.countryCode + && Objects.equals(currencyCode, that.currencyCode) + && Objects.equals(channelId, that.channelId) + && Objects.equals(customerGroupId, that.customerGroupId) + && Objects.equals(validFrom, that.validFrom) + && Objects.equals(validUntil, that.validUntil); + } + + @Override + public int hashCode() { + return Objects.hash( + countryCode, currencyCode, channelId, customerGroupId, validFrom, validUntil); + } } diff --git a/src/main/java/com/commercetools/sync/internals/utils/UnorderedCollectionSyncUtils.java b/src/main/java/com/commercetools/sync/internals/utils/UnorderedCollectionSyncUtils.java index db8624b7ca..0127896db9 100644 --- a/src/main/java/com/commercetools/sync/internals/utils/UnorderedCollectionSyncUtils.java +++ b/src/main/java/com/commercetools/sync/internals/utils/UnorderedCollectionSyncUtils.java @@ -1,72 +1,68 @@ package com.commercetools.sync.internals.utils; -import io.sphere.sdk.commands.UpdateAction; +import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToMap; +import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; +import static java.util.stream.Collectors.toList; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import io.sphere.sdk.commands.UpdateAction; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToMap; -import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; -import static java.util.stream.Collectors.toList; - -/** - * This class is only meant for the internal use of the commercetools-sync-java library. - */ +/** This class is only meant for the internal use of the commercetools-sync-java library. */ public final class UnorderedCollectionSyncUtils { - /** - * Compares a list of {@code newDrafts} with a list of {@code oldResources} and for every missing matching draft - * in the {@code oldResources}: a remove update action is created using the {@code removeUpdateActionMapper}. - * The final result is a list of all the remove update actions. - * - *

The resulting list of update actions is ensured to contain no null values. - * - *

If the draft has null key as defined by {@code draftKeyMapper}, the method will ignore generating a remove - * update action for it. - * - * @param oldResources a list of the old resource to compare to the new collection. - * @param newDrafts a list of the new drafts to compare to the old collection. - * @param oldResourceKeyMapper a function that uses the old resource to get its key matcher. - * @param draftKeyMapper a function that uses the draft to get its key matcher. - * @param removeUpdateActionMapper a function that uses the old resource to build a remove update action. - * @param type of the resulting update actions. - * @param type of the new resource key. - * @param type of the old resource. - * @param type of the new resource. - * @return a list of all the remove update actions. If there are no missing matching drafts, an empty list is - * returned. - */ - @Nonnull - public static List> buildRemoveUpdateActions( - @Nonnull final List oldResources, - @Nullable final List newDrafts, - @Nonnull final Function oldResourceKeyMapper, - @Nonnull final Function draftKeyMapper, - @Nonnull final Function> removeUpdateActionMapper) { + /** + * Compares a list of {@code newDrafts} with a list of {@code oldResources} and for every missing + * matching draft in the {@code oldResources}: a remove update action is created using the {@code + * removeUpdateActionMapper}. The final result is a list of all the remove update actions. + * + *

The resulting list of update actions is ensured to contain no null values. + * + *

If the draft has null key as defined by {@code draftKeyMapper}, the method will ignore + * generating a remove update action for it. + * + * @param oldResources a list of the old resource to compare to the new collection. + * @param newDrafts a list of the new drafts to compare to the old collection. + * @param oldResourceKeyMapper a function that uses the old resource to get its key matcher. + * @param draftKeyMapper a function that uses the draft to get its key matcher. + * @param removeUpdateActionMapper a function that uses the old resource to build a remove update + * action. + * @param type of the resulting update actions. + * @param type of the new resource key. + * @param type of the old resource. + * @param type of the new resource. + * @return a list of all the remove update actions. If there are no missing matching drafts, an + * empty list is returned. + */ + @Nonnull + public static List> buildRemoveUpdateActions( + @Nonnull final List oldResources, + @Nullable final List newDrafts, + @Nonnull final Function oldResourceKeyMapper, + @Nonnull final Function draftKeyMapper, + @Nonnull final Function> removeUpdateActionMapper) { - final Map oldResourcesMap = collectionToMap(oldResources, oldResourceKeyMapper); - oldResourcesMap.remove(null); + final Map oldResourcesMap = collectionToMap(oldResources, oldResourceKeyMapper); + oldResourcesMap.remove(null); - final Map resourcesToRemove = new HashMap<>(oldResourcesMap); + final Map resourcesToRemove = new HashMap<>(oldResourcesMap); - emptyIfNull(newDrafts).stream() - .filter(Objects::nonNull) - .map(draftKeyMapper) - .filter(Objects::nonNull) - .forEach(resourcesToRemove::remove); + emptyIfNull(newDrafts).stream() + .filter(Objects::nonNull) + .map(draftKeyMapper) + .filter(Objects::nonNull) + .forEach(resourcesToRemove::remove); - return resourcesToRemove.values() - .stream() - .map(removeUpdateActionMapper) - .filter(Objects::nonNull) - .collect(toList()); - } + return resourcesToRemove.values().stream() + .map(removeUpdateActionMapper) + .filter(Objects::nonNull) + .collect(toList()); + } - private UnorderedCollectionSyncUtils() { - } -} \ No newline at end of file + private UnorderedCollectionSyncUtils() {} +} diff --git a/src/main/java/com/commercetools/sync/internals/utils/UpdateActionsSortUtils.java b/src/main/java/com/commercetools/sync/internals/utils/UpdateActionsSortUtils.java index 76477f55f2..b36b7ecdf8 100644 --- a/src/main/java/com/commercetools/sync/internals/utils/UpdateActionsSortUtils.java +++ b/src/main/java/com/commercetools/sync/internals/utils/UpdateActionsSortUtils.java @@ -7,57 +7,56 @@ import io.sphere.sdk.products.commands.updateactions.RemovePrice; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomField; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomType; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nonnull; -/** - * This class is only meant for the internal use of the commercetools-sync-java library. - */ +/** This class is only meant for the internal use of the commercetools-sync-java library. */ public final class UpdateActionsSortUtils { - /** - * Given a list of update actions, this method returns a copy of the supplied list but sorted with the following - * precedence: - *

    - *
  1. {@link RemovePrice}
  2. - *
  3. {@link ChangePrice} or {@link SetProductPriceCustomType} or {@link SetProductPriceCustomField}
  4. - *
  5. {@link AddPrice}
  6. - *
- * - *

This is to ensure that there are no conflicts when adding a new price that might have a duplicate value for - * a unique field, which could already be changed or removed. - * - * @param updateActions list of update actions to sort. - * @return a new sorted list of update actions (remove, change, add). - */ - @Nonnull - public static List> sortPriceActions( - @Nonnull final List> updateActions) { - - final List> actionsCopy = new ArrayList<>(updateActions); - actionsCopy.sort((action1, action2) -> { - if (action1 instanceof RemovePrice && !(action2 instanceof RemovePrice)) { - return -1; - } - - if (!(action1 instanceof RemovePrice) && action2 instanceof RemovePrice) { - return 1; - } - - if (!(action1 instanceof AddPrice) && action2 instanceof AddPrice) { - return -1; - } - - if (action1 instanceof AddPrice && !(action2 instanceof AddPrice)) { - return 1; - } - - return 0; + /** + * Given a list of update actions, this method returns a copy of the supplied list but sorted with + * the following precedence: + * + *

    + *
  1. {@link RemovePrice} + *
  2. {@link ChangePrice} or {@link SetProductPriceCustomType} or {@link + * SetProductPriceCustomField} + *
  3. {@link AddPrice} + *
+ * + *

This is to ensure that there are no conflicts when adding a new price that might have a + * duplicate value for a unique field, which could already be changed or removed. + * + * @param updateActions list of update actions to sort. + * @return a new sorted list of update actions (remove, change, add). + */ + @Nonnull + public static List> sortPriceActions( + @Nonnull final List> updateActions) { + + final List> actionsCopy = new ArrayList<>(updateActions); + actionsCopy.sort( + (action1, action2) -> { + if (action1 instanceof RemovePrice && !(action2 instanceof RemovePrice)) { + return -1; + } + + if (!(action1 instanceof RemovePrice) && action2 instanceof RemovePrice) { + return 1; + } + + if (!(action1 instanceof AddPrice) && action2 instanceof AddPrice) { + return -1; + } + + if (action1 instanceof AddPrice && !(action2 instanceof AddPrice)) { + return 1; + } + + return 0; }); - return actionsCopy; - } + return actionsCopy; + } - private UpdateActionsSortUtils() { - } + private UpdateActionsSortUtils() {} } diff --git a/src/main/java/com/commercetools/sync/inventories/InventorySync.java b/src/main/java/com/commercetools/sync/inventories/InventorySync.java index b133f3cc13..2db12928b6 100644 --- a/src/main/java/com/commercetools/sync/inventories/InventorySync.java +++ b/src/main/java/com/commercetools/sync/inventories/InventorySync.java @@ -1,5 +1,13 @@ package com.commercetools.sync.inventories; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static com.commercetools.sync.inventories.utils.InventorySyncUtils.buildActions; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.inventories.helpers.InventoryBatchValidator; @@ -18,10 +26,6 @@ import io.sphere.sdk.inventory.InventoryEntry; import io.sphere.sdk.inventory.InventoryEntryDraft; import io.sphere.sdk.models.ResourceIdentifier; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.Map; @@ -30,289 +34,310 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static com.commercetools.sync.inventories.utils.InventorySyncUtils.buildActions; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; - -/** - * Default implementation of inventories sync process. - */ -public final class InventorySync extends BaseSync { - - private static final String CTP_INVENTORY_FETCH_FAILED = "Failed to fetch existing inventory entries of SKUs %s."; - private static final String CTP_INVENTORY_ENTRY_UPDATE_FAILED = "Failed to update inventory entry of SKU '%s' and " - + "supply channel id '%s'."; - private static final String FAILED_TO_PROCESS = "Failed to process the InventoryEntryDraft with SKU:'%s'. " - + "Reason: %s"; - - private final InventoryService inventoryService; - private final InventoryReferenceResolver referenceResolver; - private final InventoryBatchValidator batchValidator; - - /** - * Takes a {@link InventorySyncOptions} instance to instantiate a new {@link InventorySync} instance that could be - * used to sync inventory drafts with the given inventory entries in the CTP project specified in the injected - * {@link InventorySyncOptions} instance. - * - * @param syncOptions the container of all the options of the sync process including the CTP project client and/or - * configuration and other sync-specific options. - */ - public InventorySync(@Nonnull final InventorySyncOptions syncOptions) { - this(syncOptions, new InventoryServiceImpl(syncOptions), - new ChannelServiceImpl(syncOptions, Collections.singleton(ChannelRole.INVENTORY_SUPPLY)), - new TypeServiceImpl(syncOptions)); - } - - InventorySync(@Nonnull final InventorySyncOptions syncOptions, @Nonnull final InventoryService inventoryService, - @Nonnull final ChannelService channelService, @Nonnull final TypeService typeService) { - super(new InventorySyncStatistics(), syncOptions); - this.inventoryService = inventoryService; - this.referenceResolver = new InventoryReferenceResolver(getSyncOptions(), typeService, channelService); - this.batchValidator = new InventoryBatchValidator(getSyncOptions(), getStatistics()); - } - - /** - * Iterates through the whole {@code inventories} list and accumulates its valid drafts to batches. Every batch - * is then processed by {@link InventorySync#processBatch(List)}. - * - *

Inherited doc: - * {@inheritDoc} - * - * @param inventoryEntryDrafts {@link List} of {@link InventoryEntryDraft} resources that would be synced into CTP - * project. - * @return {@link CompletionStage} with {@link InventorySyncStatistics} holding statistics of all sync - * processes performed by this sync instance - */ - @Nonnull - @Override - protected CompletionStage process( - @Nonnull final List inventoryEntryDrafts) { - - final List> batches = batchElements(inventoryEntryDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, CompletableFuture.completedFuture(statistics)); +/** Default implementation of inventories sync process. */ +public final class InventorySync + extends BaseSync { + + private static final String CTP_INVENTORY_FETCH_FAILED = + "Failed to fetch existing inventory entries of SKUs %s."; + private static final String CTP_INVENTORY_ENTRY_UPDATE_FAILED = + "Failed to update inventory entry of SKU '%s' and " + "supply channel id '%s'."; + private static final String FAILED_TO_PROCESS = + "Failed to process the InventoryEntryDraft with SKU:'%s'. " + "Reason: %s"; + + private final InventoryService inventoryService; + private final InventoryReferenceResolver referenceResolver; + private final InventoryBatchValidator batchValidator; + + /** + * Takes a {@link InventorySyncOptions} instance to instantiate a new {@link InventorySync} + * instance that could be used to sync inventory drafts with the given inventory entries in the + * CTP project specified in the injected {@link InventorySyncOptions} instance. + * + * @param syncOptions the container of all the options of the sync process including the CTP + * project client and/or configuration and other sync-specific options. + */ + public InventorySync(@Nonnull final InventorySyncOptions syncOptions) { + this( + syncOptions, + new InventoryServiceImpl(syncOptions), + new ChannelServiceImpl(syncOptions, Collections.singleton(ChannelRole.INVENTORY_SUPPLY)), + new TypeServiceImpl(syncOptions)); + } + + InventorySync( + @Nonnull final InventorySyncOptions syncOptions, + @Nonnull final InventoryService inventoryService, + @Nonnull final ChannelService channelService, + @Nonnull final TypeService typeService) { + super(new InventorySyncStatistics(), syncOptions); + this.inventoryService = inventoryService; + this.referenceResolver = + new InventoryReferenceResolver(getSyncOptions(), typeService, channelService); + this.batchValidator = new InventoryBatchValidator(getSyncOptions(), getStatistics()); + } + + /** + * Iterates through the whole {@code inventories} list and accumulates its valid drafts to + * batches. Every batch is then processed by {@link InventorySync#processBatch(List)}. + * + *

Inherited doc: {@inheritDoc} + * + * @param inventoryEntryDrafts {@link List} of {@link InventoryEntryDraft} resources that would be + * synced into CTP project. + * @return {@link CompletionStage} with {@link InventorySyncStatistics} holding statistics of all + * sync processes performed by this sync instance + */ + @Nonnull + @Override + protected CompletionStage process( + @Nonnull final List inventoryEntryDrafts) { + + final List> batches = + batchElements(inventoryEntryDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, CompletableFuture.completedFuture(statistics)); + } + + /** + * Fetches existing {@link InventoryEntry} objects from CTP project that correspond to passed + * {@code batchOfDrafts}. Having existing inventory entries fetched, {@code batchOfDrafts} is + * compared and synced with fetched objects by {@link InventorySync#syncBatch(Set, Set)} function. + * When fetching existing inventory entries results in an empty optional then {@code + * batchOfDrafts} isn't processed. + * + * @param batch batch of drafts that need to be synced + * @return {@link CompletionStage} of {@link Void} that indicates method progress. + */ + protected CompletionStage processBatch( + @Nonnull final List batch) { + + final ImmutablePair, InventoryBatchValidator.ReferencedKeys> result = + batchValidator.validateAndCollectReferencedKeys(batch); + + final Set validDrafts = result.getLeft(); + if (validDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return CompletableFuture.completedFuture(statistics); } - /** - * Fetches existing {@link InventoryEntry} objects from CTP project that correspond to passed {@code batchOfDrafts}. - * Having existing inventory entries fetched, {@code batchOfDrafts} is compared and synced with fetched objects by - * {@link InventorySync#syncBatch(Set, Set)} function. When fetching existing inventory entries results in - * an empty optional then {@code batchOfDrafts} isn't processed. - * - * @param batch batch of drafts that need to be synced - * @return {@link CompletionStage} of {@link Void} that indicates method progress. - */ - protected CompletionStage processBatch(@Nonnull final List batch) { - - final ImmutablePair, InventoryBatchValidator.ReferencedKeys> result = - batchValidator.validateAndCollectReferencedKeys(batch); - - final Set validDrafts = result.getLeft(); - if (validDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return CompletableFuture.completedFuture(statistics); - } - - return referenceResolver - .populateKeyToIdCachesForReferencedKeys(result.getRight()) - .handle(ImmutablePair::new) - .thenCompose(cachingResponse -> { - final Throwable cachingException = cachingResponse.getValue(); - if (cachingException != null) { - handleError(new SyncException("Failed to build a cache of keys to ids.", cachingException), - validDrafts.size()); - return CompletableFuture.completedFuture(null); - } - - final Set skus = validDrafts - .stream() - .map(InventoryEntryDraft::getSku) - .collect(Collectors.toSet()); - - return inventoryService - .fetchInventoryEntriesBySkus(skus) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { + return referenceResolver + .populateKeyToIdCachesForReferencedKeys(result.getRight()) + .handle(ImmutablePair::new) + .thenCompose( + cachingResponse -> { + final Throwable cachingException = cachingResponse.getValue(); + if (cachingException != null) { + handleError( + new SyncException("Failed to build a cache of keys to ids.", cachingException), + validDrafts.size()); + return CompletableFuture.completedFuture(null); + } + + final Set skus = + validDrafts.stream().map(InventoryEntryDraft::getSku).collect(Collectors.toSet()); + + return inventoryService + .fetchInventoryEntriesBySkus(skus) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { final Set fetchedInventoryEntries = fetchResponse.getKey(); final Throwable exception = fetchResponse.getValue(); if (exception != null) { - final String errorMessage = format(CTP_INVENTORY_FETCH_FAILED, skus); - handleError(new SyncException(errorMessage, exception), skus.size()); - return CompletableFuture.completedFuture(null); + final String errorMessage = format(CTP_INVENTORY_FETCH_FAILED, skus); + handleError(new SyncException(errorMessage, exception), skus.size()); + return CompletableFuture.completedFuture(null); } else { - return syncBatch(fetchedInventoryEntries, validDrafts); + return syncBatch(fetchedInventoryEntries, validDrafts); } - }); + }); }) - .thenApply(ignored -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignored -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); - } - - - /** - * Given a list of inventory entry {@code drafts}, this method resolves the references of each entry and attempts to - * sync it to the CTP project depending whether the references resolution was successful. In addition the given - * {@code oldInventories} list is converted to a {@link Map} of an identifier to an inventory entry, for a resources - * comparison reason. - * - * @param oldInventories inventory entries from CTP - * @param inventoryEntryDrafts drafts that need to be synced - * @return a future which contains an empty result after execution of the update - */ - private CompletionStage syncBatch( - @Nonnull final Set oldInventories, - @Nonnull final Set inventoryEntryDrafts) { - - final Map oldInventoryMap = - oldInventories.stream().collect(toMap(InventoryEntryIdentifier::of, identity())); - - return CompletableFuture.allOf(inventoryEntryDrafts - .stream() - .map(newInventoryEntry -> - referenceResolver - .resolveReferences(newInventoryEntry) - .thenCompose(resolvedDraft -> syncDraft(oldInventoryMap, resolvedDraft)) - .exceptionally(completionException -> { - final String errorMessage = format(FAILED_TO_PROCESS, - newInventoryEntry.getSku(), completionException.getMessage()); - handleError(new SyncException(errorMessage, completionException), 1); - return Optional.empty(); - }) - ) + } + + /** + * Given a list of inventory entry {@code drafts}, this method resolves the references of each + * entry and attempts to sync it to the CTP project depending whether the references resolution + * was successful. In addition the given {@code oldInventories} list is converted to a {@link Map} + * of an identifier to an inventory entry, for a resources comparison reason. + * + * @param oldInventories inventory entries from CTP + * @param inventoryEntryDrafts drafts that need to be synced + * @return a future which contains an empty result after execution of the update + */ + private CompletionStage syncBatch( + @Nonnull final Set oldInventories, + @Nonnull final Set inventoryEntryDrafts) { + + final Map oldInventoryMap = + oldInventories.stream().collect(toMap(InventoryEntryIdentifier::of, identity())); + + return CompletableFuture.allOf( + inventoryEntryDrafts.stream() + .map( + newInventoryEntry -> + referenceResolver + .resolveReferences(newInventoryEntry) + .thenCompose(resolvedDraft -> syncDraft(oldInventoryMap, resolvedDraft)) + .exceptionally( + completionException -> { + final String errorMessage = + format( + FAILED_TO_PROCESS, + newInventoryEntry.getSku(), + completionException.getMessage()); + handleError(new SyncException(errorMessage, completionException), 1); + return Optional.empty(); + })) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); + } + + /** + * Checks if the {@code resolvedDraft} matches with an old existing inventory entry. If it does, + * it tries to update it. If it doesn't, it creates it. + * + * @param oldInventories map of {@link InventoryEntryIdentifier} to old {@link InventoryEntry} + * instances + * @param resolvedDraft inventory entry draft which has its references resolved + * @return a future which contains an empty result after execution of the update + */ + private CompletionStage> syncDraft( + @Nonnull final Map oldInventories, + @Nonnull final InventoryEntryDraft resolvedDraft) { + + final InventoryEntry oldInventory = + oldInventories.get(InventoryEntryIdentifier.of(resolvedDraft)); + + return ofNullable(oldInventory) + .map(type -> buildActionsAndUpdate(oldInventory, resolvedDraft)) + .orElseGet(() -> applyCallbackAndCreate(resolvedDraft)); + } + + /** + * Given an existing {@link InventoryEntry} and a new {@link InventoryEntryDraft}, the method + * calculates all the update actions required to synchronize the existing entry to be the same as + * the new one. If there are update actions found, a request is made to CTP to update the existing + * entry, otherwise it doesn't issue a request. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP, + * the error handling method is called. + * + * @param entry existing inventory entry that could be updated. + * @param draft draft containing data that could differ from data in {@code entry}. Sku + * isn't compared + * @return a future which contains an empty result after execution of the update. + */ + private CompletionStage> buildActionsAndUpdate( + @Nonnull final InventoryEntry entry, @Nonnull final InventoryEntryDraft draft) { + + final List> updateActions = + buildActions(entry, draft, syncOptions); + + final List> beforeUpdateCallBackApplied = + syncOptions.applyBeforeUpdateCallback(updateActions, draft, entry); + + if (!beforeUpdateCallBackApplied.isEmpty()) { + return inventoryService + .updateInventoryEntry(entry, beforeUpdateCallBackApplied) + .handle(ImmutablePair::new) + .thenCompose( + updateResponse -> { + final InventoryEntry updatedInventoryEntry = updateResponse.getKey(); + final Throwable sphereException = updateResponse.getValue(); + if (sphereException != null) { + final ResourceIdentifier supplyChannel = draft.getSupplyChannel(); + final String errorMessage = + format( + CTP_INVENTORY_ENTRY_UPDATE_FAILED, + draft.getSku(), + supplyChannel != null ? supplyChannel.getId() : null); + handleError( + new SyncException(errorMessage, sphereException), + entry, + draft, + updateActions); + return CompletableFuture.completedFuture(Optional.empty()); + } else { + statistics.incrementUpdated(); + return CompletableFuture.completedFuture(Optional.of(updatedInventoryEntry)); + } + }); } - - /** - * Checks if the {@code resolvedDraft} matches with an old existing inventory entry. If it does, it tries to update - * it. If it doesn't, it creates it. - * - * @param oldInventories map of {@link InventoryEntryIdentifier} to old {@link InventoryEntry} instances - * @param resolvedDraft inventory entry draft which has its references resolved - * @return a future which contains an empty result after execution of the update - */ - private CompletionStage> syncDraft( - @Nonnull final Map oldInventories, - @Nonnull final InventoryEntryDraft resolvedDraft) { - - final InventoryEntry oldInventory = oldInventories.get(InventoryEntryIdentifier.of(resolvedDraft)); - - return ofNullable(oldInventory) - .map(type -> buildActionsAndUpdate(oldInventory, resolvedDraft)) - .orElseGet(() -> applyCallbackAndCreate(resolvedDraft)); - } - - /** - * Given an existing {@link InventoryEntry} and a new {@link InventoryEntryDraft}, the method calculates all the - * update actions required to synchronize the existing entry to be the same as the new one. If there are update - * actions found, a request is made to CTP to update the existing entry, otherwise it doesn't issue a request. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP, the error handling method - * is called. - * - * @param entry existing inventory entry that could be updated. - * @param draft draft containing data that could differ from data in {@code entry}. - * Sku isn't compared - * @return a future which contains an empty result after execution of the update. - */ - private CompletionStage> buildActionsAndUpdate( - @Nonnull final InventoryEntry entry, - @Nonnull final InventoryEntryDraft draft) { - - final List> updateActions = buildActions(entry, draft, syncOptions); - - final List> beforeUpdateCallBackApplied = - syncOptions.applyBeforeUpdateCallback(updateActions, draft, entry); - - if (!beforeUpdateCallBackApplied.isEmpty()) { - return inventoryService - .updateInventoryEntry(entry, beforeUpdateCallBackApplied) - .handle(ImmutablePair::new) - .thenCompose(updateResponse -> { - final InventoryEntry updatedInventoryEntry = updateResponse.getKey(); - final Throwable sphereException = updateResponse.getValue(); - if (sphereException != null) { - final ResourceIdentifier supplyChannel = draft.getSupplyChannel(); - final String errorMessage = format(CTP_INVENTORY_ENTRY_UPDATE_FAILED, draft.getSku(), - supplyChannel != null ? supplyChannel.getId() : null); - handleError(new SyncException(errorMessage, sphereException), entry, draft, updateActions); - return CompletableFuture.completedFuture(Optional.empty()); - } else { - statistics.incrementUpdated(); - return CompletableFuture.completedFuture(Optional.of(updatedInventoryEntry)); - } - }); - } - return completedFuture(null); - } - - /** - * Given an inventory entry {@code draft}, issues a request to the CTP project to create a corresponding Inventory - * Entry. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP, the error handling method - * is called. - * - * @param inventoryEntryDraft the inventory entry draft to create the inventory entry from. - * @return a future which contains an empty result after execution of the create. - */ - private CompletionStage> applyCallbackAndCreate( - @Nonnull final InventoryEntryDraft inventoryEntryDraft) { - - return syncOptions - .applyBeforeCreateCallback(inventoryEntryDraft) - .map(draft -> inventoryService - .createInventoryEntry(draft) - .thenApply(inventoryEntryOptional -> { - if (inventoryEntryOptional.isPresent()) { - statistics.incrementCreated(); - } else { - statistics.incrementFailed(); - } - return inventoryEntryOptional; - }) - ) - .orElse(CompletableFuture.completedFuture(Optional.empty())); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed categories to sync. - * - * @param syncException The exception that caused the failure. - * @param failedTimes The number of times that the failed counter is incremented. - */ - private void handleError(@Nonnull final SyncException syncException, - final int failedTimes) { - syncOptions.applyErrorCallback(syncException); - statistics.incrementFailed(failedTimes); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed categories to sync. - * - * @param syncException The exception that caused the failure. - * @param entry existing inventory entry that could be updated. - * @param draft draft containing data that could differ from data in {@code entry}. - * @param updateActions the update actions to update the {@link InventoryEntry} with. - */ - private void handleError(@Nonnull final SyncException syncException, - @Nullable final InventoryEntry entry, - @Nullable final InventoryEntryDraft draft, - @Nullable final List> updateActions) { - syncOptions.applyErrorCallback(syncException, entry, draft, updateActions); - statistics.incrementFailed(1); - } + return completedFuture(null); + } + + /** + * Given an inventory entry {@code draft}, issues a request to the CTP project to create a + * corresponding Inventory Entry. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP, + * the error handling method is called. + * + * @param inventoryEntryDraft the inventory entry draft to create the inventory entry from. + * @return a future which contains an empty result after execution of the create. + */ + private CompletionStage> applyCallbackAndCreate( + @Nonnull final InventoryEntryDraft inventoryEntryDraft) { + + return syncOptions + .applyBeforeCreateCallback(inventoryEntryDraft) + .map( + draft -> + inventoryService + .createInventoryEntry(draft) + .thenApply( + inventoryEntryOptional -> { + if (inventoryEntryOptional.isPresent()) { + statistics.incrementCreated(); + } else { + statistics.incrementFailed(); + } + return inventoryEntryOptional; + })) + .orElse(CompletableFuture.completedFuture(Optional.empty())); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed categories to sync. + * + * @param syncException The exception that caused the failure. + * @param failedTimes The number of times that the failed counter is incremented. + */ + private void handleError(@Nonnull final SyncException syncException, final int failedTimes) { + syncOptions.applyErrorCallback(syncException); + statistics.incrementFailed(failedTimes); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed categories to sync. + * + * @param syncException The exception that caused the failure. + * @param entry existing inventory entry that could be updated. + * @param draft draft containing data that could differ from data in {@code entry}. + * @param updateActions the update actions to update the {@link InventoryEntry} with. + */ + private void handleError( + @Nonnull final SyncException syncException, + @Nullable final InventoryEntry entry, + @Nullable final InventoryEntryDraft draft, + @Nullable final List> updateActions) { + syncOptions.applyErrorCallback(syncException, entry, draft, updateActions); + statistics.incrementFailed(1); + } } diff --git a/src/main/java/com/commercetools/sync/inventories/InventorySyncOptions.java b/src/main/java/com/commercetools/sync/inventories/InventorySyncOptions.java index f2d2a6f1d7..d0be8f87dd 100644 --- a/src/main/java/com/commercetools/sync/inventories/InventorySyncOptions.java +++ b/src/main/java/com/commercetools/sync/inventories/InventorySyncOptions.java @@ -9,45 +9,55 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.inventory.InventoryEntry; import io.sphere.sdk.inventory.InventoryEntryDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -public final class InventorySyncOptions extends BaseSyncOptions { - private final boolean ensureChannels; - - InventorySyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallback, - @Nullable final TriConsumer, Optional> - warningCallback, - final int batchSize, - boolean ensureChannels, - @Nullable final TriFunction>, InventoryEntryDraft, - InventoryEntry, List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, final long cacheSize) { - super(ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize); - this.ensureChannels = ensureChannels; - - } - - /** - * @return option that indicates whether the sync process should create a supply channel of the given key - * when it doesn't exist in a target system yet. - */ - public boolean shouldEnsureChannels() { - return ensureChannels; - } - - +public final class InventorySyncOptions + extends BaseSyncOptions { + private final boolean ensureChannels; + + InventorySyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallback, + @Nullable + final TriConsumer, Optional> + warningCallback, + final int batchSize, + boolean ensureChannels, + @Nullable + final TriFunction< + List>, + InventoryEntryDraft, + InventoryEntry, + List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { + super( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + this.ensureChannels = ensureChannels; + } + + /** + * @return option that indicates whether the sync process should create a supply channel of the + * given key when it doesn't exist in a target system yet. + */ + public boolean shouldEnsureChannels() { + return ensureChannels; + } } diff --git a/src/main/java/com/commercetools/sync/inventories/InventorySyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/inventories/InventorySyncOptionsBuilder.java index 17c0c4cd63..14ab5bfcc7 100644 --- a/src/main/java/com/commercetools/sync/inventories/InventorySyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/inventories/InventorySyncOptionsBuilder.java @@ -4,77 +4,76 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.inventory.InventoryEntry; import io.sphere.sdk.inventory.InventoryEntryDraft; - import javax.annotation.Nonnull; -/** - * Builder for creation of {@link InventorySyncOptions}. - */ -public final class InventorySyncOptionsBuilder extends - BaseSyncOptionsBuilder { - static final int BATCH_SIZE_DEFAULT = 150; - static final boolean ENSURE_CHANNELS_DEFAULT = false; - private boolean ensureChannels = ENSURE_CHANNELS_DEFAULT; +/** Builder for creation of {@link InventorySyncOptions}. */ +public final class InventorySyncOptionsBuilder + extends BaseSyncOptionsBuilder< + InventorySyncOptionsBuilder, InventorySyncOptions, InventoryEntry, InventoryEntryDraft> { + static final int BATCH_SIZE_DEFAULT = 150; + static final boolean ENSURE_CHANNELS_DEFAULT = false; + private boolean ensureChannels = ENSURE_CHANNELS_DEFAULT; - private InventorySyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } + private InventorySyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } - /** - * Creates a new instance of {@link InventorySyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 150). - * - * @param ctpClient {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link InventorySyncOptionsBuilder} - */ - public static InventorySyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new InventorySyncOptionsBuilder(ctpClient) - .batchSize(BATCH_SIZE_DEFAULT); - } + /** + * Creates a new instance of {@link InventorySyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 150). + * + * @param ctpClient {@link SphereClient} responsible for interaction with the target CTP project. + * @return new instance of {@link InventorySyncOptionsBuilder} + */ + public static InventorySyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new InventorySyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } - /** - * Set option that indicates whether sync process should create a supply channel of given key when it doesn't exist - * in a target project yet. If set to {@code true} sync process would try to create new supply channel of given key, - * otherwise the sync process would log an error and fail to process the draft with the given supply channel key. - * - *

This property is {@link InventorySyncOptionsBuilder#ENSURE_CHANNELS_DEFAULT} by default. - * - * @param ensureChannels boolean that indicates whether sync process should create supply channel of given key when - * it doesn't exist in a target project yet - * @return {@code this} instance of {@link InventorySyncOptionsBuilder} - */ - public InventorySyncOptionsBuilder ensureChannels(final boolean ensureChannels) { - this.ensureChannels = ensureChannels; - return this; - } + /** + * Set option that indicates whether sync process should create a supply channel of given key when + * it doesn't exist in a target project yet. If set to {@code true} sync process would try to + * create new supply channel of given key, otherwise the sync process would log an error and fail + * to process the draft with the given supply channel key. + * + *

This property is {@link InventorySyncOptionsBuilder#ENSURE_CHANNELS_DEFAULT} by default. + * + * @param ensureChannels boolean that indicates whether sync process should create supply channel + * of given key when it doesn't exist in a target project yet + * @return {@code this} instance of {@link InventorySyncOptionsBuilder} + */ + public InventorySyncOptionsBuilder ensureChannels(final boolean ensureChannels) { + this.ensureChannels = ensureChannels; + return this; + } - /** - * Returns new instance of {@link InventorySyncOptions}, enriched with all attributes provided to {@code this} - * builder. - * - * @return new instance of {@link InventorySyncOptions} - */ - @Override - public InventorySyncOptions build() { - return new InventorySyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - ensureChannels, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize); - } + /** + * Returns new instance of {@link InventorySyncOptions}, enriched with all attributes provided to + * {@code this} builder. + * + * @return new instance of {@link InventorySyncOptions} + */ + @Override + public InventorySyncOptions build() { + return new InventorySyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + ensureChannels, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } - /** - * Returns {@code this} instance of {@link InventorySyncOptionsBuilder}. - * - *

Inherited doc:
- * {@inheritDoc} - */ - @Override - protected InventorySyncOptionsBuilder getThis() { - return this; - } + /** + * Returns {@code this} instance of {@link InventorySyncOptionsBuilder}. + * + *

Inherited doc:
+ * {@inheritDoc} + */ + @Override + protected InventorySyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/inventories/helpers/InventoryBatchValidator.java b/src/main/java/com/commercetools/sync/inventories/helpers/InventoryBatchValidator.java index 3cd25acd15..370fa78a2f 100644 --- a/src/main/java/com/commercetools/sync/inventories/helpers/InventoryBatchValidator.java +++ b/src/main/java/com/commercetools/sync/inventories/helpers/InventoryBatchValidator.java @@ -1,94 +1,96 @@ package com.commercetools.sync.inventories.helpers; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.helpers.BaseBatchValidator; import com.commercetools.sync.inventories.InventorySyncOptions; import io.sphere.sdk.inventory.InventoryEntryDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; - -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class InventoryBatchValidator extends BaseBatchValidator { - static final String INVENTORY_DRAFT_SKU_NOT_SET = "InventoryEntryDraft doesn't have a SKU. " - + "Please make sure all inventory entry drafts have SKUs."; - static final String INVENTORY_DRAFT_IS_NULL = "InventoryEntryDraft is null."; - - public InventoryBatchValidator( - @Nonnull final InventorySyncOptions syncOptions, - @Nonnull final InventorySyncStatistics syncStatistics) { - - super(syncOptions, syncStatistics); - } - - /** - * Given the {@link List}<{@link InventoryEntryDraft}> of drafts this method attempts to validate - * drafts and collect referenced keys from the draft - * and return an {@link ImmutablePair}<{@link Set}<{@link InventoryEntryDraft}>,{@link ReferencedKeys}> - * which contains the {@link Set} of valid drafts and referenced keys within a wrapper. - * - *

A valid inventory draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a sku which is not blank (null/empty)
  4. - *
- * - * @param inventoryDrafts the inventory drafts to validate and collect referenced keys. - * @return {@link ImmutablePair}<{@link Set}<{@link InventoryEntryDraft}>,{@link ReferencedKeys}> - * which contains the {@link Set} of valid drafts and referenced keys within a wrapper. - */ - @Override - public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( - @Nonnull final List inventoryDrafts) { - final ReferencedKeys referencedKeys = new ReferencedKeys(); - final Set validDrafts = inventoryDrafts - .stream() + static final String INVENTORY_DRAFT_SKU_NOT_SET = + "InventoryEntryDraft doesn't have a SKU. " + + "Please make sure all inventory entry drafts have SKUs."; + static final String INVENTORY_DRAFT_IS_NULL = "InventoryEntryDraft is null."; + + public InventoryBatchValidator( + @Nonnull final InventorySyncOptions syncOptions, + @Nonnull final InventorySyncStatistics syncStatistics) { + + super(syncOptions, syncStatistics); + } + + /** + * Given the {@link List}<{@link InventoryEntryDraft}> of drafts this method attempts to + * validate drafts and collect referenced keys from the draft and return an {@link + * ImmutablePair}<{@link Set}<{@link InventoryEntryDraft}>,{@link ReferencedKeys}> + * which contains the {@link Set} of valid drafts and referenced keys within a wrapper. + * + *

A valid inventory draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a sku which is not blank (null/empty) + *
+ * + * @param inventoryDrafts the inventory drafts to validate and collect referenced keys. + * @return {@link ImmutablePair}<{@link Set}<{@link InventoryEntryDraft}>,{@link + * ReferencedKeys}> which contains the {@link Set} of valid drafts and referenced keys + * within a wrapper. + */ + @Override + public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( + @Nonnull final List inventoryDrafts) { + final ReferencedKeys referencedKeys = new ReferencedKeys(); + final Set validDrafts = + inventoryDrafts.stream() .filter(this::isValidInventoryEntryDraft) .peek(inventoryDraft -> collectReferencedKeys(referencedKeys, inventoryDraft)) .collect(Collectors.toSet()); - return ImmutablePair.of(validDrafts, referencedKeys); - } - - private boolean isValidInventoryEntryDraft(@Nullable final InventoryEntryDraft inventoryDraft) { - if (inventoryDraft == null) { - handleError(INVENTORY_DRAFT_IS_NULL); - } else if (isBlank(inventoryDraft.getSku())) { - handleError(INVENTORY_DRAFT_SKU_NOT_SET); - } else { - return true; - } + return ImmutablePair.of(validDrafts, referencedKeys); + } - return false; + private boolean isValidInventoryEntryDraft(@Nullable final InventoryEntryDraft inventoryDraft) { + if (inventoryDraft == null) { + handleError(INVENTORY_DRAFT_IS_NULL); + } else if (isBlank(inventoryDraft.getSku())) { + handleError(INVENTORY_DRAFT_SKU_NOT_SET); + } else { + return true; } - private void collectReferencedKeys( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final InventoryEntryDraft inventoryDraft) { + return false; + } - collectReferencedKeyFromResourceIdentifier(inventoryDraft.getSupplyChannel(), - referencedKeys.channelKeys::add); - collectReferencedKeyFromCustomFieldsDraft(inventoryDraft.getCustom(), - referencedKeys.typeKeys::add); - } + private void collectReferencedKeys( + @Nonnull final ReferencedKeys referencedKeys, + @Nonnull final InventoryEntryDraft inventoryDraft) { - public static class ReferencedKeys { - private final Set channelKeys = new HashSet<>(); - private final Set typeKeys = new HashSet<>(); + collectReferencedKeyFromResourceIdentifier( + inventoryDraft.getSupplyChannel(), referencedKeys.channelKeys::add); + collectReferencedKeyFromCustomFieldsDraft( + inventoryDraft.getCustom(), referencedKeys.typeKeys::add); + } - public Set getTypeKeys() { - return typeKeys; - } + public static class ReferencedKeys { + private final Set channelKeys = new HashSet<>(); + private final Set typeKeys = new HashSet<>(); + + public Set getTypeKeys() { + return typeKeys; + } - public Set getChannelKeys() { - return channelKeys; - } + public Set getChannelKeys() { + return channelKeys; } + } } diff --git a/src/main/java/com/commercetools/sync/inventories/helpers/InventoryCustomActionBuilder.java b/src/main/java/com/commercetools/sync/inventories/helpers/InventoryCustomActionBuilder.java index 3232acc9ec..cb2c5da192 100644 --- a/src/main/java/com/commercetools/sync/inventories/helpers/InventoryCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/inventories/helpers/InventoryCustomActionBuilder.java @@ -6,36 +6,36 @@ import io.sphere.sdk.inventory.InventoryEntry; import io.sphere.sdk.inventory.commands.updateactions.SetCustomField; import io.sphere.sdk.inventory.commands.updateactions.SetCustomType; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; public class InventoryCustomActionBuilder implements GenericCustomActionBuilder { - @Nonnull - @Override - public UpdateAction buildRemoveCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String objectId) { - return SetCustomType.ofRemoveType(); - } + @Nonnull + @Override + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String objectId) { + return SetCustomType.ofRemoveType(); + } - @Nonnull - @Override - public UpdateAction buildSetCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String objectId, - @Nonnull final String customTypeId, - @Nullable final Map - customFieldsJsonMap) { - return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); - } + @Nonnull + @Override + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); + } - @Nonnull - @Override - public UpdateAction buildSetCustomFieldAction(@Nullable final Integer variantId, - @Nullable final String objectId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - return SetCustomField.ofJson(customFieldName, customFieldValue); - } + @Nonnull + @Override + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + return SetCustomField.ofJson(customFieldName, customFieldValue); + } } diff --git a/src/main/java/com/commercetools/sync/inventories/helpers/InventoryEntryIdentifier.java b/src/main/java/com/commercetools/sync/inventories/helpers/InventoryEntryIdentifier.java index 117539ce36..8b88b2c3a1 100644 --- a/src/main/java/com/commercetools/sync/inventories/helpers/InventoryEntryIdentifier.java +++ b/src/main/java/com/commercetools/sync/inventories/helpers/InventoryEntryIdentifier.java @@ -1,102 +1,105 @@ package com.commercetools.sync.inventories.helpers; +import static java.lang.String.format; import io.sphere.sdk.channels.Channel; import io.sphere.sdk.inventory.InventoryEntry; import io.sphere.sdk.inventory.InventoryEntryDraft; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; - +import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Objects; - -import static java.lang.String.format; /** - * This class provides as a container of the unique identifier of an {@link InventoryEntry} for the sync which is a - * combination of both the SKU of the inventory entry and the supply channel key of this inventory entry. + * This class provides as a container of the unique identifier of an {@link InventoryEntry} for the + * sync which is a combination of both the SKU of the inventory entry and the supply channel key of + * this inventory entry. */ public final class InventoryEntryIdentifier { - private String inventoryEntrySku; - private String inventoryEntryChannelKey; - - private InventoryEntryIdentifier(@Nonnull final String inventoryEntrySku, - @Nullable final String inventoryEntryChannelKey) { - this.inventoryEntrySku = inventoryEntrySku; - this.inventoryEntryChannelKey = inventoryEntryChannelKey; + private String inventoryEntrySku; + private String inventoryEntryChannelKey; + + private InventoryEntryIdentifier( + @Nonnull final String inventoryEntrySku, @Nullable final String inventoryEntryChannelKey) { + this.inventoryEntrySku = inventoryEntrySku; + this.inventoryEntryChannelKey = inventoryEntryChannelKey; + } + + /** + * Builds an {@link InventoryEntryIdentifier} instance given an {@link InventoryEntryDraft} using + * its sku and supply channel key. + * + * @param inventoryEntryDraft the draft to take the sku and channel key value from. + * @return an instance of {@link InventoryEntryIdentifier} for the given draft. + */ + public static InventoryEntryIdentifier of( + @Nonnull final InventoryEntryDraft inventoryEntryDraft) { + + final ResourceIdentifier supplyChannelIdentifier = + inventoryEntryDraft.getSupplyChannel(); + return new InventoryEntryIdentifier( + inventoryEntryDraft.getSku(), + supplyChannelIdentifier != null ? supplyChannelIdentifier.getId() : null); + } + + /** + * Builds an {@link InventoryEntryIdentifier} instance given an {@link InventoryEntry} using it's + * sku and supply channel id. + * + * @param inventoryEntry the entry to take the sku and channel id value from. + * @return an instance of {@link InventoryEntryIdentifier} for the given entry. + */ + public static InventoryEntryIdentifier of(@Nonnull final InventoryEntry inventoryEntry) { + + final Reference supplyChannel = inventoryEntry.getSupplyChannel(); + return new InventoryEntryIdentifier( + inventoryEntry.getSku(), supplyChannel != null ? supplyChannel.getId() : null); + } + + /** + * Builds an {@link InventoryEntryIdentifier} instance given an sku and supply channel id. + * + * @param inventoryEntrySku the SKU of the inventory entry. + * @param inventoryEntryChannelKey the channel key of the inventory entry. + * @return an instance of {@link InventoryEntryIdentifier} for the given entry. + */ + public static InventoryEntryIdentifier of( + @Nonnull final String inventoryEntrySku, @Nullable final String inventoryEntryChannelKey) { + + return new InventoryEntryIdentifier(inventoryEntrySku, inventoryEntryChannelKey); + } + + public String getInventoryEntrySku() { + return inventoryEntrySku; + } + + public String getInventoryEntryChannelKey() { + return inventoryEntryChannelKey; + } + + @Override + public String toString() { + return format("{sku='%s', channelKey='%s'}", inventoryEntrySku, inventoryEntryChannelKey); + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; } - - /** - * Builds an {@link InventoryEntryIdentifier} instance given an {@link InventoryEntryDraft} using its sku and - * supply channel key. - * @param inventoryEntryDraft the draft to take the sku and channel key value from. - * @return an instance of {@link InventoryEntryIdentifier} for the given draft. - */ - public static InventoryEntryIdentifier of(@Nonnull final InventoryEntryDraft inventoryEntryDraft) { - - final ResourceIdentifier supplyChannelIdentifier = inventoryEntryDraft.getSupplyChannel(); - return new InventoryEntryIdentifier(inventoryEntryDraft.getSku(), - supplyChannelIdentifier != null ? supplyChannelIdentifier.getId() : null); + if (!(other instanceof InventoryEntryIdentifier)) { + return false; } - /** - * Builds an {@link InventoryEntryIdentifier} instance given an {@link InventoryEntry} using it's sku and - * supply channel id. - * @param inventoryEntry the entry to take the sku and channel id value from. - * @return an instance of {@link InventoryEntryIdentifier} for the given entry. - */ - public static InventoryEntryIdentifier of(@Nonnull final InventoryEntry inventoryEntry) { - - final Reference supplyChannel = inventoryEntry.getSupplyChannel(); - return new InventoryEntryIdentifier(inventoryEntry.getSku(), - supplyChannel != null ? supplyChannel.getId() : null); - } + final InventoryEntryIdentifier that = (InventoryEntryIdentifier) other; - /** - * Builds an {@link InventoryEntryIdentifier} instance given an sku and supply channel id. - * - * @param inventoryEntrySku the SKU of the inventory entry. - * @param inventoryEntryChannelKey the channel key of the inventory entry. - * @return an instance of {@link InventoryEntryIdentifier} for the given entry. - */ - public static InventoryEntryIdentifier of( - @Nonnull final String inventoryEntrySku, - @Nullable final String inventoryEntryChannelKey) { - - return new InventoryEntryIdentifier(inventoryEntrySku, inventoryEntryChannelKey); - } + return getInventoryEntrySku().equals(that.getInventoryEntrySku()) + && Objects.equals(getInventoryEntryChannelKey(), that.getInventoryEntryChannelKey()); + } - public String getInventoryEntrySku() { - return inventoryEntrySku; - } - - public String getInventoryEntryChannelKey() { - return inventoryEntryChannelKey; - } - - @Override - public String toString() { - return format("{sku='%s', channelKey='%s'}", inventoryEntrySku, inventoryEntryChannelKey); - } - - @Override - public boolean equals(final Object other) { - if (this == other) { - return true; - } - if (!(other instanceof InventoryEntryIdentifier)) { - return false; - } - - final InventoryEntryIdentifier that = (InventoryEntryIdentifier) other; - - return getInventoryEntrySku().equals(that.getInventoryEntrySku()) - && Objects.equals(getInventoryEntryChannelKey(), that.getInventoryEntryChannelKey()); - } - - @Override - public int hashCode() { - return Objects.hash(getInventoryEntrySku(), getInventoryEntryChannelKey()); - } + @Override + public int hashCode() { + return Objects.hash(getInventoryEntrySku(), getInventoryEntryChannelKey()); + } } diff --git a/src/main/java/com/commercetools/sync/inventories/helpers/InventoryReferenceResolver.java b/src/main/java/com/commercetools/sync/inventories/helpers/InventoryReferenceResolver.java index 18bd4e9d0f..db0a016137 100644 --- a/src/main/java/com/commercetools/sync/inventories/helpers/InventoryReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/inventories/helpers/InventoryReferenceResolver.java @@ -1,5 +1,10 @@ package com.commercetools.sync.inventories.helpers; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.CustomReferenceResolver; import com.commercetools.sync.inventories.InventorySyncOptions; @@ -10,241 +15,256 @@ import io.sphere.sdk.inventory.InventoryEntryDraftBuilder; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.utils.CompletableFutureUtils; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; public final class InventoryReferenceResolver - extends CustomReferenceResolver { - - private static final String CHANNEL_DOES_NOT_EXIST = "Channel with key '%s' does not exist."; - static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type resource identifier on " - + "InventoryEntryDraft with SKU:'%s'."; - private static final String FAILED_TO_RESOLVE_SUPPLY_CHANNEL = "Failed to resolve supply channel " - + "resource identifier on InventoryEntryDraft with SKU:'%s'. Reason: %s"; - private static final String FAILED_TO_CREATE_SUPPLY_CHANNEL = "Failed to create supply channel with key: '%s'"; - private final ChannelService channelService; - private final TypeService typeService; - - /** - * Takes a {@link InventorySyncOptions} instance, a {@link TypeService} and a {@link ChannelService} to instantiate - * a {@link InventoryReferenceResolver} instance that could be used to resolve the type and supply channel - * references of inventory drafts. - * - * @param options the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - * @param typeService the service to fetch the custom types for reference resolution. - * @param channelService the service to fetch the supply channels for reference resolution. - */ - public InventoryReferenceResolver(@Nonnull final InventorySyncOptions options, - @Nonnull final TypeService typeService, - @Nonnull final ChannelService channelService) { - super(options, typeService); - this.channelService = channelService; - this.typeService = typeService; - } + extends CustomReferenceResolver< + InventoryEntryDraft, InventoryEntryDraftBuilder, InventorySyncOptions> { - /** - * Given a {@link InventoryEntryDraft} this method attempts to resolve the custom type and supply channel - * resource identifiers to return a {@link CompletionStage} which contains a new instance of the draft with the - * resolved resource identifiers. - * - * @param draft the inventoryEntryDraft to resolve its resource identifiers. - * @return a {@link CompletionStage} that contains as a result a new inventoryEntryDraft instance with resolved - * resource identifiers or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - public CompletionStage resolveReferences(@Nonnull final InventoryEntryDraft draft) { - return resolveCustomTypeReference(InventoryEntryDraftBuilder.of(draft)) - .thenCompose(this::resolveSupplyChannelReference) - .thenApply(InventoryEntryDraftBuilder::build); - } + private static final String CHANNEL_DOES_NOT_EXIST = "Channel with key '%s' does not exist."; + static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = + "Failed to resolve custom type resource identifier on " + + "InventoryEntryDraft with SKU:'%s'."; + private static final String FAILED_TO_RESOLVE_SUPPLY_CHANNEL = + "Failed to resolve supply channel " + + "resource identifier on InventoryEntryDraft with SKU:'%s'. Reason: %s"; + private static final String FAILED_TO_CREATE_SUPPLY_CHANNEL = + "Failed to create supply channel with key: '%s'"; + private final ChannelService channelService; + private final TypeService typeService; - @Override - @Nonnull - protected CompletionStage resolveCustomTypeReference( - @Nonnull final InventoryEntryDraftBuilder draftBuilder) { + /** + * Takes a {@link InventorySyncOptions} instance, a {@link TypeService} and a {@link + * ChannelService} to instantiate a {@link InventoryReferenceResolver} instance that could be used + * to resolve the type and supply channel references of inventory drafts. + * + * @param options the container of all the options of the sync process including the CTP project + * client and/or configuration and other sync-specific options. + * @param typeService the service to fetch the custom types for reference resolution. + * @param channelService the service to fetch the supply channels for reference resolution. + */ + public InventoryReferenceResolver( + @Nonnull final InventorySyncOptions options, + @Nonnull final TypeService typeService, + @Nonnull final ChannelService channelService) { + super(options, typeService); + this.channelService = channelService; + this.typeService = typeService; + } - return resolveCustomTypeReference(draftBuilder, - InventoryEntryDraftBuilder::getCustom, - InventoryEntryDraftBuilder::custom, - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getSku())); - } + /** + * Given a {@link InventoryEntryDraft} this method attempts to resolve the custom type and supply + * channel resource identifiers to return a {@link CompletionStage} which contains a new instance + * of the draft with the resolved resource identifiers. + * + * @param draft the inventoryEntryDraft to resolve its resource identifiers. + * @return a {@link CompletionStage} that contains as a result a new inventoryEntryDraft instance + * with resolved resource identifiers or, in case an error occurs during reference resolution, + * a {@link ReferenceResolutionException}. + */ + public CompletionStage resolveReferences( + @Nonnull final InventoryEntryDraft draft) { + return resolveCustomTypeReference(InventoryEntryDraftBuilder.of(draft)) + .thenCompose(this::resolveSupplyChannelReference) + .thenApply(InventoryEntryDraftBuilder::build); + } + + @Override + @Nonnull + protected CompletionStage resolveCustomTypeReference( + @Nonnull final InventoryEntryDraftBuilder draftBuilder) { + + return resolveCustomTypeReference( + draftBuilder, + InventoryEntryDraftBuilder::getCustom, + InventoryEntryDraftBuilder::custom, + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getSku())); + } - /** - * Given a {@link InventoryEntryDraftBuilder} this method attempts to resolve the supply channel resource identifier - * to return a {@link CompletionStage} which contains a new instance of the draft builder with the resolved - * supply channel resource identifier. - * - *

The method then tries to fetch the key of the supply channel, optimistically from a - * cache. If the id is not found in cache nor the CTP project and {@code ensureChannel} - * option is set to true, a new channel will be created with this key and the role {@code "InventorySupply"}. - * However, if the {@code ensureChannel} is set to false, the future is completed exceptionally with a - * {@link ReferenceResolutionException}. - * - * @param draftBuilder the inventory draft builder to read it's values (key, sku, channel) - * and then to write resolved resource identifier. - * @return a {@link CompletionStage} that contains as a result the same {@code draftBuilder} inventory draft builder - * instance with resolved supply channel or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Nonnull - CompletionStage resolveSupplyChannelReference( - @Nonnull final InventoryEntryDraftBuilder draftBuilder) { - - final ResourceIdentifier channelReference = draftBuilder.getSupplyChannel(); - if (channelReference != null && channelReference.getId() == null) { - try { - final String channelKey = getKeyFromResourceIdentifier(channelReference); - return fetchOrCreateAndResolveReference(draftBuilder, channelKey); - } catch (ReferenceResolutionException exception) { - return CompletableFutureUtils.exceptionallyCompletedFuture( - new ReferenceResolutionException(format(FAILED_TO_RESOLVE_SUPPLY_CHANNEL, draftBuilder.getSku(), - exception.getMessage()), exception)); - } - } - return completedFuture(draftBuilder); + /** + * Given a {@link InventoryEntryDraftBuilder} this method attempts to resolve the supply channel + * resource identifier to return a {@link CompletionStage} which contains a new instance of the + * draft builder with the resolved supply channel resource identifier. + * + *

The method then tries to fetch the key of the supply channel, optimistically from a cache. + * If the id is not found in cache nor the CTP project and {@code ensureChannel} option is set to + * true, a new channel will be created with this key and the role {@code "InventorySupply"}. + * However, if the {@code ensureChannel} is set to false, the future is completed exceptionally + * with a {@link ReferenceResolutionException}. + * + * @param draftBuilder the inventory draft builder to read it's values (key, sku, channel) and + * then to write resolved resource identifier. + * @return a {@link CompletionStage} that contains as a result the same {@code draftBuilder} + * inventory draft builder instance with resolved supply channel or, in case an error occurs + * during reference resolution, a {@link ReferenceResolutionException}. + */ + @Nonnull + CompletionStage resolveSupplyChannelReference( + @Nonnull final InventoryEntryDraftBuilder draftBuilder) { + + final ResourceIdentifier channelReference = draftBuilder.getSupplyChannel(); + if (channelReference != null && channelReference.getId() == null) { + try { + final String channelKey = getKeyFromResourceIdentifier(channelReference); + return fetchOrCreateAndResolveReference(draftBuilder, channelKey); + } catch (ReferenceResolutionException exception) { + return CompletableFutureUtils.exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_SUPPLY_CHANNEL, + draftBuilder.getSku(), + exception.getMessage()), + exception)); + } } + return completedFuture(draftBuilder); + } + + /** + * Given an {@link InventoryEntryDraftBuilder} and a {@code channelKey} this method fetches the + * actual id of the channel corresponding to this key, ideally from a cache. Then it sets this id + * on the supply channel reference id of the inventory entry draft builder. If the id is not found + * in cache nor the CTP project and {@code ensureChannel} option is set to true, a new channel + * will be created with this key and the role {@code "InventorySupply"}. However, if the {@code + * ensureChannel} is set to false, the future is completed exceptionally with a {@link + * ReferenceResolutionException}. + * + * @param draftBuilder the inventory draft builder to read it's values (key, sku, channel) and + * then to write resolved resource identifiers. + * @param channelKey the key of the channel to resolve it's actual id on the draft. + * @return a {@link CompletionStage} that contains as a result the same {@code draftBuilder} + * inventory draft builder instance with resolved supply channel resource identifier or an + * exception. + */ + @Nonnull + private CompletionStage fetchOrCreateAndResolveReference( + @Nonnull final InventoryEntryDraftBuilder draftBuilder, @Nonnull final String channelKey) { - /** - * Given an {@link InventoryEntryDraftBuilder} and a {@code channelKey} this method fetches the actual id of the - * channel corresponding to this key, ideally from a cache. Then it sets this id on the supply channel reference - * id of the inventory entry draft builder. If the id is not found in cache nor the CTP project - * and {@code ensureChannel} option is set to true, a new channel will be created with this key - * and the role {@code "InventorySupply"}. - * However, if the {@code ensureChannel} is set to false, the future is completed exceptionally with a - * {@link ReferenceResolutionException}. - * - * @param draftBuilder the inventory draft builder to read it's values (key, sku, channel) - * and then to write resolved resource identifiers. - * @param channelKey the key of the channel to resolve it's actual id on the draft. - * @return a {@link CompletionStage} that contains as a result the same {@code draftBuilder} inventory draft builder - * instance with resolved supply channel resource identifier or an exception. - */ - @Nonnull - private CompletionStage fetchOrCreateAndResolveReference( - @Nonnull final InventoryEntryDraftBuilder draftBuilder, - @Nonnull final String channelKey) { - - final CompletionStage inventoryEntryDraftCompletionStage = channelService + final CompletionStage inventoryEntryDraftCompletionStage = + channelService .fetchCachedChannelId(channelKey) - .thenCompose(resolvedChannelIdOptional -> resolvedChannelIdOptional - .map(resolvedChannelId -> setChannelReference(resolvedChannelId, draftBuilder)) - .orElseGet(() -> createChannelAndSetReference(channelKey, draftBuilder))); - - final CompletableFuture result = new CompletableFuture<>(); - inventoryEntryDraftCompletionStage - .whenComplete((resolvedDraftBuilder, exception) -> { - if (exception != null) { - result.completeExceptionally( - new ReferenceResolutionException(format(FAILED_TO_RESOLVE_SUPPLY_CHANNEL, draftBuilder.getSku(), - exception.getCause().getMessage()), exception)); + .thenCompose( + resolvedChannelIdOptional -> + resolvedChannelIdOptional + .map( + resolvedChannelId -> + setChannelReference(resolvedChannelId, draftBuilder)) + .orElseGet(() -> createChannelAndSetReference(channelKey, draftBuilder))); + + final CompletableFuture result = new CompletableFuture<>(); + inventoryEntryDraftCompletionStage.whenComplete( + (resolvedDraftBuilder, exception) -> { + if (exception != null) { + result.completeExceptionally( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_SUPPLY_CHANNEL, + draftBuilder.getSku(), + exception.getCause().getMessage()), + exception)); + } else { + result.complete(resolvedDraftBuilder); + } + }); + return result; + } + + /** + * Helper method that returns a completed CompletionStage with a resolved channel resource + * identifier {@link InventoryEntryDraftBuilder} object as a result of setting the passed {@code + * channelId} as the id of channel resource identifier. + * + * @param channelId the channel id to set on the inventory entry supply channel resource + * identifier id field. + * @param draftBuilder the inventory draft builder where to write resolved resource identifier. + * @return a completed CompletionStage with a resolved channel resource identifier with the same + * {@link InventoryEntryDraftBuilder} instance as a result of setting the passed {@code + * channelId} as the id of channel resource identifier. + */ + @Nonnull + private static CompletionStage setChannelReference( + @Nonnull final String channelId, @Nonnull final InventoryEntryDraftBuilder draftBuilder) { + return completedFuture(draftBuilder.supplyChannel(ResourceIdentifier.ofId(channelId))); + } + + /** + * Helper method that creates a new {@link Channel} on the CTP project with the specified {@code + * channelKey} and of the role {@code "InventorySupply"}. Only if the {@code ensureChannels} + * options is set to {@code true} on the {@code options} instance of {@code this} class. Then it + * resolves the supply channel resource identifier on the supplied {@code inventoryEntryDraft} by + * setting the id of its supply channel resource identifier with the newly created Channel. + * + *

If the {@code ensureChannels} options is set to {@code false} on the {@code options} + * instance of {@code this} class, the future is completed exceptionally with a {@link + * ReferenceResolutionException}. + * + *

The method then returns a CompletionStage with a resolved channel resource identifiers + * {@link InventoryEntryDraftBuilder} object. + * + * @param channelKey the key to create the new channel with. + * @param draftBuilder the inventory draft builder where to write resolved resource identifiers. + * @return a CompletionStage with the same {@code draftBuilder} inventory draft builder instance + * where channel channels are resolved. + */ + @Nonnull + private CompletionStage createChannelAndSetReference( + @Nonnull final String channelKey, @Nonnull final InventoryEntryDraftBuilder draftBuilder) { + + if (options.shouldEnsureChannels()) { + return channelService + .createAndCacheChannel(channelKey) + .thenCompose( + createdChannelOptional -> { + if (createdChannelOptional.isPresent()) { + return setChannelReference(createdChannelOptional.get().getId(), draftBuilder); } else { - result.complete(resolvedDraftBuilder); + final ReferenceResolutionException referenceResolutionException = + new ReferenceResolutionException( + format(FAILED_TO_CREATE_SUPPLY_CHANNEL, channelKey)); + return CompletableFutureUtils.exceptionallyCompletedFuture( + referenceResolutionException); } - }); - return result; - + }); + } else { + final ReferenceResolutionException referenceResolutionException = + new ReferenceResolutionException(format(CHANNEL_DOES_NOT_EXIST, channelKey)); + return CompletableFutureUtils.exceptionallyCompletedFuture(referenceResolutionException); } + } + /** + * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys (supply + * channel and type) from the commercetools to populate caches for the reference resolution. + * + *

Note: This method is meant be only used internally by the library to improve performance. + * + * @param referencedKeys a wrapper for the inventory references to fetch and cache the id's for. + * @return {@link CompletionStage}<{@link List}<{@link Map}<{@link String}>{@link + * String}>>> in which the results of it's completions contains a map of requested + * references keys -> ids of it's references. + */ + @Nonnull + public CompletableFuture>> populateKeyToIdCachesForReferencedKeys( + @Nonnull final InventoryBatchValidator.ReferencedKeys referencedKeys) { - /** - * Helper method that returns a completed CompletionStage with a resolved channel resource identifier - * {@link InventoryEntryDraftBuilder} object as a result of setting the passed {@code channelId} - * as the id of channel resource identifier. - * - * @param channelId the channel id to set on the inventory entry supply channel resource identifier id field. - * @param draftBuilder the inventory draft builder where to write resolved resource identifier. - * @return a completed CompletionStage with a resolved channel resource identifier with the same - * {@link InventoryEntryDraftBuilder} instance as a result of setting the passed {@code channelId} - * as the id of channel resource identifier. - */ - @Nonnull - private static CompletionStage setChannelReference( - @Nonnull final String channelId, - @Nonnull final InventoryEntryDraftBuilder draftBuilder) { - return completedFuture(draftBuilder.supplyChannel(ResourceIdentifier.ofId(channelId))); - } + final List>> futures = new ArrayList<>(); - /** - * Helper method that creates a new {@link Channel} on the CTP project with the specified {@code channelKey} and of - * the role {@code "InventorySupply"}. Only if the {@code ensureChannels} options is set to {@code true} on the - * {@code options} instance of {@code this} class. Then it resolves the supply channel resource identifier on the - * supplied {@code inventoryEntryDraft} by setting the id of its supply channel resource identifier with the newly - * created Channel. - * - *

If the {@code ensureChannels} options is set to {@code false} on the {@code options} instance of {@code this} - * class, the future is completed exceptionally with a {@link ReferenceResolutionException}. - * - *

The method then returns a CompletionStage with a resolved channel resource identifiers - * {@link InventoryEntryDraftBuilder} object. - * - * @param channelKey the key to create the new channel with. - * @param draftBuilder the inventory draft builder where to write resolved resource identifiers. - * @return a CompletionStage with the same {@code draftBuilder} inventory draft builder instance where channel - * channels are resolved. - */ - @Nonnull - private CompletionStage createChannelAndSetReference( - @Nonnull final String channelKey, - @Nonnull final InventoryEntryDraftBuilder draftBuilder) { - - if (options.shouldEnsureChannels()) { - return channelService - .createAndCacheChannel(channelKey) - .thenCompose(createdChannelOptional -> { - if (createdChannelOptional.isPresent()) { - return setChannelReference(createdChannelOptional.get().getId(), draftBuilder); - } else { - final ReferenceResolutionException referenceResolutionException = - new ReferenceResolutionException(format(FAILED_TO_CREATE_SUPPLY_CHANNEL, channelKey)); - return CompletableFutureUtils.exceptionallyCompletedFuture(referenceResolutionException); - } - }); - } else { - final ReferenceResolutionException referenceResolutionException = - new ReferenceResolutionException(format(CHANNEL_DOES_NOT_EXIST, channelKey)); - return CompletableFutureUtils.exceptionallyCompletedFuture(referenceResolutionException); - } + final Set channelKeys = referencedKeys.getChannelKeys(); + if (!channelKeys.isEmpty()) { + futures.add(channelService.cacheKeysToIds(channelKeys)); } - /** - * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys (supply channel and type) - * from the commercetools to populate caches for the reference resolution. - * - *

Note: This method is meant be only used internally by the library to improve performance. - * - * @param referencedKeys a wrapper for the inventory references to fetch and cache the id's for. - * @return {@link CompletionStage}<{@link List}<{@link Map}<{@link String}>{@link String}>>> - * in which the results of it's completions contains a map of - * requested references keys -> ids of it's references. - */ - @Nonnull - public CompletableFuture>> populateKeyToIdCachesForReferencedKeys( - @Nonnull final InventoryBatchValidator.ReferencedKeys referencedKeys) { - - final List>> futures = new ArrayList<>(); - - final Set channelKeys = referencedKeys.getChannelKeys(); - if (!channelKeys.isEmpty()) { - futures.add(channelService.cacheKeysToIds(channelKeys)); - } - - final Set typeKeys = referencedKeys.getTypeKeys(); - if (!typeKeys.isEmpty()) { - futures.add(typeService.cacheKeysToIds(typeKeys)); - } - - return collectionOfFuturesToFutureOfCollection(futures, toList()); + final Set typeKeys = referencedKeys.getTypeKeys(); + if (!typeKeys.isEmpty()) { + futures.add(typeService.cacheKeysToIds(typeKeys)); } + + return collectionOfFuturesToFutureOfCollection(futures, toList()); + } } diff --git a/src/main/java/com/commercetools/sync/inventories/helpers/InventorySyncStatistics.java b/src/main/java/com/commercetools/sync/inventories/helpers/InventorySyncStatistics.java index af74d57274..410a2e62cf 100644 --- a/src/main/java/com/commercetools/sync/inventories/helpers/InventorySyncStatistics.java +++ b/src/main/java/com/commercetools/sync/inventories/helpers/InventorySyncStatistics.java @@ -4,20 +4,22 @@ public class InventorySyncStatistics extends BaseSyncStatistics { - public InventorySyncStatistics() { - super(); - } + public InventorySyncStatistics() { + super(); + } - /** - * Builds a summary of the inventory sync statistics instance that looks like the following example: - *

-     *     Summary: 25 inventory entries were processed in total (9 created, 5 updated, 2 failed to sync).
-     * 
- * - * @return summary message - */ - @Override - public String getReportMessage() { - return getDefaultReportMessageForResource("inventory entries"); - } + /** + * Builds a summary of the inventory sync statistics instance that looks like the following + * example: + * + *
+   *     Summary: 25 inventory entries were processed in total (9 created, 5 updated, 2 failed to sync).
+   * 
+ * + * @return summary message + */ + @Override + public String getReportMessage() { + return getDefaultReportMessageForResource("inventory entries"); + } } diff --git a/src/main/java/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtils.java index a45b0b8444..c5f5f5a566 100644 --- a/src/main/java/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtils.java @@ -1,5 +1,9 @@ package com.commercetools.sync.inventories.utils; +import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; +import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; +import static java.util.stream.Collectors.toList; + import io.sphere.sdk.channels.Channel; import io.sphere.sdk.expansion.ExpansionPath; import io.sphere.sdk.inventory.InventoryEntry; @@ -11,96 +15,92 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.queries.QueryExecutionUtils; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; import java.util.List; - -import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; -import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class InventoryReferenceResolutionUtils { - /** - * Returns an {@link List}<{@link InventoryEntryDraft}> consisting of the results of applying the - * mapping from {@link InventoryEntry} to {@link InventoryEntryDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
supplyChannel{@link Reference}<{@link Channel}>{@link ResourceIdentifier}<{@link Channel}>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
- * - *

Note: The {@link Channel} and {@link Type} references should be expanded with a key. - * Any reference that is not expanded will have its id in place and not replaced by the key will be - * considered as existing resources on the target commercetools project and - * the library will issues an update/create API request without reference resolution. - * - * @param inventoryEntries the inventory entries with expanded references. - * @return a {@link List} of {@link InventoryEntryDraft} built from the - * supplied {@link List} of {@link InventoryEntry}. - */ - @Nonnull - public static List mapToInventoryEntryDrafts( - @Nonnull final List inventoryEntries) { + /** + * Returns an {@link List}<{@link InventoryEntryDraft}> consisting of the results of + * applying the mapping from {@link InventoryEntry} to {@link InventoryEntryDraft} with + * considering reference resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
supplyChannel{@link Reference}<{@link Channel}>{@link ResourceIdentifier}<{@link Channel}>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
+ * + *

Note: The {@link Channel} and {@link Type} references should be expanded with a key. + * Any reference that is not expanded will have its id in place and not replaced by the key will + * be considered as existing resources on the target commercetools project and the library will + * issues an update/create API request without reference resolution. + * + * @param inventoryEntries the inventory entries with expanded references. + * @return a {@link List} of {@link InventoryEntryDraft} built from the supplied {@link List} of + * {@link InventoryEntry}. + */ + @Nonnull + public static List mapToInventoryEntryDrafts( + @Nonnull final List inventoryEntries) { - return inventoryEntries - .stream() - .map(InventoryReferenceResolutionUtils::mapToInventoryEntryDraft) - .collect(toList()); - } + return inventoryEntries.stream() + .map(InventoryReferenceResolutionUtils::mapToInventoryEntryDraft) + .collect(toList()); + } - @Nonnull - private static InventoryEntryDraft mapToInventoryEntryDraft(@Nonnull final InventoryEntry inventoryEntry) { - return InventoryEntryDraftBuilder - .of(inventoryEntry) - .custom(mapToCustomFieldsDraft(inventoryEntry)) - .supplyChannel(getResourceIdentifierWithKey(inventoryEntry.getSupplyChannel())) - .build(); - } + @Nonnull + private static InventoryEntryDraft mapToInventoryEntryDraft( + @Nonnull final InventoryEntry inventoryEntry) { + return InventoryEntryDraftBuilder.of(inventoryEntry) + .custom(mapToCustomFieldsDraft(inventoryEntry)) + .supplyChannel(getResourceIdentifierWithKey(inventoryEntry.getSupplyChannel())) + .build(); + } - /** - * Builds a {@link InventoryEntryQuery} for fetching inventories from a source CTP project with all the - * needed references expanded for the sync: - *

    - *
  • Custom Type
  • - *
  • Supply Channel
  • - *
- * - *

Note: Please only use this util if you desire to sync all the aforementioned references from - * a source commercetools project. Otherwise, it is more efficient to build the query without expansions, if they - * are not needed, to avoid unnecessarily bigger payloads fetched from the source project. - * - * @return the query for fetching inventories from the source CTP project with all the aforementioned references - * expanded. - */ - public static InventoryEntryQuery buildInventoryQuery() { - return InventoryEntryQuery.of() - .withLimit(QueryExecutionUtils.DEFAULT_PAGE_SIZE) - .withExpansionPaths(ExpansionPath.of("custom.type")) - .plusExpansionPaths(InventoryEntryExpansionModel::supplyChannel); - } + /** + * Builds a {@link InventoryEntryQuery} for fetching inventories from a source CTP project with + * all the needed references expanded for the sync: + * + *

    + *
  • Custom Type + *
  • Supply Channel + *
+ * + *

Note: Please only use this util if you desire to sync all the aforementioned references from + * a source commercetools project. Otherwise, it is more efficient to build the query without + * expansions, if they are not needed, to avoid unnecessarily bigger payloads fetched from the + * source project. + * + * @return the query for fetching inventories from the source CTP project with all the + * aforementioned references expanded. + */ + public static InventoryEntryQuery buildInventoryQuery() { + return InventoryEntryQuery.of() + .withLimit(QueryExecutionUtils.DEFAULT_PAGE_SIZE) + .withExpansionPaths(ExpansionPath.of("custom.type")) + .plusExpansionPaths(InventoryEntryExpansionModel::supplyChannel); + } - private InventoryReferenceResolutionUtils() { - } + private InventoryReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/inventories/utils/InventorySyncUtils.java b/src/main/java/com/commercetools/sync/inventories/utils/InventorySyncUtils.java index a1e7bee8ad..3a1cb014b4 100644 --- a/src/main/java/com/commercetools/sync/inventories/utils/InventorySyncUtils.java +++ b/src/main/java/com/commercetools/sync/inventories/utils/InventorySyncUtils.java @@ -1,59 +1,61 @@ package com.commercetools.sync.inventories.utils; +import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildPrimaryResourceCustomUpdateActions; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildChangeQuantityAction; +import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetExpectedDeliveryAction; +import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetRestockableInDaysAction; +import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetSupplyChannelAction; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.inventories.InventorySyncOptions; import com.commercetools.sync.inventories.helpers.InventoryCustomActionBuilder; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.inventory.InventoryEntry; import io.sphere.sdk.inventory.InventoryEntryDraft; - -import javax.annotation.Nonnull; import java.util.List; +import javax.annotation.Nonnull; -import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildPrimaryResourceCustomUpdateActions; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildChangeQuantityAction; -import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetExpectedDeliveryAction; -import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetRestockableInDaysAction; -import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetSupplyChannelAction; - -/** - * This class provides factory methods for assembling update actions of inventory entries. - */ +/** This class provides factory methods for assembling update actions of inventory entries. */ public final class InventorySyncUtils { - private static final InventoryCustomActionBuilder inventoryCustomActionBuilder = new InventoryCustomActionBuilder(); - - /** - * Compares the quantityOnStock, the restockableInDays, the expectedDelivery, the supply channel and Custom - * fields/ type fields of an {@link InventoryEntry} and an {@link InventoryEntryDraft}. It returns a {@link List} of - * {@link UpdateAction}<{@link InventoryEntry}> as a result. If no update action is needed an empty - * {@link List} is returned. - * - * @param oldEntry the inventory entry which should be updated - * @param newEntry the inventory entry draft that contains new data that should be applied to {@code oldEntry} - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied by - * the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link BaseSyncOptions} - * for more info. - * @return list containing {@link UpdateAction} that need to be performed on {@code oldEntry} resource so - * that it will be synced with {@code newEntry} or empty list when both entries are already in sync. - */ - @Nonnull - public static List> buildActions(@Nonnull final InventoryEntry oldEntry, - @Nonnull final InventoryEntryDraft newEntry, - @Nonnull final InventorySyncOptions syncOptions) { - - final List> actions = filterEmptyOptionals( + private static final InventoryCustomActionBuilder inventoryCustomActionBuilder = + new InventoryCustomActionBuilder(); + + /** + * Compares the quantityOnStock, the restockableInDays, the expectedDelivery, the supply channel + * and Custom fields/ type fields of an {@link InventoryEntry} and an {@link InventoryEntryDraft}. + * It returns a {@link List} of {@link UpdateAction}<{@link InventoryEntry}> as a result. If + * no update action is needed an empty {@link List} is returned. + * + * @param oldEntry the inventory entry which should be updated + * @param newEntry the inventory entry draft that contains new data that should be applied to + * {@code oldEntry} + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * BaseSyncOptions} for more info. + * @return list containing {@link UpdateAction} that need to be performed on {@code oldEntry} + * resource so that it will be synced with {@code newEntry} or empty list when both entries + * are already in sync. + */ + @Nonnull + public static List> buildActions( + @Nonnull final InventoryEntry oldEntry, + @Nonnull final InventoryEntryDraft newEntry, + @Nonnull final InventorySyncOptions syncOptions) { + + final List> actions = + filterEmptyOptionals( buildChangeQuantityAction(oldEntry, newEntry), buildSetRestockableInDaysAction(oldEntry, newEntry), buildSetExpectedDeliveryAction(oldEntry, newEntry), - buildSetSupplyChannelAction(oldEntry, newEntry) - ); + buildSetSupplyChannelAction(oldEntry, newEntry)); - actions.addAll(buildPrimaryResourceCustomUpdateActions(oldEntry, newEntry, inventoryCustomActionBuilder, - syncOptions)); - return actions; - } + actions.addAll( + buildPrimaryResourceCustomUpdateActions( + oldEntry, newEntry, inventoryCustomActionBuilder, syncOptions)); + return actions; + } - private InventorySyncUtils() { } + private InventorySyncUtils() {} } diff --git a/src/main/java/com/commercetools/sync/inventories/utils/InventoryUpdateActionUtils.java b/src/main/java/com/commercetools/sync/inventories/utils/InventoryUpdateActionUtils.java index 1b318eab6a..f011b8fafb 100644 --- a/src/main/java/com/commercetools/sync/inventories/utils/InventoryUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/inventories/utils/InventoryUpdateActionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.inventories.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; + import io.sphere.sdk.channels.Channel; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.inventory.InventoryEntry; @@ -10,105 +13,106 @@ import io.sphere.sdk.inventory.commands.updateactions.SetSupplyChannel; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; -import org.apache.commons.lang3.math.NumberUtils; - -import javax.annotation.Nonnull; import java.time.ZonedDateTime; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.math.NumberUtils; /** * This class provides static utility methods for building update actions related to inventories. */ public final class InventoryUpdateActionUtils { - /** - * Compares the {@code quantityOnStock} values of an {@link InventoryEntry} and an {@link InventoryEntryDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeQuantity"} - * {@link UpdateAction}. If both {@link InventoryEntry} and {@link InventoryEntryDraft} have the same - * {@code quantityOnStock} values, then no update action is needed and empty optional will be returned. - * If the {@code quantityOnStock} from the {@code newEntry} is {@code null}, the new {@code quantityOnStock} will - * have a value of 0L. - * - * @param oldEntry the inventory entry that should be updated - * @param newEntry the inventory entry draft which contains new quantity on stock - * @return optional containing update action or empty optional if quantities on stock are identical - */ - @Nonnull - public static Optional> buildChangeQuantityAction(@Nonnull final InventoryEntry - oldEntry, - @Nonnull final InventoryEntryDraft - newEntry) { - final Long oldQuantityOnStock = oldEntry.getQuantityOnStock(); - final Long newQuantityOnStock = newEntry.getQuantityOnStock() == null ? NumberUtils.LONG_ZERO : newEntry - .getQuantityOnStock(); - return buildUpdateAction(oldQuantityOnStock, newQuantityOnStock, () -> ChangeQuantity.of(newQuantityOnStock)); - } + /** + * Compares the {@code quantityOnStock} values of an {@link InventoryEntry} and an {@link + * InventoryEntryDraft} and returns an {@link Optional} of update action, which would contain the + * {@code "changeQuantity"} {@link UpdateAction}. If both {@link InventoryEntry} and {@link + * InventoryEntryDraft} have the same {@code quantityOnStock} values, then no update action is + * needed and empty optional will be returned. If the {@code quantityOnStock} from the {@code + * newEntry} is {@code null}, the new {@code quantityOnStock} will have a value of 0L. + * + * @param oldEntry the inventory entry that should be updated + * @param newEntry the inventory entry draft which contains new quantity on stock + * @return optional containing update action or empty optional if quantities on stock are + * identical + */ + @Nonnull + public static Optional> buildChangeQuantityAction( + @Nonnull final InventoryEntry oldEntry, @Nonnull final InventoryEntryDraft newEntry) { + final Long oldQuantityOnStock = oldEntry.getQuantityOnStock(); + final Long newQuantityOnStock = + newEntry.getQuantityOnStock() == null + ? NumberUtils.LONG_ZERO + : newEntry.getQuantityOnStock(); + return buildUpdateAction( + oldQuantityOnStock, newQuantityOnStock, () -> ChangeQuantity.of(newQuantityOnStock)); + } - /** - * Compares the {@code restockableInDays} values of an {@link InventoryEntry} and an {@link InventoryEntryDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setRestockableInDays"} - * {@link UpdateAction}. If both {@link InventoryEntry} and the {@link InventoryEntryDraft} have the same - * {@code restockableInDays} values, then no update action is needed and empty optional will be returned. - * - * @param oldEntry the inventory entry that should be updated - * @param newEntry the inventory entry draft which contains a new {@code restockableInDays} value - * @return optional containing update action or empty optional if restockable in days are identical - */ - @Nonnull - public static Optional> buildSetRestockableInDaysAction(@Nonnull final - InventoryEntry oldEntry, - @Nonnull final - InventoryEntryDraft newEntry) { - final Integer oldRestockableInDays = oldEntry.getRestockableInDays(); - final Integer newRestockableInDays = newEntry.getRestockableInDays(); - return buildUpdateAction(oldRestockableInDays, newRestockableInDays, - () -> SetRestockableInDays.of(newRestockableInDays)); - } + /** + * Compares the {@code restockableInDays} values of an {@link InventoryEntry} and an {@link + * InventoryEntryDraft} and returns an {@link Optional} of update action, which would contain the + * {@code "setRestockableInDays"} {@link UpdateAction}. If both {@link InventoryEntry} and the + * {@link InventoryEntryDraft} have the same {@code restockableInDays} values, then no update + * action is needed and empty optional will be returned. + * + * @param oldEntry the inventory entry that should be updated + * @param newEntry the inventory entry draft which contains a new {@code restockableInDays} value + * @return optional containing update action or empty optional if restockable in days are + * identical + */ + @Nonnull + public static Optional> buildSetRestockableInDaysAction( + @Nonnull final InventoryEntry oldEntry, @Nonnull final InventoryEntryDraft newEntry) { + final Integer oldRestockableInDays = oldEntry.getRestockableInDays(); + final Integer newRestockableInDays = newEntry.getRestockableInDays(); + return buildUpdateAction( + oldRestockableInDays, + newRestockableInDays, + () -> SetRestockableInDays.of(newRestockableInDays)); + } - /** - * Compares the {@code expectedDelivery} values of an {@link InventoryEntry} and an {@link InventoryEntryDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setExpectedDelivery"} - * {@link UpdateAction}. If both {@link InventoryEntry} and {@link InventoryEntryDraft} have the same - * {@code expectedDelivery} values, then no update action is needed and empty optional will be returned. - * - * @param oldEntry the inventory entry that should be updated - * @param newEntry the inventory entry draft which contains new expected delivery - * @return optional containing update action or empty optional if expected deliveries are identical - */ - @Nonnull - public static Optional> buildSetExpectedDeliveryAction(@Nonnull final InventoryEntry - oldEntry, - @Nonnull final - InventoryEntryDraft newEntry) { - final ZonedDateTime oldExpectedDelivery = oldEntry.getExpectedDelivery(); - final ZonedDateTime newExpectedDelivery = newEntry.getExpectedDelivery(); - return buildUpdateAction(oldExpectedDelivery, newExpectedDelivery, - () -> SetExpectedDelivery.of(newExpectedDelivery)); - } + /** + * Compares the {@code expectedDelivery} values of an {@link InventoryEntry} and an {@link + * InventoryEntryDraft} and returns an {@link Optional} of update action, which would contain the + * {@code "setExpectedDelivery"} {@link UpdateAction}. If both {@link InventoryEntry} and {@link + * InventoryEntryDraft} have the same {@code expectedDelivery} values, then no update action is + * needed and empty optional will be returned. + * + * @param oldEntry the inventory entry that should be updated + * @param newEntry the inventory entry draft which contains new expected delivery + * @return optional containing update action or empty optional if expected deliveries are + * identical + */ + @Nonnull + public static Optional> buildSetExpectedDeliveryAction( + @Nonnull final InventoryEntry oldEntry, @Nonnull final InventoryEntryDraft newEntry) { + final ZonedDateTime oldExpectedDelivery = oldEntry.getExpectedDelivery(); + final ZonedDateTime newExpectedDelivery = newEntry.getExpectedDelivery(); + return buildUpdateAction( + oldExpectedDelivery, + newExpectedDelivery, + () -> SetExpectedDelivery.of(newExpectedDelivery)); + } - /** - * Compares the {@code supplyChannel}s of an {@link InventoryEntry} and an {@link InventoryEntryDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setSupplyChannel"} - * {@link UpdateAction}. If both {@link InventoryEntry} and {@link InventoryEntryDraft} have the same supply - * channel, then no update action is needed and empty optional will be returned. - * - * @param oldEntry the inventory entry that should be updated - * @param newEntry the inventory entry draft which contains new supply channel - * @return optional containing update action or empty optional if supply channels are identical - */ - @Nonnull - public static Optional> buildSetSupplyChannelAction(@Nonnull final InventoryEntry - oldEntry, - @Nonnull final InventoryEntryDraft - newEntry) { - final Reference oldSupplyChannel = oldEntry.getSupplyChannel(); - final ResourceIdentifier newSupplyChannel = newEntry.getSupplyChannel(); - return buildUpdateActionForReferences(oldSupplyChannel, newSupplyChannel, - () -> SetSupplyChannel.of(newSupplyChannel)); - } + /** + * Compares the {@code supplyChannel}s of an {@link InventoryEntry} and an {@link + * InventoryEntryDraft} and returns an {@link Optional} of update action, which would contain the + * {@code "setSupplyChannel"} {@link UpdateAction}. If both {@link InventoryEntry} and {@link + * InventoryEntryDraft} have the same supply channel, then no update action is needed and empty + * optional will be returned. + * + * @param oldEntry the inventory entry that should be updated + * @param newEntry the inventory entry draft which contains new supply channel + * @return optional containing update action or empty optional if supply channels are identical + */ + @Nonnull + public static Optional> buildSetSupplyChannelAction( + @Nonnull final InventoryEntry oldEntry, @Nonnull final InventoryEntryDraft newEntry) { + final Reference oldSupplyChannel = oldEntry.getSupplyChannel(); + final ResourceIdentifier newSupplyChannel = newEntry.getSupplyChannel(); + return buildUpdateActionForReferences( + oldSupplyChannel, newSupplyChannel, () -> SetSupplyChannel.of(newSupplyChannel)); + } - private InventoryUpdateActionUtils() { } + private InventoryUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/products/ActionGroup.java b/src/main/java/com/commercetools/sync/products/ActionGroup.java index 12c4bf7819..bfdb8fc324 100644 --- a/src/main/java/com/commercetools/sync/products/ActionGroup.java +++ b/src/main/java/com/commercetools/sync/products/ActionGroup.java @@ -1,20 +1,19 @@ package com.commercetools.sync.products; - public enum ActionGroup { - NAME, - DESCRIPTION, - SLUG, - CATEGORIES, - METATITLE, - METADESCRIPTION, - METAKEYWORDS, - TAXCATEGORY, - SEARCHKEYWORDS, - STATE, - ATTRIBUTES, - PRICES, - ASSETS, - IMAGES, - SKU + NAME, + DESCRIPTION, + SLUG, + CATEGORIES, + METATITLE, + METADESCRIPTION, + METAKEYWORDS, + TAXCATEGORY, + SEARCHKEYWORDS, + STATE, + ATTRIBUTES, + PRICES, + ASSETS, + IMAGES, + SKU } diff --git a/src/main/java/com/commercetools/sync/products/AttributeMetaData.java b/src/main/java/com/commercetools/sync/products/AttributeMetaData.java index 54e1c49ff5..ecc03208a3 100644 --- a/src/main/java/com/commercetools/sync/products/AttributeMetaData.java +++ b/src/main/java/com/commercetools/sync/products/AttributeMetaData.java @@ -2,50 +2,50 @@ import io.sphere.sdk.products.attributes.AttributeConstraint; import io.sphere.sdk.products.attributes.AttributeDefinition; - import javax.annotation.Nonnull; /** - * Custom container for product variant attribute information: its name and whether it has the constraint "SameForAll" - * or not. + * Custom container for product variant attribute information: its name and whether it has the + * constraint "SameForAll" or not. */ public final class AttributeMetaData { - private String name; - private boolean isSameForAll; + private String name; + private boolean isSameForAll; - private AttributeMetaData(@Nonnull final String name, final boolean isSameForAll) { - this.name = name; - this.isSameForAll = isSameForAll; - } + private AttributeMetaData(@Nonnull final String name, final boolean isSameForAll) { + this.name = name; + this.isSameForAll = isSameForAll; + } - /** - * Uses the supplied {@link AttributeDefinition} instance to infer the name and whether it has the constraint - * "SameForAll" or not, to instantiate a new {@link AttributeMetaData} containing the aforementioned information. - * - * @param attributeDefinition the instance for which the needed information is used. - * @return a new instance of {@link AttributeMetaData}. - */ - public static AttributeMetaData of(@Nonnull final AttributeDefinition attributeDefinition) { - boolean isSameForAll = attributeDefinition.getAttributeConstraint() - .equals(AttributeConstraint.SAME_FOR_ALL); - return new AttributeMetaData(attributeDefinition.getName(), isSameForAll); - } + /** + * Uses the supplied {@link AttributeDefinition} instance to infer the name and whether it has the + * constraint "SameForAll" or not, to instantiate a new {@link AttributeMetaData} containing the + * aforementioned information. + * + * @param attributeDefinition the instance for which the needed information is used. + * @return a new instance of {@link AttributeMetaData}. + */ + public static AttributeMetaData of(@Nonnull final AttributeDefinition attributeDefinition) { + boolean isSameForAll = + attributeDefinition.getAttributeConstraint().equals(AttributeConstraint.SAME_FOR_ALL); + return new AttributeMetaData(attributeDefinition.getName(), isSameForAll); + } - /** - * Gets the name of the attribute. - * - * @return the name of the attribute. - */ - public String getName() { - return name; - } + /** + * Gets the name of the attribute. + * + * @return the name of the attribute. + */ + public String getName() { + return name; + } - /** - * Does the attribute have a "SameForAll" constraint or not. - * - * @return boolean flag specifying whether the attribute has a "SameForAll" constraint or not. - */ - public boolean isSameForAll() { - return isSameForAll; - } + /** + * Does the attribute have a "SameForAll" constraint or not. + * + * @return boolean flag specifying whether the attribute has a "SameForAll" constraint or not. + */ + public boolean isSameForAll() { + return isSameForAll; + } } diff --git a/src/main/java/com/commercetools/sync/products/ProductSync.java b/src/main/java/com/commercetools/sync/products/ProductSync.java index 63c06e9572..3a8285777c 100644 --- a/src/main/java/com/commercetools/sync/products/ProductSync.java +++ b/src/main/java/com/commercetools/sync/products/ProductSync.java @@ -1,5 +1,14 @@ package com.commercetools.sync.products; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static com.commercetools.sync.products.utils.ProductSyncUtils.buildActions; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.getAllVariants; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.allOf; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.categories.CategorySyncOptionsBuilder; import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.SyncException; @@ -28,16 +37,12 @@ import com.commercetools.sync.services.impl.TaxCategoryServiceImpl; import com.commercetools.sync.services.impl.TypeServiceImpl; import com.commercetools.sync.services.impl.UnresolvedReferencesServiceImpl; -import com.commercetools.sync.taxcategories.TaxCategorySyncOptionsBuilder; import com.commercetools.sync.states.StateSyncOptionsBuilder; +import com.commercetools.sync.taxcategories.TaxCategorySyncOptionsBuilder; import io.sphere.sdk.channels.ChannelRole; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -50,464 +55,519 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static com.commercetools.sync.products.utils.ProductSyncUtils.buildActions; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.getAllVariants; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.allOf; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class ProductSync extends BaseSync { - private static final String CTP_PRODUCT_FETCH_FAILED = "Failed to fetch existing products with keys:" - + " '%s'."; - private static final String UNRESOLVED_REFERENCES_STORE_FETCH_FAILED = "Failed to fetch ProductDrafts waiting to " - + "be resolved with keys '%s'."; - private static final String UPDATE_FAILED = "Failed to update Product with key: '%s'. Reason: %s"; - private static final String FAILED_TO_PROCESS = "Failed to process the ProductDraft with key:'%s'. Reason: %s"; - private static final String FAILED_TO_FETCH_PRODUCT_TYPE = "Failed to fetch a productType for the product to " - + "build the products' attributes metadata."; - - private final ProductService productService; - private final ProductTypeService productTypeService; - private final ProductReferenceResolver productReferenceResolver; - private final UnresolvedReferencesService unresolvedReferencesService; - private final ProductBatchValidator batchValidator; - - private ConcurrentHashMap.KeySetView readyToResolve; - - /** - * Takes a {@link ProductSyncOptions} instance to instantiate a new {@link ProductSync} instance that could be - * used to sync product drafts with the given products in the CTP project specified in the injected - * {@link ProductSyncOptions} instance. - * - * @param productSyncOptions the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - */ - public ProductSync(@Nonnull final ProductSyncOptions productSyncOptions) { - this(productSyncOptions, new ProductServiceImpl(productSyncOptions), - new ProductTypeServiceImpl(productSyncOptions), - new CategoryServiceImpl(CategorySyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build()), - new TypeServiceImpl(productSyncOptions), - new ChannelServiceImpl(productSyncOptions, Collections.singleton(ChannelRole.PRODUCT_DISTRIBUTION)), - new CustomerGroupServiceImpl(productSyncOptions), - new TaxCategoryServiceImpl(TaxCategorySyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build()), - new StateServiceImpl(StateSyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build()), - new UnresolvedReferencesServiceImpl(productSyncOptions), - new CustomObjectServiceImpl(CustomObjectSyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build())); + private static final String CTP_PRODUCT_FETCH_FAILED = + "Failed to fetch existing products with keys:" + " '%s'."; + private static final String UNRESOLVED_REFERENCES_STORE_FETCH_FAILED = + "Failed to fetch ProductDrafts waiting to " + "be resolved with keys '%s'."; + private static final String UPDATE_FAILED = "Failed to update Product with key: '%s'. Reason: %s"; + private static final String FAILED_TO_PROCESS = + "Failed to process the ProductDraft with key:'%s'. Reason: %s"; + private static final String FAILED_TO_FETCH_PRODUCT_TYPE = + "Failed to fetch a productType for the product to " + + "build the products' attributes metadata."; + + private final ProductService productService; + private final ProductTypeService productTypeService; + private final ProductReferenceResolver productReferenceResolver; + private final UnresolvedReferencesService unresolvedReferencesService; + private final ProductBatchValidator batchValidator; + + private ConcurrentHashMap.KeySetView readyToResolve; + + /** + * Takes a {@link ProductSyncOptions} instance to instantiate a new {@link ProductSync} instance + * that could be used to sync product drafts with the given products in the CTP project specified + * in the injected {@link ProductSyncOptions} instance. + * + * @param productSyncOptions the container of all the options of the sync process including the + * CTP project client and/or configuration and other sync-specific options. + */ + public ProductSync(@Nonnull final ProductSyncOptions productSyncOptions) { + this( + productSyncOptions, + new ProductServiceImpl(productSyncOptions), + new ProductTypeServiceImpl(productSyncOptions), + new CategoryServiceImpl( + CategorySyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build()), + new TypeServiceImpl(productSyncOptions), + new ChannelServiceImpl( + productSyncOptions, Collections.singleton(ChannelRole.PRODUCT_DISTRIBUTION)), + new CustomerGroupServiceImpl(productSyncOptions), + new TaxCategoryServiceImpl( + TaxCategorySyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build()), + new StateServiceImpl(StateSyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build()), + new UnresolvedReferencesServiceImpl(productSyncOptions), + new CustomObjectServiceImpl( + CustomObjectSyncOptionsBuilder.of(productSyncOptions.getCtpClient()).build())); + } + + ProductSync( + @Nonnull final ProductSyncOptions productSyncOptions, + @Nonnull final ProductService productService, + @Nonnull final ProductTypeService productTypeService, + @Nonnull final CategoryService categoryService, + @Nonnull final TypeService typeService, + @Nonnull final ChannelService channelService, + @Nonnull final CustomerGroupService customerGroupService, + @Nonnull final TaxCategoryService taxCategoryService, + @Nonnull final StateService stateService, + @Nonnull final UnresolvedReferencesService unresolvedReferencesService, + @Nonnull final CustomObjectService customObjectService) { + super(new ProductSyncStatistics(), productSyncOptions); + this.productService = productService; + this.productTypeService = productTypeService; + this.productReferenceResolver = + new ProductReferenceResolver( + getSyncOptions(), + productTypeService, + categoryService, + typeService, + channelService, + customerGroupService, + taxCategoryService, + stateService, + productService, + customObjectService); + this.unresolvedReferencesService = unresolvedReferencesService; + this.batchValidator = new ProductBatchValidator(getSyncOptions(), getStatistics()); + } + + @Override + protected CompletionStage process( + @Nonnull final List resourceDrafts) { + final List> batches = + batchElements(resourceDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, CompletableFuture.completedFuture(statistics)); + } + + @Override + protected CompletionStage processBatch( + @Nonnull final List batch) { + + readyToResolve = ConcurrentHashMap.newKeySet(); + + final ImmutablePair, ProductBatchValidator.ReferencedKeys> result = + batchValidator.validateAndCollectReferencedKeys(batch); + + final Set validDrafts = result.getLeft(); + if (validDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return CompletableFuture.completedFuture(statistics); } - ProductSync(@Nonnull final ProductSyncOptions productSyncOptions, @Nonnull final ProductService productService, - @Nonnull final ProductTypeService productTypeService, @Nonnull final CategoryService categoryService, - @Nonnull final TypeService typeService, @Nonnull final ChannelService channelService, - @Nonnull final CustomerGroupService customerGroupService, - @Nonnull final TaxCategoryService taxCategoryService, @Nonnull final StateService stateService, - @Nonnull final UnresolvedReferencesService unresolvedReferencesService, - @Nonnull final CustomObjectService customObjectService) { - super(new ProductSyncStatistics(), productSyncOptions); - this.productService = productService; - this.productTypeService = productTypeService; - this.productReferenceResolver = new ProductReferenceResolver(getSyncOptions(), productTypeService, - categoryService, typeService, channelService, customerGroupService, taxCategoryService, stateService, - productService, customObjectService); - this.unresolvedReferencesService = unresolvedReferencesService; - this.batchValidator = new ProductBatchValidator(getSyncOptions(), getStatistics()); - } - - @Override - protected CompletionStage process(@Nonnull final List resourceDrafts) { - final List> batches = batchElements(resourceDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, CompletableFuture.completedFuture(statistics)); - } - - @Override - protected CompletionStage processBatch(@Nonnull final List batch) { - - readyToResolve = ConcurrentHashMap.newKeySet(); - - final ImmutablePair, ProductBatchValidator.ReferencedKeys> result - = batchValidator.validateAndCollectReferencedKeys(batch); - - final Set validDrafts = result.getLeft(); - if (validDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return CompletableFuture.completedFuture(statistics); - } - - return productReferenceResolver - .populateKeyToIdCachesForReferencedKeys(result.getRight()) - .handle(ImmutablePair::new) - .thenCompose(cachingResponse -> { - final Throwable cachingException = cachingResponse.getValue(); - if (cachingException != null) { - handleError(new SyncException("Failed to build a cache of keys to ids.", cachingException), - validDrafts.size()); - return CompletableFuture.completedFuture(null); - } - - final Map productKeyToIdCache = cachingResponse.getKey(); - return syncBatch(validDrafts, productKeyToIdCache); + return productReferenceResolver + .populateKeyToIdCachesForReferencedKeys(result.getRight()) + .handle(ImmutablePair::new) + .thenCompose( + cachingResponse -> { + final Throwable cachingException = cachingResponse.getValue(); + if (cachingException != null) { + handleError( + new SyncException("Failed to build a cache of keys to ids.", cachingException), + validDrafts.size()); + return CompletableFuture.completedFuture(null); + } + + final Map productKeyToIdCache = cachingResponse.getKey(); + return syncBatch(validDrafts, productKeyToIdCache); }) - .thenApply(ignoredResult -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignoredResult -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); - } + } - @Nonnull - private CompletionStage syncBatch( - @Nonnull final Set productDrafts, - @Nonnull final Map keyToIdCache) { + @Nonnull + private CompletionStage syncBatch( + @Nonnull final Set productDrafts, + @Nonnull final Map keyToIdCache) { - if (productDrafts.isEmpty()) { - return CompletableFuture.completedFuture(null); - } - - final Set productDraftKeys = productDrafts - .stream() - .map(ProductDraft::getKey) - .collect(Collectors.toSet()); - - return productService - .fetchMatchingProductsByKeys(productDraftKeys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Throwable fetchException = fetchResponse.getValue(); - if (fetchException != null) { - final String errorMessage = format(CTP_PRODUCT_FETCH_FAILED, productDraftKeys); - handleError( new SyncException(errorMessage, fetchException), productDraftKeys.size()); - return CompletableFuture.completedFuture(null); - } else { - final Set matchingProducts = fetchResponse.getKey(); - return syncOrKeepTrack(productDrafts, matchingProducts, keyToIdCache) - .thenCompose(aVoid -> resolveNowReadyReferences(keyToIdCache)); - } - }); + if (productDrafts.isEmpty()) { + return CompletableFuture.completedFuture(null); } - /** - * Given a set of product drafts, for each new draft: if it doesn't have any product references which are missing, - * it syncs the new draft. However, if it does have missing references, it keeps track of it by persisting it. - * - * @param oldProducts old product types. - * @param newProducts drafts that need to be synced. - * @return a {@link CompletionStage} which contains an empty result after execution of the update - */ - @Nonnull - private CompletionStage syncOrKeepTrack( - @Nonnull final Set newProducts, - @Nonnull final Set oldProducts, - @Nonnull final Map keyToIdCache) { - - return allOf(newProducts - .stream() - .map(newDraft -> { - final Set missingReferencedProductKeys = - getMissingReferencedProductKeys(newDraft, keyToIdCache); - - if (!missingReferencedProductKeys.isEmpty()) { + final Set productDraftKeys = + productDrafts.stream().map(ProductDraft::getKey).collect(Collectors.toSet()); + + return productService + .fetchMatchingProductsByKeys(productDraftKeys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Throwable fetchException = fetchResponse.getValue(); + if (fetchException != null) { + final String errorMessage = format(CTP_PRODUCT_FETCH_FAILED, productDraftKeys); + handleError( + new SyncException(errorMessage, fetchException), productDraftKeys.size()); + return CompletableFuture.completedFuture(null); + } else { + final Set matchingProducts = fetchResponse.getKey(); + return syncOrKeepTrack(productDrafts, matchingProducts, keyToIdCache) + .thenCompose(aVoid -> resolveNowReadyReferences(keyToIdCache)); + } + }); + } + + /** + * Given a set of product drafts, for each new draft: if it doesn't have any product references + * which are missing, it syncs the new draft. However, if it does have missing references, it + * keeps track of it by persisting it. + * + * @param oldProducts old product types. + * @param newProducts drafts that need to be synced. + * @return a {@link CompletionStage} which contains an empty result after execution of the update + */ + @Nonnull + private CompletionStage syncOrKeepTrack( + @Nonnull final Set newProducts, + @Nonnull final Set oldProducts, + @Nonnull final Map keyToIdCache) { + + return allOf( + newProducts.stream() + .map( + newDraft -> { + final Set missingReferencedProductKeys = + getMissingReferencedProductKeys(newDraft, keyToIdCache); + + if (!missingReferencedProductKeys.isEmpty()) { return keepTrackOfMissingReferences(newDraft, missingReferencedProductKeys); - } else { + } else { return syncDraft(oldProducts, newDraft); - } - }) + } + }) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); - } + } - private Set getMissingReferencedProductKeys( - @Nonnull final ProductDraft newProduct, - @Nonnull final Map keyToIdCache) { + private Set getMissingReferencedProductKeys( + @Nonnull final ProductDraft newProduct, @Nonnull final Map keyToIdCache) { - final Set referencedProductKeys = getAllVariants(newProduct) - .stream() + final Set referencedProductKeys = + getAllVariants(newProduct).stream() .map(ProductBatchValidator::getReferencedProductKeys) .flatMap(Collection::stream) .collect(Collectors.toSet()); - return referencedProductKeys - .stream() - .filter(key -> !keyToIdCache.containsKey(key)) - .collect(Collectors.toSet()); - } + return referencedProductKeys.stream() + .filter(key -> !keyToIdCache.containsKey(key)) + .collect(Collectors.toSet()); + } - private CompletionStage> keepTrackOfMissingReferences( - @Nonnull final ProductDraft newProduct, - @Nonnull final Set missingReferencedProductKeys) { + private CompletionStage> keepTrackOfMissingReferences( + @Nonnull final ProductDraft newProduct, + @Nonnull final Set missingReferencedProductKeys) { - missingReferencedProductKeys.forEach(missingParentKey -> - statistics.addMissingDependency(missingParentKey, newProduct.getKey())); - return unresolvedReferencesService.save(new WaitingToBeResolved(newProduct, missingReferencedProductKeys)); - } + missingReferencedProductKeys.forEach( + missingParentKey -> statistics.addMissingDependency(missingParentKey, newProduct.getKey())); + return unresolvedReferencesService.save( + new WaitingToBeResolved(newProduct, missingReferencedProductKeys)); + } - @Nonnull - private CompletionStage resolveNowReadyReferences(final Map keyToIdCache) { + @Nonnull + private CompletionStage resolveNowReadyReferences(final Map keyToIdCache) { - // We delete anyways the keys from the statistics before we attempt resolution, because even if resolution fails - // the products that failed to be synced would be counted as failed. + // We delete anyways the keys from the statistics before we attempt resolution, because even if + // resolution fails + // the products that failed to be synced would be counted as failed. - final Set referencingDraftKeys = readyToResolve - .stream() + final Set referencingDraftKeys = + readyToResolve.stream() .map(statistics::removeAndGetReferencingKeys) .filter(Objects::nonNull) .flatMap(Set::stream) .collect(Collectors.toSet()); - if (referencingDraftKeys.isEmpty()) { - return CompletableFuture.completedFuture(null); - } - - - final Set readyToSync = new HashSet<>(); - final Set waitingDraftsToBeUpdated = new HashSet<>(); - - return unresolvedReferencesService - .fetch(referencingDraftKeys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Set waitingDrafts = fetchResponse.getKey(); - final Throwable fetchException = fetchResponse.getValue(); - - if (fetchException != null) { - final String errorMessage = format(UNRESOLVED_REFERENCES_STORE_FETCH_FAILED, referencingDraftKeys); - handleError(new SyncException(errorMessage, fetchException), referencingDraftKeys.size()); - return CompletableFuture.completedFuture(null); - } - - waitingDrafts - .forEach(waitingDraft -> { - final Set missingReferencedProductKeys = waitingDraft.getMissingReferencedProductKeys(); - missingReferencedProductKeys.removeAll(readyToResolve); - - if (missingReferencedProductKeys.isEmpty()) { - readyToSync.add(waitingDraft.getProductDraft()); - } else { - waitingDraftsToBeUpdated.add(waitingDraft); - } - }); + if (referencingDraftKeys.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + final Set readyToSync = new HashSet<>(); + final Set waitingDraftsToBeUpdated = new HashSet<>(); + + return unresolvedReferencesService + .fetch(referencingDraftKeys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Set waitingDrafts = fetchResponse.getKey(); + final Throwable fetchException = fetchResponse.getValue(); + + if (fetchException != null) { + final String errorMessage = + format(UNRESOLVED_REFERENCES_STORE_FETCH_FAILED, referencingDraftKeys); + handleError( + new SyncException(errorMessage, fetchException), referencingDraftKeys.size()); + return CompletableFuture.completedFuture(null); + } + + waitingDrafts.forEach( + waitingDraft -> { + final Set missingReferencedProductKeys = + waitingDraft.getMissingReferencedProductKeys(); + missingReferencedProductKeys.removeAll(readyToResolve); + + if (missingReferencedProductKeys.isEmpty()) { + readyToSync.add(waitingDraft.getProductDraft()); + } else { + waitingDraftsToBeUpdated.add(waitingDraft); + } + }); - return updateWaitingDrafts(waitingDraftsToBeUpdated) - .thenCompose(aVoid -> syncBatch(readyToSync, keyToIdCache)) - .thenCompose(aVoid -> removeFromWaiting(readyToSync)); + return updateWaitingDrafts(waitingDraftsToBeUpdated) + .thenCompose(aVoid -> syncBatch(readyToSync, keyToIdCache)) + .thenCompose(aVoid -> removeFromWaiting(readyToSync)); }); - } + } - @Nonnull - private CompletableFuture updateWaitingDrafts( - @Nonnull final Set waitingDraftsToBeUpdated) { + @Nonnull + private CompletableFuture updateWaitingDrafts( + @Nonnull final Set waitingDraftsToBeUpdated) { - return allOf(waitingDraftsToBeUpdated - .stream() + return allOf( + waitingDraftsToBeUpdated.stream() .map(unresolvedReferencesService::save) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); - } + } - @Nonnull - private CompletableFuture removeFromWaiting( - @Nonnull final Set drafts) { - return allOf(drafts - .stream() + @Nonnull + private CompletableFuture removeFromWaiting(@Nonnull final Set drafts) { + return allOf( + drafts.stream() .map(ProductDraft::getKey) .map(unresolvedReferencesService::delete) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); - } - - - @Nonnull - private CompletionStage syncDraft( - @Nonnull final Set oldProducts, - @Nonnull final ProductDraft newProductDraft) { + } - final Map oldProductMap = - oldProducts.stream().collect(toMap(Product::getKey, identity())); + @Nonnull + private CompletionStage syncDraft( + @Nonnull final Set oldProducts, @Nonnull final ProductDraft newProductDraft) { - return productReferenceResolver - .resolveReferences(newProductDraft) - .thenCompose(resolvedDraft -> { + final Map oldProductMap = + oldProducts.stream().collect(toMap(Product::getKey, identity())); - final Product oldProduct = oldProductMap.get(newProductDraft.getKey()); - - return ofNullable(oldProduct) - .map(product -> fetchProductAttributesMetadataAndUpdate(oldProduct, resolvedDraft)) - .orElseGet(() -> applyCallbackAndCreate(resolvedDraft)); + return productReferenceResolver + .resolveReferences(newProductDraft) + .thenCompose( + resolvedDraft -> { + final Product oldProduct = oldProductMap.get(newProductDraft.getKey()); + return ofNullable(oldProduct) + .map( + product -> fetchProductAttributesMetadataAndUpdate(oldProduct, resolvedDraft)) + .orElseGet(() -> applyCallbackAndCreate(resolvedDraft)); }) - - .exceptionally(completionException -> { - final String errorMessage = format(FAILED_TO_PROCESS, newProductDraft.getKey(), - completionException.getMessage()); - handleError(new SyncException(errorMessage, completionException), 1); - return null; + .exceptionally( + completionException -> { + final String errorMessage = + format( + FAILED_TO_PROCESS, + newProductDraft.getKey(), + completionException.getMessage()); + handleError(new SyncException(errorMessage, completionException), 1); + return null; }); + } - } + @Nonnull + private CompletionStage fetchProductAttributesMetadataAndUpdate( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { - @Nonnull - private CompletionStage fetchProductAttributesMetadataAndUpdate( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - - return productTypeService - .fetchCachedProductAttributeMetaDataMap(oldProduct.getProductType().getId()) - .thenCompose(optionalAttributesMetaDataMap -> + return productTypeService + .fetchCachedProductAttributeMetaDataMap(oldProduct.getProductType().getId()) + .thenCompose( + optionalAttributesMetaDataMap -> optionalAttributesMetaDataMap - .map(attributeMetaDataMap -> { - final List> updateActions = - buildActions(oldProduct, newProduct, syncOptions, attributeMetaDataMap); - - final List> beforeUpdateCallBackApplied = - syncOptions.applyBeforeUpdateCallback(updateActions, newProduct, oldProduct); - - if (!beforeUpdateCallBackApplied.isEmpty()) { - return updateProduct(oldProduct, newProduct, beforeUpdateCallBackApplied); - } - - return CompletableFuture.completedFuture((Void) null); - - }) - .orElseGet(() -> { - final String errorMessage = - format(UPDATE_FAILED, oldProduct.getKey(), FAILED_TO_FETCH_PRODUCT_TYPE); - handleError(errorMessage, oldProduct, newProduct); - return CompletableFuture.completedFuture(null); - }) - ); - } - - @Nonnull - private CompletionStage updateProduct( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final List> updateActions) { - - return productService - .updateProduct(oldProduct, updateActions) - .handle(ImmutablePair::new) - .thenCompose(updateResponse -> { - final Throwable sphereException = updateResponse.getValue(); - if (sphereException != null) { - return executeSupplierIfConcurrentModificationException(sphereException, - () -> fetchAndUpdate(oldProduct, newProduct), + .map( + attributeMetaDataMap -> { + final List> updateActions = + buildActions( + oldProduct, newProduct, syncOptions, attributeMetaDataMap); + + final List> beforeUpdateCallBackApplied = + syncOptions.applyBeforeUpdateCallback( + updateActions, newProduct, oldProduct); + + if (!beforeUpdateCallBackApplied.isEmpty()) { + return updateProduct( + oldProduct, newProduct, beforeUpdateCallBackApplied); + } + + return CompletableFuture.completedFuture((Void) null); + }) + .orElseGet( () -> { - final String productKey = oldProduct.getKey(); - handleError(format(UPDATE_FAILED, productKey, sphereException), sphereException, - oldProduct, newProduct, updateActions); - return CompletableFuture.completedFuture(null); - }); - } else { - statistics.incrementUpdated(); - return CompletableFuture.completedFuture(null); - } + final String errorMessage = + format( + UPDATE_FAILED, oldProduct.getKey(), FAILED_TO_FETCH_PRODUCT_TYPE); + handleError(errorMessage, oldProduct, newProduct); + return CompletableFuture.completedFuture(null); + })); + } + + @Nonnull + private CompletionStage updateProduct( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final List> updateActions) { + + return productService + .updateProduct(oldProduct, updateActions) + .handle(ImmutablePair::new) + .thenCompose( + updateResponse -> { + final Throwable sphereException = updateResponse.getValue(); + if (sphereException != null) { + return executeSupplierIfConcurrentModificationException( + sphereException, + () -> fetchAndUpdate(oldProduct, newProduct), + () -> { + final String productKey = oldProduct.getKey(); + handleError( + format(UPDATE_FAILED, productKey, sphereException), + sphereException, + oldProduct, + newProduct, + updateActions); + return CompletableFuture.completedFuture(null); + }); + } else { + statistics.incrementUpdated(); + return CompletableFuture.completedFuture(null); + } }); - } - - - /** - * Given an existing {@link Product} and a new {@link ProductDraft}, first fetches a fresh copy of the existing - * product, then it calculates all the update actions required to synchronize the existing product to be the - * same as the new one. If there are update actions found, a request is made to CTP to update the - * existing one, otherwise it doesn't issue a request. - * - * @param oldProduct the product which could be updated. - * @param newProduct the product draft where we get the new data. - * @return a future which contains an empty result after execution of the update. - */ - @Nonnull - private CompletionStage fetchAndUpdate(@Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - - final String key = oldProduct.getKey(); - return productService - .fetchProduct(key) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Optional fetchedProductOptional = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(UPDATE_FAILED, key, "Failed to fetch from CTP while " - + "retrying after concurrency modification."); - handleError(errorMessage, exception, oldProduct, newProduct, null); - return CompletableFuture.completedFuture(null); - } - - return fetchedProductOptional - .map(fetchedProduct -> fetchProductAttributesMetadataAndUpdate(fetchedProduct, newProduct)) - .orElseGet(() -> { - final String errorMessage = format(UPDATE_FAILED, key, "Not found when attempting to fetch " - + "while retrying after concurrency modification."); + } + + /** + * Given an existing {@link Product} and a new {@link ProductDraft}, first fetches a fresh copy of + * the existing product, then it calculates all the update actions required to synchronize the + * existing product to be the same as the new one. If there are update actions found, a request is + * made to CTP to update the existing one, otherwise it doesn't issue a request. + * + * @param oldProduct the product which could be updated. + * @param newProduct the product draft where we get the new data. + * @return a future which contains an empty result after execution of the update. + */ + @Nonnull + private CompletionStage fetchAndUpdate( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + + final String key = oldProduct.getKey(); + return productService + .fetchProduct(key) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Optional fetchedProductOptional = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + UPDATE_FAILED, + key, + "Failed to fetch from CTP while " + + "retrying after concurrency modification."); + handleError(errorMessage, exception, oldProduct, newProduct, null); + return CompletableFuture.completedFuture(null); + } + + return fetchedProductOptional + .map( + fetchedProduct -> + fetchProductAttributesMetadataAndUpdate(fetchedProduct, newProduct)) + .orElseGet( + () -> { + final String errorMessage = + format( + UPDATE_FAILED, + key, + "Not found when attempting to fetch " + + "while retrying after concurrency modification."); handleError(errorMessage, oldProduct, newProduct); return CompletableFuture.completedFuture(null); - }); + }); }); - } - - @Nonnull - private CompletionStage applyCallbackAndCreate(@Nonnull final ProductDraft productDraft) { - return syncOptions - .applyBeforeCreateCallback(productDraft) - .map(draft -> productService - .createProduct(draft) - .thenAccept(productOptional -> { - if (productOptional.isPresent()) { - readyToResolve.add(productDraft.getKey()); - statistics.incrementCreated(); - } else { - statistics.incrementFailed(); - } - }) - ) - .orElse(CompletableFuture.completedFuture(null)); - } - - /** - * Given a {@link String} {@code errorMessage}, this method calls the optional error callback specified in the - * {@code syncOptions} and updates the {@code statistics} instance by incrementing the total number of failed - * products to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - */ - private void handleError(@Nonnull final String errorMessage, @Nullable final Product oldResource, - @Nullable final ProductDraft newResource) { - handleError(errorMessage, null, oldResource, newResource, null); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed products to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - * @param exception The exception that called caused the failure, if any. - * @param oldProduct the product which could be updated. - * @param newProduct the product draft where we get the new data. - * @param updateActions the update actions to update the {@link Product} with. - */ - private void handleError(@Nonnull final String errorMessage, @Nullable final Throwable exception, - @Nullable final Product oldProduct, @Nullable final ProductDraft newProduct, - @Nullable final List> updateActions) { - SyncException syncException = exception != null ? new SyncException(errorMessage, exception) + } + + @Nonnull + private CompletionStage applyCallbackAndCreate(@Nonnull final ProductDraft productDraft) { + return syncOptions + .applyBeforeCreateCallback(productDraft) + .map( + draft -> + productService + .createProduct(draft) + .thenAccept( + productOptional -> { + if (productOptional.isPresent()) { + readyToResolve.add(productDraft.getKey()); + statistics.incrementCreated(); + } else { + statistics.incrementFailed(); + } + })) + .orElse(CompletableFuture.completedFuture(null)); + } + + /** + * Given a {@link String} {@code errorMessage}, this method calls the optional error callback + * specified in the {@code syncOptions} and updates the {@code statistics} instance by + * incrementing the total number of failed products to sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + */ + private void handleError( + @Nonnull final String errorMessage, + @Nullable final Product oldResource, + @Nullable final ProductDraft newResource) { + handleError(errorMessage, null, oldResource, newResource, null); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed products to sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + * @param exception The exception that called caused the failure, if any. + * @param oldProduct the product which could be updated. + * @param newProduct the product draft where we get the new data. + * @param updateActions the update actions to update the {@link Product} with. + */ + private void handleError( + @Nonnull final String errorMessage, + @Nullable final Throwable exception, + @Nullable final Product oldProduct, + @Nullable final ProductDraft newProduct, + @Nullable final List> updateActions) { + SyncException syncException = + exception != null + ? new SyncException(errorMessage, exception) : new SyncException(errorMessage); - syncOptions.applyErrorCallback(syncException, oldProduct, newProduct, updateActions); - statistics.incrementFailed(); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed product to sync with the supplied {@code failedTimes}. - * - * @param syncException The exception that caused the failure. - * @param failedTimes The number of times that the failed products counter is incremented. - */ - private void handleError(@Nonnull final SyncException syncException, - final int failedTimes) { - syncOptions.applyErrorCallback(syncException); - statistics.incrementFailed(failedTimes); - } + syncOptions.applyErrorCallback(syncException, oldProduct, newProduct, updateActions); + statistics.incrementFailed(); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed product to sync with the + * supplied {@code failedTimes}. + * + * @param syncException The exception that caused the failure. + * @param failedTimes The number of times that the failed products counter is incremented. + */ + private void handleError(@Nonnull final SyncException syncException, final int failedTimes) { + syncOptions.applyErrorCallback(syncException); + statistics.incrementFailed(failedTimes); + } } diff --git a/src/main/java/com/commercetools/sync/products/ProductSyncOptions.java b/src/main/java/com/commercetools/sync/products/ProductSyncOptions.java index f97987fc8b..406618cb1d 100644 --- a/src/main/java/com/commercetools/sync/products/ProductSyncOptions.java +++ b/src/main/java/com/commercetools/sync/products/ProductSyncOptions.java @@ -1,5 +1,7 @@ package com.commercetools.sync.products; +import static java.util.Optional.ofNullable; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; @@ -9,52 +11,66 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static java.util.Optional.ofNullable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class ProductSyncOptions extends BaseSyncOptions { - private final SyncFilter syncFilter; // which attributes to calculate update actions to black list or white list - private final boolean ensurePriceChannels; + private final SyncFilter + syncFilter; // which attributes to calculate update actions to black list or white list + private final boolean ensurePriceChannels; - ProductSyncOptions(@Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallBack, - @Nullable final TriConsumer, Optional> - warningCallBack, - final int batchSize, - @Nullable final SyncFilter syncFilter, - @Nullable final TriFunction>, ProductDraft, Product, - List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, final long cacheSize, - boolean ensurePriceChannels) { - super(ctpClient, errorCallBack, warningCallBack, batchSize, beforeUpdateCallback, - beforeCreateCallback, cacheSize); - this.syncFilter = ofNullable(syncFilter).orElseGet(SyncFilter::of); - this.ensurePriceChannels = ensurePriceChannels; - } + ProductSyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallBack, + @Nullable + final TriConsumer, Optional> + warningCallBack, + final int batchSize, + @Nullable final SyncFilter syncFilter, + @Nullable + final TriFunction< + List>, ProductDraft, Product, List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize, + boolean ensurePriceChannels) { + super( + ctpClient, + errorCallBack, + warningCallBack, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + this.syncFilter = ofNullable(syncFilter).orElseGet(SyncFilter::of); + this.ensurePriceChannels = ensurePriceChannels; + } - /** - * Returns the {@link SyncFilter} set to {@code this} {@link ProductSyncOptions}. - * It represents either a blacklist or a whitelist for filtering certain update action groups. - * - * @return the {@link SyncFilter} set to {@code this} {@link ProductSyncOptions}. - */ - @Nonnull - public SyncFilter getSyncFilter() { - return syncFilter; - } + /** + * Returns the {@link SyncFilter} set to {@code this} {@link ProductSyncOptions}. It represents + * either a blacklist or a whitelist for filtering certain update action groups. + * + * @return the {@link SyncFilter} set to {@code this} {@link ProductSyncOptions}. + */ + @Nonnull + public SyncFilter getSyncFilter() { + return syncFilter; + } - /** - * @return option that indicates whether the sync process should create price channel of the given key - * when it doesn't exist in a target project yet. - */ - public boolean shouldEnsurePriceChannels() { - return ensurePriceChannels; - } + /** + * @return option that indicates whether the sync process should create price channel of the given + * key when it doesn't exist in a target project yet. + */ + public boolean shouldEnsurePriceChannels() { + return ensurePriceChannels; + } } diff --git a/src/main/java/com/commercetools/sync/products/ProductSyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/products/ProductSyncOptionsBuilder.java index 6ff1d746de..bba86ea2e5 100644 --- a/src/main/java/com/commercetools/sync/products/ProductSyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/products/ProductSyncOptionsBuilder.java @@ -4,85 +4,84 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; - import javax.annotation.Nonnull; public final class ProductSyncOptionsBuilder - extends BaseSyncOptionsBuilder { - public static final int BATCH_SIZE_DEFAULT = 30; - private SyncFilter syncFilter; - static final boolean ENSURE_CHANNELS_DEFAULT = false; - private boolean ensurePriceChannels = ENSURE_CHANNELS_DEFAULT; - - private ProductSyncOptionsBuilder(final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } + extends BaseSyncOptionsBuilder< + ProductSyncOptionsBuilder, ProductSyncOptions, Product, ProductDraft> { + public static final int BATCH_SIZE_DEFAULT = 30; + private SyncFilter syncFilter; + static final boolean ENSURE_CHANNELS_DEFAULT = false; + private boolean ensurePriceChannels = ENSURE_CHANNELS_DEFAULT; - public static ProductSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new ProductSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); - } + private ProductSyncOptionsBuilder(final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } - /** - * Set option that defines {@link SyncFilter} for the sync, which defines either a blacklist or a whitelist for - * filtering certain update action groups. - * - *

The action groups can be a list of any of the values of the enum {@link ActionGroup}, namely: - *

    - *
  • ATTRIBUTES
  • - *
  • PRICES
  • - *
  • IMAGES
  • - *
  • CATEGORIES
  • - *
  • .. and others
  • - *
- * - * - * @param syncFilter defines either a blacklist or a whitelist for filtering certain update action groups. - * - * @return {@code this} instance of {@link ProductSyncOptionsBuilder} - */ - @Nonnull - public ProductSyncOptionsBuilder syncFilter(@Nonnull final SyncFilter syncFilter) { - this.syncFilter = syncFilter; - return this; - } + public static ProductSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new ProductSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } - /** - * Set option that indicates whether sync process should create a price channel of given key when it doesn't exist - * in a target project yet. If set to {@code true}, the sync process would try to create the new price channel of - * the given key, otherwise the sync process would log an error and fail to process the draft with the given price - * channel key. - * - *

This property is {@link ProductSyncOptionsBuilder#ENSURE_CHANNELS_DEFAULT} by default. - * - * @param ensurePriceChannels boolean that indicates whether sync process should create price channel of given key - * when it doesn't exist in a target project yet - * @return {@code this} instance of {@link ProductSyncOptionsBuilder} - */ - @Nonnull - public ProductSyncOptionsBuilder ensurePriceChannels(final boolean ensurePriceChannels) { - this.ensurePriceChannels = ensurePriceChannels; - return this; - } + /** + * Set option that defines {@link SyncFilter} for the sync, which defines either a blacklist or a + * whitelist for filtering certain update action groups. + * + *

The action groups can be a list of any of the values of the enum {@link ActionGroup}, + * namely: + * + *

    + *
  • ATTRIBUTES + *
  • PRICES + *
  • IMAGES + *
  • CATEGORIES + *
  • .. and others + *
+ * + * @param syncFilter defines either a blacklist or a whitelist for filtering certain update action + * groups. + * @return {@code this} instance of {@link ProductSyncOptionsBuilder} + */ + @Nonnull + public ProductSyncOptionsBuilder syncFilter(@Nonnull final SyncFilter syncFilter) { + this.syncFilter = syncFilter; + return this; + } - @Override - @Nonnull - public ProductSyncOptions build() { - return new ProductSyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - syncFilter, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize, - ensurePriceChannels - ); - } + /** + * Set option that indicates whether sync process should create a price channel of given key when + * it doesn't exist in a target project yet. If set to {@code true}, the sync process would try to + * create the new price channel of the given key, otherwise the sync process would log an error + * and fail to process the draft with the given price channel key. + * + *

This property is {@link ProductSyncOptionsBuilder#ENSURE_CHANNELS_DEFAULT} by default. + * + * @param ensurePriceChannels boolean that indicates whether sync process should create price + * channel of given key when it doesn't exist in a target project yet + * @return {@code this} instance of {@link ProductSyncOptionsBuilder} + */ + @Nonnull + public ProductSyncOptionsBuilder ensurePriceChannels(final boolean ensurePriceChannels) { + this.ensurePriceChannels = ensurePriceChannels; + return this; + } + @Override + @Nonnull + public ProductSyncOptions build() { + return new ProductSyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + syncFilter, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize, + ensurePriceChannels); + } - @Override - protected ProductSyncOptionsBuilder getThis() { - return this; - } + @Override + protected ProductSyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/products/SyncFilter.java b/src/main/java/com/commercetools/sync/products/SyncFilter.java index 26a6d99c71..cc593d984c 100644 --- a/src/main/java/com/commercetools/sync/products/SyncFilter.java +++ b/src/main/java/com/commercetools/sync/products/SyncFilter.java @@ -1,87 +1,86 @@ package com.commercetools.sync.products; -import javax.annotation.Nonnull; +import static java.util.Arrays.asList; +import static java.util.Optional.ofNullable; + import java.util.Collections; import java.util.HashSet; import java.util.Set; - -import static java.util.Arrays.asList; -import static java.util.Optional.ofNullable; +import javax.annotation.Nonnull; /** - * Defines either a blacklist or a whitelist for filtering certain update action groups ({@link ActionGroup}). + * Defines either a blacklist or a whitelist for filtering certain update action groups ({@link + * ActionGroup}). * *

The action groups can be a list of any of the values of the enum {@link ActionGroup}, namely: + * *

    - *
  • ATTRIBUTES
  • - *
  • PRICES
  • - *
  • IMAGES
  • - *
  • CATEGORIES
  • - *
  • .. and others
  • + *
  • ATTRIBUTES + *
  • PRICES + *
  • IMAGES + *
  • CATEGORIES + *
  • .. and others *
* *

The {@code includeOnly} flag defines whether the list is to be blacklisted (@code false) or - * whitelisted (@code true). A blacklist means that everything but these action - * groups will be synced. A whitelist means that only these action groups will be synced. + * whitelisted (@code true). A blacklist means that everything but these action groups will + * be synced. A whitelist means that only these action groups will be synced. */ public final class SyncFilter { - /** - * Defines which attributes to calculate update actions for. - */ - private final Set actionGroups; + /** Defines which attributes to calculate update actions for. */ + private final Set actionGroups; - /** - * Defines the filter type: blacklist (false) or whitelist (true). - */ - private final boolean includeOnly; + /** Defines the filter type: blacklist (false) or whitelist (true). */ + private final boolean includeOnly; - private static SyncFilter defaultSyncFilter = null; + private static SyncFilter defaultSyncFilter = null; - private SyncFilter(final boolean includeOnly, @Nonnull final ActionGroup[] actionGroups) { - this.includeOnly = includeOnly; - this.actionGroups = new HashSet<>(asList(actionGroups)); - } + private SyncFilter(final boolean includeOnly, @Nonnull final ActionGroup[] actionGroups) { + this.includeOnly = includeOnly; + this.actionGroups = new HashSet<>(asList(actionGroups)); + } - private SyncFilter() { - this.includeOnly = false; - this.actionGroups = Collections.emptySet(); - } + private SyncFilter() { + this.includeOnly = false; + this.actionGroups = Collections.emptySet(); + } - @Nonnull - public static SyncFilter ofWhiteList(@Nonnull final ActionGroup ... actionGroups) { - return new SyncFilter(true, actionGroups); - } + @Nonnull + public static SyncFilter ofWhiteList(@Nonnull final ActionGroup... actionGroups) { + return new SyncFilter(true, actionGroups); + } - @Nonnull - public static SyncFilter ofBlackList(@Nonnull final ActionGroup ... actionGroups) { - return new SyncFilter(false, actionGroups); - } + @Nonnull + public static SyncFilter ofBlackList(@Nonnull final ActionGroup... actionGroups) { + return new SyncFilter(false, actionGroups); + } - @Nonnull - public static SyncFilter of() { - defaultSyncFilter = ofNullable(defaultSyncFilter).orElseGet(SyncFilter::new); - return defaultSyncFilter; - } + @Nonnull + public static SyncFilter of() { + defaultSyncFilter = ofNullable(defaultSyncFilter).orElseGet(SyncFilter::new); + return defaultSyncFilter; + } - /** - * Checks if the supplied {@link ActionGroup} passes {@code this} filter. - * - *

Passing the filter has an XOR logic as follows: - * - * - * - * - * - * - * - * - *
includeOnly actionGroups contains actionGroup passes filter
falsefalsetrue (actionGroup is not in blacklist)
falsetruefalse (actionGroup is in blacklist)
truefalsefalse (actionGroup is not in whitelist)
truetruetrue (actionGroup is in whitelist)
- * - * @param actionGroup the supplied action group to be tested if it passes the current filter. - * @return true if the {@link ActionGroup} passes {@code this} filter, otherwise false. - */ - public boolean filterActionGroup(@Nonnull final ActionGroup actionGroup) { - return includeOnly == actionGroups.contains(actionGroup); - } + /** + * Checks if the supplied {@link ActionGroup} passes {@code this} filter. + * + *

Passing the filter has an XOR logic as follows: + * + * + * + * + * + * + * + * + * + *
includeOnly actionGroups contains actionGroup passes filter
falsefalsetrue (actionGroup is not in blacklist)
falsetruefalse (actionGroup is in blacklist)
truefalsefalse (actionGroup is not in whitelist)
truetruetrue (actionGroup is in whitelist)
+ * + * @param actionGroup the supplied action group to be tested if it passes the current filter. + * @return true if the {@link ActionGroup} passes {@code this} filter, otherwise false. + */ + public boolean filterActionGroup(@Nonnull final ActionGroup actionGroup) { + return includeOnly == actionGroups.contains(actionGroup); + } } diff --git a/src/main/java/com/commercetools/sync/products/helpers/AssetCustomActionBuilder.java b/src/main/java/com/commercetools/sync/products/helpers/AssetCustomActionBuilder.java index f420fd77d1..9716c6f9f3 100644 --- a/src/main/java/com/commercetools/sync/products/helpers/AssetCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/products/helpers/AssetCustomActionBuilder.java @@ -1,5 +1,6 @@ package com.commercetools.sync.products.helpers; +import static io.sphere.sdk.types.CustomFieldsDraft.ofTypeIdAndJson; import com.commercetools.sync.commons.helpers.GenericCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -7,39 +8,38 @@ import io.sphere.sdk.products.Product; import io.sphere.sdk.products.commands.updateactions.SetAssetCustomField; import io.sphere.sdk.products.commands.updateactions.SetAssetCustomType; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; - -import static io.sphere.sdk.types.CustomFieldsDraft.ofTypeIdAndJson; public class AssetCustomActionBuilder implements GenericCustomActionBuilder { - @Override - @Nonnull - public UpdateAction buildRemoveCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String assetKey) { - return SetAssetCustomType.ofVariantIdAndAssetKey(variantId, assetKey, null, true); - } - - @Override - @Nonnull - public UpdateAction buildSetCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String assetKey, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - return SetAssetCustomType.ofVariantIdAndAssetKey(variantId, assetKey, - ofTypeIdAndJson(customTypeId, customFieldsJsonMap), true); - } - - @Override - @Nonnull - public UpdateAction buildSetCustomFieldAction(@Nullable final Integer variantId, - @Nullable final String assetKey, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - return SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(variantId, assetKey, customFieldName, customFieldValue, true); - } + @Override + @Nonnull + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String assetKey) { + return SetAssetCustomType.ofVariantIdAndAssetKey(variantId, assetKey, null, true); + } + + @Override + @Nonnull + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String assetKey, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + return SetAssetCustomType.ofVariantIdAndAssetKey( + variantId, assetKey, ofTypeIdAndJson(customTypeId, customFieldsJsonMap), true); + } + + @Override + @Nonnull + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String assetKey, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + return SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + variantId, assetKey, customFieldName, customFieldValue, true); + } } diff --git a/src/main/java/com/commercetools/sync/products/helpers/PriceCustomActionBuilder.java b/src/main/java/com/commercetools/sync/products/helpers/PriceCustomActionBuilder.java index 7ecbb9701f..a9c9e4b187 100644 --- a/src/main/java/com/commercetools/sync/products/helpers/PriceCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/products/helpers/PriceCustomActionBuilder.java @@ -1,41 +1,42 @@ package com.commercetools.sync.products.helpers; - import com.commercetools.sync.commons.helpers.GenericCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomField; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomType; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; public class PriceCustomActionBuilder implements GenericCustomActionBuilder { - @Override - @Nonnull - public UpdateAction buildRemoveCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String priceId) { - return SetProductPriceCustomType.ofRemoveType(priceId, true); - } + @Override + @Nonnull + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String priceId) { + return SetProductPriceCustomType.ofRemoveType(priceId, true); + } - @Override - @Nonnull - public UpdateAction buildSetCustomTypeAction(@Nullable final Integer variantId, - @Nullable final String priceId, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - return SetProductPriceCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap, priceId, true); - } + @Override + @Nonnull + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String priceId, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + return SetProductPriceCustomType.ofTypeIdAndJson( + customTypeId, customFieldsJsonMap, priceId, true); + } - @Override - @Nonnull - public UpdateAction buildSetCustomFieldAction(@Nullable final Integer variantId, - @Nullable final String priceId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - return SetProductPriceCustomField.ofJson(customFieldName, customFieldValue, priceId, true); - } + @Override + @Nonnull + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String priceId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + return SetProductPriceCustomField.ofJson(customFieldName, customFieldValue, priceId, true); + } } diff --git a/src/main/java/com/commercetools/sync/products/helpers/PriceReferenceResolver.java b/src/main/java/com/commercetools/sync/products/helpers/PriceReferenceResolver.java index 4c6fe88d54..a26872501c 100644 --- a/src/main/java/com/commercetools/sync/products/helpers/PriceReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/products/helpers/PriceReferenceResolver.java @@ -1,5 +1,9 @@ package com.commercetools.sync.products.helpers; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.CustomReferenceResolver; import com.commercetools.sync.products.ProductSyncOptions; @@ -12,228 +16,269 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.PriceDraft; import io.sphere.sdk.products.PriceDraftBuilder; - -import javax.annotation.Nonnull; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; +import javax.annotation.Nonnull; public final class PriceReferenceResolver extends CustomReferenceResolver { - private final ChannelService channelService; - private final CustomerGroupService customerGroupService; - static final String CHANNEL_DOES_NOT_EXIST = "Channel with key '%s' does not exist."; - static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type reference on " - + "PriceDraft with country:'%s' and value: '%s'."; - static final String FAILED_TO_RESOLVE_REFERENCE = "Failed to resolve '%s' reference on PriceDraft with " - + "country:'%s' and value: '%s'. Reason: %s"; - static final String CUSTOMER_GROUP_DOES_NOT_EXIST = "Customer Group with key '%s' does not exist."; - - /** - * Takes a {@link ProductSyncOptions} instance, {@link TypeService}, a {@link ChannelService} and a - * {@link CustomerGroupService} to instantiate a {@link PriceReferenceResolver} instance that could be used to - * resolve the prices of variant drafts in the CTP project specified in the injected {@link ProductSyncOptions} - * instance. - * - * @param options the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - * @param typeService the service to fetch the custom types for reference resolution. - * @param channelService the service to fetch the channels for reference resolution. - * @param customerGroupService the service to fetch the customer groups for reference resolution. - */ - public PriceReferenceResolver(@Nonnull final ProductSyncOptions options, - @Nonnull final TypeService typeService, - @Nonnull final ChannelService channelService, - @Nonnull final CustomerGroupService customerGroupService) { - super(options, typeService); - this.channelService = channelService; - this.customerGroupService = customerGroupService; - } + private final ChannelService channelService; + private final CustomerGroupService customerGroupService; + static final String CHANNEL_DOES_NOT_EXIST = "Channel with key '%s' does not exist."; + static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = + "Failed to resolve custom type reference on " + + "PriceDraft with country:'%s' and value: '%s'."; + static final String FAILED_TO_RESOLVE_REFERENCE = + "Failed to resolve '%s' reference on PriceDraft with " + + "country:'%s' and value: '%s'. Reason: %s"; + static final String CUSTOMER_GROUP_DOES_NOT_EXIST = + "Customer Group with key '%s' does not exist."; - /** - * Given a {@link PriceDraft} this method attempts to resolve the custom type and channel - * references to return a {@link CompletionStage} which contains a new instance of the draft with the resolved - * references. - * - *

The method then tries to fetch the key of the customer group, optimistically from a cache. - * If the id is not found in cache nor the CTP project the {@link ReferenceResolutionException} will be thrown. - * - * @param priceDraft the priceDraft to resolve it's references. - * @return a {@link CompletionStage} that contains as a result a new inventoryEntryDraft instance with resolved - * references or, in case an error occurs during reference resolution a - * {@link ReferenceResolutionException}. - */ - @Override - public CompletionStage resolveReferences(@Nonnull final PriceDraft priceDraft) { - return resolveCustomTypeReference(PriceDraftBuilder.of(priceDraft)) - .thenCompose(this::resolveChannelReference) - .thenCompose(this::resolveCustomerGroupReference) - .thenApply(PriceDraftBuilder::build); - } + /** + * Takes a {@link ProductSyncOptions} instance, {@link TypeService}, a {@link ChannelService} and + * a {@link CustomerGroupService} to instantiate a {@link PriceReferenceResolver} instance that + * could be used to resolve the prices of variant drafts in the CTP project specified in the + * injected {@link ProductSyncOptions} instance. + * + * @param options the container of all the options of the sync process including the CTP project + * client and/or configuration and other sync-specific options. + * @param typeService the service to fetch the custom types for reference resolution. + * @param channelService the service to fetch the channels for reference resolution. + * @param customerGroupService the service to fetch the customer groups for reference resolution. + */ + public PriceReferenceResolver( + @Nonnull final ProductSyncOptions options, + @Nonnull final TypeService typeService, + @Nonnull final ChannelService channelService, + @Nonnull final CustomerGroupService customerGroupService) { + super(options, typeService); + this.channelService = channelService; + this.customerGroupService = customerGroupService; + } - @Override - @Nonnull - protected CompletionStage resolveCustomTypeReference( - @Nonnull final PriceDraftBuilder draftBuilder) { + /** + * Given a {@link PriceDraft} this method attempts to resolve the custom type and channel + * references to return a {@link CompletionStage} which contains a new instance of the draft with + * the resolved references. + * + *

The method then tries to fetch the key of the customer group, optimistically from a cache. + * If the id is not found in cache nor the CTP project the {@link ReferenceResolutionException} + * will be thrown. + * + * @param priceDraft the priceDraft to resolve it's references. + * @return a {@link CompletionStage} that contains as a result a new inventoryEntryDraft instance + * with resolved references or, in case an error occurs during reference resolution a {@link + * ReferenceResolutionException}. + */ + @Override + public CompletionStage resolveReferences(@Nonnull final PriceDraft priceDraft) { + return resolveCustomTypeReference(PriceDraftBuilder.of(priceDraft)) + .thenCompose(this::resolveChannelReference) + .thenCompose(this::resolveCustomerGroupReference) + .thenApply(PriceDraftBuilder::build); + } - return resolveCustomTypeReference(draftBuilder, - PriceDraftBuilder::getCustom, - PriceDraftBuilder::custom, - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getCountry(), draftBuilder.getValue())); - } + @Override + @Nonnull + protected CompletionStage resolveCustomTypeReference( + @Nonnull final PriceDraftBuilder draftBuilder) { - /** - * Given a {@link PriceDraftBuilder} this method attempts to resolve the supply channel reference to return - * a {@link CompletionStage} which contains the same instance of draft builder with the resolved - * supply channel reference. - * - *

The method then tries to fetch the key of the supply channel, optimistically from a - * cache. If the id is not found in cache nor the CTP project and {@code ensureChannel} - * option is set to true, a new channel will be created with this key and the role {@code "InventorySupply"}. - * However, if the {@code ensureChannel} is set to false, the future is completed exceptionally with a - * {@link ReferenceResolutionException}. - * - * @param draftBuilder the PriceDraftBuilder to resolve its channel reference. - * @return a {@link CompletionStage} that contains as a result a new price draft builder instance with resolved - * supply channel or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Nonnull - CompletionStage resolveChannelReference(@Nonnull final PriceDraftBuilder draftBuilder) { - final ResourceIdentifier channelResourceIdentifier = draftBuilder.getChannel(); - if (channelResourceIdentifier != null && channelResourceIdentifier.getId() == null) { - String channelKey; - try { - channelKey = getKeyFromResourceIdentifier(channelResourceIdentifier); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, Channel.resourceTypeId(), draftBuilder.getCountry(), - draftBuilder.getValue(), referenceResolutionException.getMessage()))); - } - - return fetchAndResolveChannelReference(draftBuilder, channelKey); - } - return completedFuture(draftBuilder); - } + return resolveCustomTypeReference( + draftBuilder, + PriceDraftBuilder::getCustom, + PriceDraftBuilder::custom, + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getCountry(), draftBuilder.getValue())); + } - @Nonnull - private CompletionStage fetchAndResolveChannelReference( - @Nonnull final PriceDraftBuilder draftBuilder, - @Nonnull final String channelKey) { - - return channelService - .fetchCachedChannelId(channelKey) - .thenCompose(resolvedChannelIdOptional -> resolvedChannelIdOptional - .map(resolvedChannelId -> - completedFuture(draftBuilder.channel( - Channel.referenceOfId(resolvedChannelId).toResourceIdentifier()))) - .orElseGet(() -> { - final CompletableFuture result = new CompletableFuture<>(); - createChannelAndSetReference(channelKey, draftBuilder) - .whenComplete((draftWithCreatedChannel, exception) -> { - if (exception != null) { - result.completeExceptionally( - new ReferenceResolutionException(format(FAILED_TO_RESOLVE_REFERENCE, - Channel.resourceTypeId(), draftBuilder.getCountry(), draftBuilder.getValue(), - exception.getMessage()), exception)); - } else { - result.complete(draftWithCreatedChannel); - } - }); - return result; - })); - } + /** + * Given a {@link PriceDraftBuilder} this method attempts to resolve the supply channel reference + * to return a {@link CompletionStage} which contains the same instance of draft builder with the + * resolved supply channel reference. + * + *

The method then tries to fetch the key of the supply channel, optimistically from a cache. + * If the id is not found in cache nor the CTP project and {@code ensureChannel} option is set to + * true, a new channel will be created with this key and the role {@code "InventorySupply"}. + * However, if the {@code ensureChannel} is set to false, the future is completed exceptionally + * with a {@link ReferenceResolutionException}. + * + * @param draftBuilder the PriceDraftBuilder to resolve its channel reference. + * @return a {@link CompletionStage} that contains as a result a new price draft builder instance + * with resolved supply channel or, in case an error occurs during reference resolution, a + * {@link ReferenceResolutionException}. + */ + @Nonnull + CompletionStage resolveChannelReference( + @Nonnull final PriceDraftBuilder draftBuilder) { + final ResourceIdentifier channelResourceIdentifier = draftBuilder.getChannel(); + if (channelResourceIdentifier != null && channelResourceIdentifier.getId() == null) { + String channelKey; + try { + channelKey = getKeyFromResourceIdentifier(channelResourceIdentifier); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + Channel.resourceTypeId(), + draftBuilder.getCountry(), + draftBuilder.getValue(), + referenceResolutionException.getMessage()))); + } - /** - * Helper method that creates a new {@link Channel} on the CTP project with the specified {@code channelKey} and of - * the role {@code "InventorySupply"}. Only if the {@code ensureChannels} options is set to {@code true} on the - * {@code options} instance of {@code this} class. Then it resolves the supply channel reference on the supplied - * {@code inventoryEntryDraft} by setting the id of it's supply channel reference with the newly created Channel. - * - *

If the {@code ensureChannels} options is set to {@code false} on the {@code options} instance of {@code this} - * class, the future is completed exceptionally with a {@link ReferenceResolutionException}. - * - *

The method then returns a CompletionStage with a resolved channel reference {@link PriceDraftBuilder} - * object. - * - * @param channelKey the key to create the new channel with. - * @param draftBuilder the inventory entry draft builder where to resolve it's supply channel reference to the newly - * created channel. - * @return a CompletionStage with the same {@code draftBuilder} instance having resolved channel reference. - */ - @Nonnull - private CompletionStage createChannelAndSetReference( - @Nonnull final String channelKey, - @Nonnull final PriceDraftBuilder draftBuilder) { - - if (options.shouldEnsurePriceChannels()) { - return channelService - .createAndCacheChannel(channelKey) - .thenCompose(createdChannelOptional -> { - if (createdChannelOptional.isPresent()) { - return completedFuture(draftBuilder.channel(createdChannelOptional.get())); - } else { - final ReferenceResolutionException referenceResolutionException = - new ReferenceResolutionException(format(CHANNEL_DOES_NOT_EXIST, channelKey)); - return exceptionallyCompletedFuture(referenceResolutionException); - } - }); - } else { - final ReferenceResolutionException referenceResolutionException = - new ReferenceResolutionException(format(CHANNEL_DOES_NOT_EXIST, channelKey)); - return exceptionallyCompletedFuture(referenceResolutionException); - } + return fetchAndResolveChannelReference(draftBuilder, channelKey); } + return completedFuture(draftBuilder); + } + + @Nonnull + private CompletionStage fetchAndResolveChannelReference( + @Nonnull final PriceDraftBuilder draftBuilder, @Nonnull final String channelKey) { - /** - * Given a {@link PriceDraftBuilder} this method attempts to resolve the customer group reference to return - * a {@link CompletionStage} which contains the same instance of draft builder with the resolved - * customer group reference. - * - *

Note: The key of the customer group reference taken from the value of the id field of the reference. - * - * @param draftBuilder the priceDraftBuilder to resolve its customer group reference. - * @return a {@link CompletionStage} that contains as a result a new price draft builder instance with resolved - * customer group reference or no customer group reference if the customer group doesn't exist or in case an - * error occurs during reference resolution a {@link ReferenceResolutionException}. - */ - @Nonnull - CompletionStage resolveCustomerGroupReference(@Nonnull final PriceDraftBuilder draftBuilder) { - final Reference customerGroupReference = draftBuilder.getCustomerGroup(); - if (customerGroupReference != null) { - String customerGroupKey; - try { - customerGroupKey = getIdFromReference(customerGroupReference); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, CustomerGroup.referenceTypeId(), draftBuilder.getCountry(), - draftBuilder.getValue(), referenceResolutionException.getMessage()))); - } - - return fetchAndResolveCustomerGroupReference(draftBuilder, customerGroupKey); - } - return completedFuture(draftBuilder); + return channelService + .fetchCachedChannelId(channelKey) + .thenCompose( + resolvedChannelIdOptional -> + resolvedChannelIdOptional + .map( + resolvedChannelId -> + completedFuture( + draftBuilder.channel( + Channel.referenceOfId(resolvedChannelId) + .toResourceIdentifier()))) + .orElseGet( + () -> { + final CompletableFuture result = + new CompletableFuture<>(); + createChannelAndSetReference(channelKey, draftBuilder) + .whenComplete( + (draftWithCreatedChannel, exception) -> { + if (exception != null) { + result.completeExceptionally( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + Channel.resourceTypeId(), + draftBuilder.getCountry(), + draftBuilder.getValue(), + exception.getMessage()), + exception)); + } else { + result.complete(draftWithCreatedChannel); + } + }); + return result; + })); + } + + /** + * Helper method that creates a new {@link Channel} on the CTP project with the specified {@code + * channelKey} and of the role {@code "InventorySupply"}. Only if the {@code ensureChannels} + * options is set to {@code true} on the {@code options} instance of {@code this} class. Then it + * resolves the supply channel reference on the supplied {@code inventoryEntryDraft} by setting + * the id of it's supply channel reference with the newly created Channel. + * + *

If the {@code ensureChannels} options is set to {@code false} on the {@code options} + * instance of {@code this} class, the future is completed exceptionally with a {@link + * ReferenceResolutionException}. + * + *

The method then returns a CompletionStage with a resolved channel reference {@link + * PriceDraftBuilder} object. + * + * @param channelKey the key to create the new channel with. + * @param draftBuilder the inventory entry draft builder where to resolve it's supply channel + * reference to the newly created channel. + * @return a CompletionStage with the same {@code draftBuilder} instance having resolved channel + * reference. + */ + @Nonnull + private CompletionStage createChannelAndSetReference( + @Nonnull final String channelKey, @Nonnull final PriceDraftBuilder draftBuilder) { + + if (options.shouldEnsurePriceChannels()) { + return channelService + .createAndCacheChannel(channelKey) + .thenCompose( + createdChannelOptional -> { + if (createdChannelOptional.isPresent()) { + return completedFuture(draftBuilder.channel(createdChannelOptional.get())); + } else { + final ReferenceResolutionException referenceResolutionException = + new ReferenceResolutionException(format(CHANNEL_DOES_NOT_EXIST, channelKey)); + return exceptionallyCompletedFuture(referenceResolutionException); + } + }); + } else { + final ReferenceResolutionException referenceResolutionException = + new ReferenceResolutionException(format(CHANNEL_DOES_NOT_EXIST, channelKey)); + return exceptionallyCompletedFuture(referenceResolutionException); } + } + + /** + * Given a {@link PriceDraftBuilder} this method attempts to resolve the customer group reference + * to return a {@link CompletionStage} which contains the same instance of draft builder with the + * resolved customer group reference. + * + *

Note: The key of the customer group reference taken from the value of the id field of the + * reference. + * + * @param draftBuilder the priceDraftBuilder to resolve its customer group reference. + * @return a {@link CompletionStage} that contains as a result a new price draft builder instance + * with resolved customer group reference or no customer group reference if the customer group + * doesn't exist or in case an error occurs during reference resolution a {@link + * ReferenceResolutionException}. + */ + @Nonnull + CompletionStage resolveCustomerGroupReference( + @Nonnull final PriceDraftBuilder draftBuilder) { + final Reference customerGroupReference = draftBuilder.getCustomerGroup(); + if (customerGroupReference != null) { + String customerGroupKey; + try { + customerGroupKey = getIdFromReference(customerGroupReference); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + CustomerGroup.referenceTypeId(), + draftBuilder.getCountry(), + draftBuilder.getValue(), + referenceResolutionException.getMessage()))); + } - @Nonnull - private CompletionStage fetchAndResolveCustomerGroupReference( - @Nonnull final PriceDraftBuilder draftBuilder, - @Nonnull final String customerGroupKey) { - - return customerGroupService - .fetchCachedCustomerGroupId(customerGroupKey) - .thenCompose(resolvedCustomerGroupIdOptional -> resolvedCustomerGroupIdOptional - .map(resolvedCustomerGroupId -> - completedFuture(draftBuilder.customerGroup(CustomerGroup.referenceOfId(resolvedCustomerGroupId)))) - .orElseGet(() -> { - final String errorMessage = format(CUSTOMER_GROUP_DOES_NOT_EXIST, customerGroupKey); - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, CustomerGroup.referenceTypeId(), draftBuilder.getCountry(), - draftBuilder.getValue(), errorMessage))); - })); + return fetchAndResolveCustomerGroupReference(draftBuilder, customerGroupKey); } + return completedFuture(draftBuilder); + } + + @Nonnull + private CompletionStage fetchAndResolveCustomerGroupReference( + @Nonnull final PriceDraftBuilder draftBuilder, @Nonnull final String customerGroupKey) { + + return customerGroupService + .fetchCachedCustomerGroupId(customerGroupKey) + .thenCompose( + resolvedCustomerGroupIdOptional -> + resolvedCustomerGroupIdOptional + .map( + resolvedCustomerGroupId -> + completedFuture( + draftBuilder.customerGroup( + CustomerGroup.referenceOfId(resolvedCustomerGroupId)))) + .orElseGet( + () -> { + final String errorMessage = + format(CUSTOMER_GROUP_DOES_NOT_EXIST, customerGroupKey); + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + CustomerGroup.referenceTypeId(), + draftBuilder.getCountry(), + draftBuilder.getValue(), + errorMessage))); + })); + } } diff --git a/src/main/java/com/commercetools/sync/products/helpers/ProductAssetActionFactory.java b/src/main/java/com/commercetools/sync/products/helpers/ProductAssetActionFactory.java index 6e0615a428..034e03c9a2 100644 --- a/src/main/java/com/commercetools/sync/products/helpers/ProductAssetActionFactory.java +++ b/src/main/java/com/commercetools/sync/products/helpers/ProductAssetActionFactory.java @@ -1,5 +1,7 @@ package com.commercetools.sync.products.helpers; +import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildActions; + import com.commercetools.sync.commons.helpers.AssetActionFactory; import com.commercetools.sync.products.ProductSyncOptions; import io.sphere.sdk.commands.UpdateAction; @@ -10,45 +12,47 @@ import io.sphere.sdk.products.commands.updateactions.AddAsset; import io.sphere.sdk.products.commands.updateactions.ChangeAssetOrder; import io.sphere.sdk.products.commands.updateactions.RemoveAsset; - -import javax.annotation.Nonnull; import java.util.List; +import javax.annotation.Nonnull; -import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildActions; - -public final class ProductAssetActionFactory extends AssetActionFactory { - private Integer variantId; - - public ProductAssetActionFactory(@Nonnull final Integer variantId, - @Nonnull final ProductSyncOptions syncOptions) { - this.variantId = variantId; - this.syncOptions = syncOptions; - } - - @Override - public List> buildAssetActions(@Nonnull final Product oldResource, - @Nonnull final ProductDraft newResource, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAssetDraft) { - return buildActions(oldResource, newResource, variantId, oldAsset, newAssetDraft, - (ProductSyncOptions) syncOptions); - } - - @Override - public UpdateAction buildRemoveAssetAction(@Nonnull final String assetKey) { - return RemoveAsset.ofVariantIdWithKey(variantId, assetKey, true); - } - - @Override - public UpdateAction buildChangeAssetOrderAction(@Nonnull final List newAssetOrder) { - return ChangeAssetOrder.ofVariantId(variantId, newAssetOrder, true); - } - - @Override - public UpdateAction buildAddAssetAction(@Nonnull final AssetDraft assetDraft, - @Nonnull final Integer position) { - return AddAsset.ofVariantId(variantId, assetDraft) - .withPosition(position) - .withStaged(true); - } +public final class ProductAssetActionFactory extends AssetActionFactory { + private Integer variantId; + + public ProductAssetActionFactory( + @Nonnull final Integer variantId, @Nonnull final ProductSyncOptions syncOptions) { + this.variantId = variantId; + this.syncOptions = syncOptions; + } + + @Override + public List> buildAssetActions( + @Nonnull final Product oldResource, + @Nonnull final ProductDraft newResource, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAssetDraft) { + return buildActions( + oldResource, + newResource, + variantId, + oldAsset, + newAssetDraft, + (ProductSyncOptions) syncOptions); + } + + @Override + public UpdateAction buildRemoveAssetAction(@Nonnull final String assetKey) { + return RemoveAsset.ofVariantIdWithKey(variantId, assetKey, true); + } + + @Override + public UpdateAction buildChangeAssetOrderAction( + @Nonnull final List newAssetOrder) { + return ChangeAssetOrder.ofVariantId(variantId, newAssetOrder, true); + } + + @Override + public UpdateAction buildAddAssetAction( + @Nonnull final AssetDraft assetDraft, @Nonnull final Integer position) { + return AddAsset.ofVariantId(variantId, assetDraft).withPosition(position).withStaged(true); + } } diff --git a/src/main/java/com/commercetools/sync/products/helpers/ProductBatchValidator.java b/src/main/java/com/commercetools/sync/products/helpers/ProductBatchValidator.java index ae6b0df981..c28c0addca 100644 --- a/src/main/java/com/commercetools/sync/products/helpers/ProductBatchValidator.java +++ b/src/main/java/com/commercetools/sync/products/helpers/ProductBatchValidator.java @@ -1,5 +1,14 @@ package com.commercetools.sync.products.helpers; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.isReferenceOfType; +import static java.lang.String.format; +import static java.util.Collections.emptySet; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toSet; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.helpers.BaseBatchValidator; import com.commercetools.sync.commons.utils.SyncUtils; import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; @@ -12,10 +21,6 @@ import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.products.attributes.AttributeDraft; import io.sphere.sdk.producttypes.ProductType; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -24,332 +29,334 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.isReferenceOfType; -import static java.lang.String.format; -import static java.util.Collections.emptySet; -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toSet; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class ProductBatchValidator extends BaseBatchValidator { - static final String PRODUCT_DRAFT_KEY_NOT_SET = "ProductDraft with name: %s doesn't have a key. " - + "Please make sure all product drafts have keys."; - static final String PRODUCT_DRAFT_IS_NULL = "ProductDraft is null."; - static final String PRODUCT_VARIANT_DRAFT_IS_NULL = "ProductVariantDraft at position '%d' of ProductDraft " - + "with key '%s' is null."; - static final String PRODUCT_VARIANT_DRAFT_SKU_NOT_SET = "ProductVariantDraft at position '%d' of " - + "ProductDraft with key '%s' has no SKU set. Please make sure all variants have SKUs."; - static final String PRODUCT_VARIANT_DRAFT_KEY_NOT_SET = "ProductVariantDraft at position '%d' of " - + "ProductDraft with key '%s' has no key set. Please make sure all variants have keys."; - - public ProductBatchValidator(@Nonnull final ProductSyncOptions syncOptions, - @Nonnull final ProductSyncStatistics syncStatistics) { - super(syncOptions, syncStatistics); - } - - /** - * Given the {@link List}<{@link ProductDraft}> of drafts this method attempts to validate - * drafts and collect referenced keys from the draft and return an {@link ImmutablePair}<{@link Set}< - * {@link ProductDraft}>,{@link ProductBatchValidator.ReferencedKeys}> - * which contains the {@link Set} of valid drafts and referenced keys within a wrapper. - * - *

A valid product draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a key which is not blank (null/empty)
  4. - *
  5. It has all variants AND master variant valid
  6. - *
  7. A variant is valid if it satisfies the following conditions: - *
      - *
    1. It has a key which is not blank (null/empty)
    2. - *
    3. It has a SKU which is not blank (null/empty)
    4. - *
    - *
  8. - *
- * - * @param productDrafts the product drafts to validate and collect referenced keys. - * @return {@link ImmutablePair}<{@link Set}<{@link ProductDraft}>, - * {@link ProductBatchValidator.ReferencedKeys}> which contains the {@link Set} of valid drafts - * and referenced keys within a wrapper. - */ - @Override - public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( - @Nonnull final List productDrafts) { - - final ReferencedKeys referencedKeys = new ReferencedKeys(); - final Set validDrafts = productDrafts - .stream() + static final String PRODUCT_DRAFT_KEY_NOT_SET = + "ProductDraft with name: %s doesn't have a key. " + + "Please make sure all product drafts have keys."; + static final String PRODUCT_DRAFT_IS_NULL = "ProductDraft is null."; + static final String PRODUCT_VARIANT_DRAFT_IS_NULL = + "ProductVariantDraft at position '%d' of ProductDraft " + "with key '%s' is null."; + static final String PRODUCT_VARIANT_DRAFT_SKU_NOT_SET = + "ProductVariantDraft at position '%d' of " + + "ProductDraft with key '%s' has no SKU set. Please make sure all variants have SKUs."; + static final String PRODUCT_VARIANT_DRAFT_KEY_NOT_SET = + "ProductVariantDraft at position '%d' of " + + "ProductDraft with key '%s' has no key set. Please make sure all variants have keys."; + + public ProductBatchValidator( + @Nonnull final ProductSyncOptions syncOptions, + @Nonnull final ProductSyncStatistics syncStatistics) { + super(syncOptions, syncStatistics); + } + + /** + * Given the {@link List}<{@link ProductDraft}> of drafts this method attempts to validate + * drafts and collect referenced keys from the draft and return an {@link ImmutablePair}<{@link + * Set}< {@link ProductDraft}>,{@link ProductBatchValidator.ReferencedKeys}> which + * contains the {@link Set} of valid drafts and referenced keys within a wrapper. + * + *

A valid product draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a key which is not blank (null/empty) + *
  3. It has all variants AND master variant valid + *
  4. A variant is valid if it satisfies the following conditions: + *
      + *
    1. It has a key which is not blank (null/empty) + *
    2. It has a SKU which is not blank (null/empty) + *
    + *
+ * + * @param productDrafts the product drafts to validate and collect referenced keys. + * @return {@link ImmutablePair}<{@link Set}<{@link ProductDraft}>, {@link + * ProductBatchValidator.ReferencedKeys}> which contains the {@link Set} of valid drafts + * and referenced keys within a wrapper. + */ + @Override + public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( + @Nonnull final List productDrafts) { + + final ReferencedKeys referencedKeys = new ReferencedKeys(); + final Set validDrafts = + productDrafts.stream() .filter(this::isValidProductDraft) .peek(productDraft -> collectReferencedKeys(referencedKeys, productDraft)) .collect(Collectors.toSet()); - return ImmutablePair.of(validDrafts, referencedKeys); + return ImmutablePair.of(validDrafts, referencedKeys); + } + + private boolean isValidProductDraft(@Nullable final ProductDraft productDraft) { + if (productDraft == null) { + handleError(PRODUCT_DRAFT_IS_NULL); + } else if (isBlank(productDraft.getKey())) { + handleError(format(PRODUCT_DRAFT_KEY_NOT_SET, productDraft.getName())); + } else { + final List draftErrors = getVariantDraftErrorsInAllVariants(productDraft); + if (!draftErrors.isEmpty()) { + draftErrors.forEach(this::handleError); + } else { + return true; + } } - - private boolean isValidProductDraft(@Nullable final ProductDraft productDraft) { - if (productDraft == null) { - handleError(PRODUCT_DRAFT_IS_NULL); - } else if (isBlank(productDraft.getKey())) { - handleError(format(PRODUCT_DRAFT_KEY_NOT_SET, productDraft.getName())); - } else { - final List draftErrors = getVariantDraftErrorsInAllVariants(productDraft); - if (!draftErrors.isEmpty()) { - draftErrors.forEach(this::handleError); - } else { - return true; - } - } - return false; + return false; + } + + private void collectReferencedKeys( + @Nonnull final ReferencedKeys referencedKeys, @Nonnull final ProductDraft productDraft) { + + collectReferencedKeyFromResourceIdentifier( + productDraft.getProductType(), referencedKeys.productTypeKeys::add); + collectReferencedKeysInCategories(referencedKeys, productDraft); + collectReferencedKeysInVariants(referencedKeys, productDraft); + collectReferencedKeyFromResourceIdentifier( + productDraft.getTaxCategory(), referencedKeys.taxCategoryKeys::add); + collectReferencedKeyFromResourceIdentifier( + productDraft.getState(), referencedKeys.stateKeys::add); + } + + private void collectReferencedKeysInCategories( + @Nonnull final ReferencedKeys referencedKeys, @Nonnull final ProductDraft productDraft) { + + productDraft.getCategories().stream() + .filter(Objects::nonNull) + .forEach( + resourceIdentifier -> + collectReferencedKeyFromResourceIdentifier( + resourceIdentifier, referencedKeys.categoryKeys::add)); + } + + private void collectReferencedKeysInVariants( + @Nonnull final ReferencedKeys referencedKeys, @Nonnull final ProductDraft productDraft) { + + getAllVariants(productDraft) + .forEach( + variantDraft -> { + collectReferencedKeysInPrices(referencedKeys, variantDraft); + collectReferencedKeysInAttributes(referencedKeys, variantDraft); + collectReferencedKeysFromAssetDrafts( + variantDraft.getAssets(), referencedKeys.typeKeys::add); + }); + } + + @Nonnull + private List getAllVariants(@Nonnull final ProductDraft productDraft) { + final List allVariants = new ArrayList<>(); + allVariants.add(productDraft.getMasterVariant()); + if (productDraft.getVariants() != null) { + allVariants.addAll( + productDraft.getVariants().stream() + .filter(Objects::nonNull) + .collect(Collectors.toList())); } + return allVariants; + } - private void collectReferencedKeys( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final ProductDraft productDraft) { + private void collectReferencedKeysInPrices( + @Nonnull final ReferencedKeys referencedKeys, + @Nonnull final ProductVariantDraft variantDraft) { - collectReferencedKeyFromResourceIdentifier(productDraft.getProductType(), referencedKeys.productTypeKeys::add); - collectReferencedKeysInCategories(referencedKeys, productDraft); - collectReferencedKeysInVariants(referencedKeys, productDraft); - collectReferencedKeyFromResourceIdentifier(productDraft.getTaxCategory(), - referencedKeys.taxCategoryKeys::add); - collectReferencedKeyFromResourceIdentifier(productDraft.getState(), referencedKeys.stateKeys::add); + if (variantDraft.getPrices() == null) { + return; } - private void collectReferencedKeysInCategories( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final ProductDraft productDraft) { + variantDraft.getPrices().stream() + .filter(Objects::nonNull) + .forEach( + priceDraft -> { + collectReferencedKeyFromReference( + priceDraft.getCustomerGroup(), referencedKeys.customerGroupKeys::add); + collectReferencedKeyFromResourceIdentifier( + priceDraft.getChannel(), referencedKeys.channelKeys::add); + collectReferencedKeyFromCustomFieldsDraft( + priceDraft.getCustom(), referencedKeys.typeKeys::add); + }); + } - productDraft - .getCategories() - .stream() - .filter(Objects::nonNull) - .forEach(resourceIdentifier -> - collectReferencedKeyFromResourceIdentifier(resourceIdentifier, referencedKeys.categoryKeys::add)); - } + private void collectReferencedKeysInAttributes( + @Nonnull final ReferencedKeys referencedKeys, + @Nonnull final ProductVariantDraft variantDraft) { + referencedKeys.productKeys.addAll(getReferencedProductKeys(variantDraft)); - private void collectReferencedKeysInVariants( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final ProductDraft productDraft) { + referencedKeys.categoryKeys.addAll( + getReferencedKeysWithReferenceTypeId(variantDraft, Category.referenceTypeId())); - getAllVariants(productDraft).forEach(variantDraft -> { - collectReferencedKeysInPrices(referencedKeys, variantDraft); - collectReferencedKeysInAttributes(referencedKeys, variantDraft); - collectReferencedKeysFromAssetDrafts(variantDraft.getAssets(), referencedKeys.typeKeys::add); - }); - } + referencedKeys.productTypeKeys.addAll( + getReferencedKeysWithReferenceTypeId(variantDraft, ProductType.referenceTypeId())); - @Nonnull - private List getAllVariants(@Nonnull final ProductDraft productDraft) { - final List allVariants = new ArrayList<>(); - allVariants.add(productDraft.getMasterVariant()); - if (productDraft.getVariants() != null) { - allVariants.addAll( - productDraft.getVariants() - .stream() - .filter(Objects::nonNull) - .collect(Collectors.toList())); - } - return allVariants; - } + referencedKeys.customObjectCompositeIdentifiers.addAll( + getReferencedKeysWithReferenceTypeId(variantDraft, CustomObject.referenceTypeId())); + } - private void collectReferencedKeysInPrices( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final ProductVariantDraft variantDraft) { + @Nonnull + private List getVariantDraftErrorsInAllVariants( + @Nonnull final ProductDraft productDraft) { + final List errorMessages = new ArrayList<>(); - if (variantDraft.getPrices() == null) { - return; - } + // don't filter the nulls + final List allVariants = new ArrayList<>(); + allVariants.add(productDraft.getMasterVariant()); + allVariants.addAll(productDraft.getVariants()); - variantDraft - .getPrices() - .stream() - .filter(Objects::nonNull) - .forEach(priceDraft -> { - collectReferencedKeyFromReference(priceDraft.getCustomerGroup(), - referencedKeys.customerGroupKeys::add); - collectReferencedKeyFromResourceIdentifier(priceDraft.getChannel(), - referencedKeys.channelKeys::add); - collectReferencedKeyFromCustomFieldsDraft(priceDraft.getCustom(), - referencedKeys.typeKeys::add); - }); + for (int i = 0; i < allVariants.size(); i++) { + errorMessages.addAll( + getVariantDraftErrorsInAllVariants( + allVariants.get(i), i, requireNonNull(productDraft.getKey()))); } - private void collectReferencedKeysInAttributes( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final ProductVariantDraft variantDraft) { - referencedKeys.productKeys.addAll(getReferencedProductKeys(variantDraft)); + return errorMessages; + } + + @Nonnull + private List getVariantDraftErrorsInAllVariants( + @Nullable final ProductVariantDraft productVariantDraft, + final int variantPosition, + @Nonnull final String productDraftKey) { + final List errorMessages = new ArrayList<>(); + if (productVariantDraft != null) { + if (isBlank(productVariantDraft.getKey())) { + errorMessages.add( + format(PRODUCT_VARIANT_DRAFT_KEY_NOT_SET, variantPosition, productDraftKey)); + } + if (isBlank(productVariantDraft.getSku())) { + errorMessages.add( + format(PRODUCT_VARIANT_DRAFT_SKU_NOT_SET, variantPosition, productDraftKey)); + } + } else { + errorMessages.add(format(PRODUCT_VARIANT_DRAFT_IS_NULL, variantPosition, productDraftKey)); + } + return errorMessages; + } + + /** + * Get a set of referenced product keys on all attribute drafts on the supplied Product Variant + * Draft. + * + *

Note: Null attributes are skipped since they are validated at a later stage in ({@link + * com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils}) + * + * @param variantDraft the variant draft to get the referenced product keys from. + * @return the set of referenced product keys. + */ + @Nonnull + public static Set getReferencedProductKeys( + @Nonnull final ProductVariantDraft variantDraft) { + return getReferencedKeysWithReferenceTypeId(variantDraft, Product.referenceTypeId()); + } + + private static Set getReferencedKeysWithReferenceTypeId( + @Nonnull final ProductVariantDraft variantDraft, @Nonnull final String referenceTypeId) { + + final List attributeDrafts = variantDraft.getAttributes(); + if (attributeDrafts == null) { + return emptySet(); + } + return attributeDrafts.stream() + .filter(Objects::nonNull) + .map( + attributeDraft -> getReferencedKeysWithReferenceTypeId(attributeDraft, referenceTypeId)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } + + @Nonnull + private static Set getReferencedKeysWithReferenceTypeId( + @Nonnull final AttributeDraft attributeDraft, @Nonnull final String referenceTypeId) { + + final JsonNode attributeDraftValue = attributeDraft.getValue(); + if (attributeDraftValue == null) { + return emptySet(); + } - referencedKeys.categoryKeys.addAll( - getReferencedKeysWithReferenceTypeId(variantDraft, Category.referenceTypeId())); + final List allAttributeReferences = + attributeDraftValue.findParents(REFERENCE_TYPE_ID_FIELD); + + return allAttributeReferences.stream() + .filter(reference -> isReferenceOfType(reference, referenceTypeId)) + .map(reference -> reference.get(REFERENCE_ID_FIELD).asText()) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + public static class ReferencedKeys { + private final Set productKeys = new HashSet<>(); + private final Set productTypeKeys = new HashSet<>(); + private final Set categoryKeys = new HashSet<>(); + private final Set taxCategoryKeys = new HashSet<>(); + private final Set stateKeys = new HashSet<>(); + private final Set typeKeys = new HashSet<>(); + private final Set channelKeys = new HashSet<>(); + private final Set customerGroupKeys = new HashSet<>(); + private final Set customObjectCompositeIdentifiers = new HashSet<>(); + + public Set getProductKeys() { + return productKeys; + } - referencedKeys.productTypeKeys.addAll( - getReferencedKeysWithReferenceTypeId(variantDraft, ProductType.referenceTypeId())); + public Set getProductTypeKeys() { + return productTypeKeys; + } - referencedKeys.customObjectCompositeIdentifiers.addAll( - getReferencedKeysWithReferenceTypeId(variantDraft, CustomObject.referenceTypeId())); + public Set getCategoryKeys() { + return categoryKeys; } - @Nonnull - private List getVariantDraftErrorsInAllVariants(@Nonnull final ProductDraft productDraft) { - final List errorMessages = new ArrayList<>(); + public Set getTaxCategoryKeys() { + return taxCategoryKeys; + } - // don't filter the nulls - final List allVariants = new ArrayList<>(); - allVariants.add(productDraft.getMasterVariant()); - allVariants.addAll(productDraft.getVariants()); + public Set getStateKeys() { + return stateKeys; + } - for (int i = 0; i < allVariants.size(); i++) { - errorMessages.addAll(getVariantDraftErrorsInAllVariants(allVariants.get(i), - i, requireNonNull(productDraft.getKey()))); - } + public Set getTypeKeys() { + return typeKeys; + } - return errorMessages; + public Set getChannelKeys() { + return channelKeys; } - @Nonnull - private List getVariantDraftErrorsInAllVariants(@Nullable final ProductVariantDraft productVariantDraft, - final int variantPosition, - @Nonnull final String productDraftKey) { - final List errorMessages = new ArrayList<>(); - if (productVariantDraft != null) { - if (isBlank(productVariantDraft.getKey())) { - errorMessages.add(format(PRODUCT_VARIANT_DRAFT_KEY_NOT_SET, variantPosition, productDraftKey)); - } - if (isBlank(productVariantDraft.getSku())) { - errorMessages.add(format(PRODUCT_VARIANT_DRAFT_SKU_NOT_SET, variantPosition, productDraftKey)); - } - } else { - errorMessages.add(format(PRODUCT_VARIANT_DRAFT_IS_NULL, variantPosition, productDraftKey)); - } - return errorMessages; + public Set getCustomerGroupKeys() { + return customerGroupKeys; } /** - * Get a set of referenced product keys on all attribute drafts on the supplied Product - * Variant Draft. + * Applies mapping of {@link Set}<{@link String}> identifiers (collected from reference id + * fields of product `key-value-document` references) to {@link Set}< {@link + * CustomObjectCompositeIdentifier}> to be used for caching purposes. * - *

Note: Null attributes are skipped since they are validated at a later stage in - * ({@link com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils}) + *

Note: Invalid identifiers and uuid formatted identifiers will be filtered out. Validation + * handling will be part of the {@link VariantReferenceResolver}. * - * @param variantDraft the variant draft to get the referenced product keys from. - * @return the set of referenced product keys. + * @return a result set with valid identifiers mapped to {@link + * CustomObjectCompositeIdentifier}. */ - @Nonnull - public static Set getReferencedProductKeys(@Nonnull final ProductVariantDraft variantDraft) { - return getReferencedKeysWithReferenceTypeId(variantDraft, Product.referenceTypeId()); - } - - private static Set getReferencedKeysWithReferenceTypeId( - @Nonnull final ProductVariantDraft variantDraft, - @Nonnull final String referenceTypeId) { - - final List attributeDrafts = variantDraft.getAttributes(); - if (attributeDrafts == null) { - return emptySet(); - } - return attributeDrafts - .stream() + public Set getCustomObjectCompositeIdentifiers() { + if (!customObjectCompositeIdentifiers.isEmpty()) { + return customObjectCompositeIdentifiers.stream() + .map( + (identifierAsString) -> { + if (SyncUtils.isUuid(identifierAsString)) { + return null; + } + try { + return CustomObjectCompositeIdentifier.of(identifierAsString); + } catch (IllegalArgumentException ignored) { + return null; + } + }) .filter(Objects::nonNull) - .map(attributeDraft -> - getReferencedKeysWithReferenceTypeId(attributeDraft, referenceTypeId)) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); - } - - @Nonnull - private static Set getReferencedKeysWithReferenceTypeId( - @Nonnull final AttributeDraft attributeDraft, - @Nonnull final String referenceTypeId) { - - final JsonNode attributeDraftValue = attributeDraft.getValue(); - if (attributeDraftValue == null) { - return emptySet(); - } - - final List allAttributeReferences = attributeDraftValue.findParents(REFERENCE_TYPE_ID_FIELD); - - return allAttributeReferences - .stream() - .filter(reference -> isReferenceOfType(reference, referenceTypeId)) - .map(reference -> reference.get(REFERENCE_ID_FIELD).asText()) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } + .collect(toSet()); + } - public static class ReferencedKeys { - private final Set productKeys = new HashSet<>(); - private final Set productTypeKeys = new HashSet<>(); - private final Set categoryKeys = new HashSet<>(); - private final Set taxCategoryKeys = new HashSet<>(); - private final Set stateKeys = new HashSet<>(); - private final Set typeKeys = new HashSet<>(); - private final Set channelKeys = new HashSet<>(); - private final Set customerGroupKeys = new HashSet<>(); - private final Set customObjectCompositeIdentifiers = new HashSet<>(); - - public Set getProductKeys() { - return productKeys; - } - - public Set getProductTypeKeys() { - return productTypeKeys; - } - - public Set getCategoryKeys() { - return categoryKeys; - } - - public Set getTaxCategoryKeys() { - return taxCategoryKeys; - } - - public Set getStateKeys() { - return stateKeys; - } - - public Set getTypeKeys() { - return typeKeys; - } - - public Set getChannelKeys() { - return channelKeys; - } - - public Set getCustomerGroupKeys() { - return customerGroupKeys; - } - - /** - * Applies mapping of {@link Set}<{@link String}> identifiers - * (collected from reference id fields of product `key-value-document` references) to {@link Set}< - * {@link CustomObjectCompositeIdentifier}> to be used for caching purposes. - * - *

Note: Invalid identifiers and uuid formatted identifiers will be filtered out. - * Validation handling will be part of the {@link VariantReferenceResolver}. - * - * @return a result set with valid identifiers mapped to {@link CustomObjectCompositeIdentifier}. - */ - public Set getCustomObjectCompositeIdentifiers() { - if (!customObjectCompositeIdentifiers.isEmpty()) { - return customObjectCompositeIdentifiers - .stream() - .map((identifierAsString) -> { - if (SyncUtils.isUuid(identifierAsString)) { - return null; - } - try { - return CustomObjectCompositeIdentifier.of(identifierAsString); - } catch (IllegalArgumentException ignored) { - return null; - } - }) - .filter(Objects::nonNull) - .collect(toSet()); - } - - return Collections.emptySet(); - } + return Collections.emptySet(); } + } } diff --git a/src/main/java/com/commercetools/sync/products/helpers/ProductReferenceResolver.java b/src/main/java/com/commercetools/sync/products/helpers/ProductReferenceResolver.java index 6305a5e8d0..b3fa360541 100644 --- a/src/main/java/com/commercetools/sync/products/helpers/ProductReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/products/helpers/ProductReferenceResolver.java @@ -1,5 +1,16 @@ package com.commercetools.sync.products.helpers; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.BaseReferenceResolver; import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; @@ -22,8 +33,6 @@ import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.states.State; import io.sphere.sdk.taxcategories.TaxCategory; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -33,404 +42,473 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.apache.commons.lang3.StringUtils.isBlank; - -public final class ProductReferenceResolver extends BaseReferenceResolver { - private final ProductTypeService productTypeService; - private final CategoryService categoryService; - private final VariantReferenceResolver variantReferenceResolver; - private final TaxCategoryService taxCategoryService; - private final StateService stateService; - private final TypeService typeService; - private final ChannelService channelService; - private final ProductService productService; - private final CustomerGroupService customerGroupService; - private final CustomObjectService customObjectService; - - public static final String FAILED_TO_RESOLVE_REFERENCE = "Failed to resolve '%s' resource identifier on " - + "ProductDraft with key:'%s'. Reason: %s"; - public static final String PRODUCT_TYPE_DOES_NOT_EXIST = "Product type with key '%s' doesn't exist."; - public static final String TAX_CATEGORY_DOES_NOT_EXIST = "TaxCategory with key '%s' doesn't exist."; - public static final String CATEGORIES_DO_NOT_EXIST = "Categories with keys '%s' don't exist."; - public static final String STATE_DOES_NOT_EXIST = "State with key '%s' doesn't exist."; - - /** - * Takes a {@link ProductSyncOptions} instance, a {@link ProductTypeService}, a {@link CategoryService}, a - * {@link TypeService}, a {@link ChannelService}, a {@link CustomerGroupService}, a {@link TaxCategoryService}, - * a {@link StateService} and a {@link ProductService} to instantiate a {@link ProductReferenceResolver} instance - * that could be used to resolve the product type, categories, variants, tax category and product state references - * of product drafts in the CTP project specified in the injected {@link ProductSyncOptions} instance. - * - * @param productSyncOptions the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - * @param productTypeService the service to fetch the product type for reference resolution. - * @param categoryService the service to fetch the categories for reference resolution. - * @param typeService the service to fetch the custom types for reference resolution. - * @param channelService the service to fetch the channels for reference resolution. - * @param customerGroupService the service to fetch the customer groups for reference resolution. - * @param taxCategoryService the service to fetch tax categories for reference resolution. - * @param stateService the service to fetch product states for reference resolution. - * @param productService the service to fetch products for product reference resolution on reference - * attributes. - * @param customObjectService the service to fetch custom objects for reference resolution. - */ - public ProductReferenceResolver(@Nonnull final ProductSyncOptions productSyncOptions, - @Nonnull final ProductTypeService productTypeService, - @Nonnull final CategoryService categoryService, - @Nonnull final TypeService typeService, - @Nonnull final ChannelService channelService, - @Nonnull final CustomerGroupService customerGroupService, - @Nonnull final TaxCategoryService taxCategoryService, - @Nonnull final StateService stateService, - @Nonnull final ProductService productService, - @Nonnull final CustomObjectService customObjectService) { - super(productSyncOptions); - this.productTypeService = productTypeService; - this.categoryService = categoryService; - this.taxCategoryService = taxCategoryService; - this.stateService = stateService; - this.typeService = typeService; - this.channelService = channelService; - this.productService = productService; - this.customerGroupService = customerGroupService; - this.customObjectService = customObjectService; - this.variantReferenceResolver = - new VariantReferenceResolver(productSyncOptions, typeService, channelService, customerGroupService, - productService, productTypeService, categoryService, customObjectService); - } - - /** - * Given a {@link ProductDraft} this method attempts to resolve the product type, categories, variants, tax - * category and product state references to return a {@link CompletionStage} which contains a new instance of the - * draft with the resolved references. - * - * @param productDraft the productDraft to resolve it's references. - * @return a {@link CompletionStage} that contains as a result a new productDraft instance with resolved references - * or, in case an error occurs during reference resolution, a {@link ReferenceResolutionException}. - */ - @Override - public CompletionStage resolveReferences(@Nonnull final ProductDraft productDraft) { - return resolveProductTypeReference(ProductDraftBuilder.of(productDraft)) - .thenCompose(this::resolveCategoryReferences) - .thenCompose(this::resolveAllVariantsReferences) - .thenCompose(this::resolveTaxCategoryReference) - .thenCompose(this::resolveStateReference) - .thenApply(ProductDraftBuilder::build); +public final class ProductReferenceResolver + extends BaseReferenceResolver { + private final ProductTypeService productTypeService; + private final CategoryService categoryService; + private final VariantReferenceResolver variantReferenceResolver; + private final TaxCategoryService taxCategoryService; + private final StateService stateService; + private final TypeService typeService; + private final ChannelService channelService; + private final ProductService productService; + private final CustomerGroupService customerGroupService; + private final CustomObjectService customObjectService; + + public static final String FAILED_TO_RESOLVE_REFERENCE = + "Failed to resolve '%s' resource identifier on " + "ProductDraft with key:'%s'. Reason: %s"; + public static final String PRODUCT_TYPE_DOES_NOT_EXIST = + "Product type with key '%s' doesn't exist."; + public static final String TAX_CATEGORY_DOES_NOT_EXIST = + "TaxCategory with key '%s' doesn't exist."; + public static final String CATEGORIES_DO_NOT_EXIST = "Categories with keys '%s' don't exist."; + public static final String STATE_DOES_NOT_EXIST = "State with key '%s' doesn't exist."; + + /** + * Takes a {@link ProductSyncOptions} instance, a {@link ProductTypeService}, a {@link + * CategoryService}, a {@link TypeService}, a {@link ChannelService}, a {@link + * CustomerGroupService}, a {@link TaxCategoryService}, a {@link StateService} and a {@link + * ProductService} to instantiate a {@link ProductReferenceResolver} instance that could be used + * to resolve the product type, categories, variants, tax category and product state references of + * product drafts in the CTP project specified in the injected {@link ProductSyncOptions} + * instance. + * + * @param productSyncOptions the container of all the options of the sync process including the + * CTP project client and/or configuration and other sync-specific options. + * @param productTypeService the service to fetch the product type for reference resolution. + * @param categoryService the service to fetch the categories for reference resolution. + * @param typeService the service to fetch the custom types for reference resolution. + * @param channelService the service to fetch the channels for reference resolution. + * @param customerGroupService the service to fetch the customer groups for reference resolution. + * @param taxCategoryService the service to fetch tax categories for reference resolution. + * @param stateService the service to fetch product states for reference resolution. + * @param productService the service to fetch products for product reference resolution on + * reference attributes. + * @param customObjectService the service to fetch custom objects for reference resolution. + */ + public ProductReferenceResolver( + @Nonnull final ProductSyncOptions productSyncOptions, + @Nonnull final ProductTypeService productTypeService, + @Nonnull final CategoryService categoryService, + @Nonnull final TypeService typeService, + @Nonnull final ChannelService channelService, + @Nonnull final CustomerGroupService customerGroupService, + @Nonnull final TaxCategoryService taxCategoryService, + @Nonnull final StateService stateService, + @Nonnull final ProductService productService, + @Nonnull final CustomObjectService customObjectService) { + super(productSyncOptions); + this.productTypeService = productTypeService; + this.categoryService = categoryService; + this.taxCategoryService = taxCategoryService; + this.stateService = stateService; + this.typeService = typeService; + this.channelService = channelService; + this.productService = productService; + this.customerGroupService = customerGroupService; + this.customObjectService = customObjectService; + this.variantReferenceResolver = + new VariantReferenceResolver( + productSyncOptions, + typeService, + channelService, + customerGroupService, + productService, + productTypeService, + categoryService, + customObjectService); + } + + /** + * Given a {@link ProductDraft} this method attempts to resolve the product type, categories, + * variants, tax category and product state references to return a {@link CompletionStage} which + * contains a new instance of the draft with the resolved references. + * + * @param productDraft the productDraft to resolve it's references. + * @return a {@link CompletionStage} that contains as a result a new productDraft instance with + * resolved references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Override + public CompletionStage resolveReferences(@Nonnull final ProductDraft productDraft) { + return resolveProductTypeReference(ProductDraftBuilder.of(productDraft)) + .thenCompose(this::resolveCategoryReferences) + .thenCompose(this::resolveAllVariantsReferences) + .thenCompose(this::resolveTaxCategoryReference) + .thenCompose(this::resolveStateReference) + .thenApply(ProductDraftBuilder::build); + } + + @Nonnull + private CompletionStage resolveAllVariantsReferences( + @Nonnull final ProductDraftBuilder draftBuilder) { + final ProductVariantDraft masterVariantDraft = draftBuilder.getMasterVariant(); + return variantReferenceResolver + .resolveReferences(masterVariantDraft) + .thenApply(draftBuilder::masterVariant) + .thenCompose(this::resolveVariantsReferences); + } + + @Nonnull + private CompletionStage resolveVariantsReferences( + @Nonnull final ProductDraftBuilder draftBuilder) { + final List productDraftVariants = draftBuilder.getVariants(); + + return mapValuesToFutureOfCompletedValues( + productDraftVariants, variantReferenceResolver::resolveReferences, toList()) + .thenApply(draftBuilder::variants); + } + + /** + * Given a {@link ProductDraftBuilder} this method attempts to resolve the product type to return + * a {@link CompletionStage} which contains a new instance of the builder with the resolved + * product type reference. + * + * @param draftBuilder the productDraft to resolve its product type reference. + * @return a {@link CompletionStage} that contains as a result a new builder instance with + * resolved product type reference or, in case an error occurs during reference resolution, a + * {@link ReferenceResolutionException}. + */ + @Nonnull + public CompletionStage resolveProductTypeReference( + @Nonnull final ProductDraftBuilder draftBuilder) { + + final ResourceIdentifier productTypeReference = draftBuilder.getProductType(); + if (productTypeReference != null && productTypeReference.getId() == null) { + String productTypeKey; + try { + productTypeKey = getKeyFromResourceIdentifier(productTypeReference); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + ProductType.referenceTypeId(), + draftBuilder.getKey(), + referenceResolutionException.getMessage()))); + } + + return fetchAndResolveProductTypeReference(draftBuilder, productTypeKey); } - - @Nonnull - private CompletionStage resolveAllVariantsReferences( - @Nonnull final ProductDraftBuilder draftBuilder) { - final ProductVariantDraft masterVariantDraft = draftBuilder.getMasterVariant(); - return variantReferenceResolver.resolveReferences(masterVariantDraft) - .thenApply(draftBuilder::masterVariant) - .thenCompose(this::resolveVariantsReferences); + return completedFuture(draftBuilder); + } + + @Nonnull + private CompletionStage fetchAndResolveProductTypeReference( + @Nonnull final ProductDraftBuilder draftBuilder, @Nonnull final String productTypeKey) { + + return productTypeService + .fetchCachedProductTypeId(productTypeKey) + .thenCompose( + resolvedProductTypeIdOptional -> + resolvedProductTypeIdOptional + .map( + resolvedProductTypeId -> + completedFuture( + draftBuilder.productType( + ProductType.referenceOfId(resolvedProductTypeId) + .toResourceIdentifier()))) + .orElseGet( + () -> { + final String errorMessage = + format(PRODUCT_TYPE_DOES_NOT_EXIST, productTypeKey); + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + ProductType.referenceTypeId(), + draftBuilder.getKey(), + errorMessage))); + })); + } + + /** + * Given a {@link ProductDraftBuilder} this method attempts to resolve the categories and + * categoryOrderHints to return a {@link CompletionStage} which contains a new instance of the + * builder with the resolved references. + * + * @param draftBuilder the productDraft to resolve its category and categoryOrderHints references. + * @return a {@link CompletionStage} that contains as a result a new builder instance with + * resolved references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Nonnull + public CompletionStage resolveCategoryReferences( + @Nonnull final ProductDraftBuilder draftBuilder) { + + final Set> categoryResourceIdentifiers = + draftBuilder.getCategories(); + final Set categoryKeys = new HashSet<>(); + final List> directCategoryResourceIdentifiers = new ArrayList<>(); + for (ResourceIdentifier categoryResourceIdentifier : categoryResourceIdentifiers) { + if (categoryResourceIdentifier != null && categoryResourceIdentifier.getId() == null) { + try { + final String categoryKey = getKeyFromResourceIdentifier(categoryResourceIdentifier); + categoryKeys.add(categoryKey); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + Category.referenceTypeId(), + draftBuilder.getKey(), + referenceResolutionException.getMessage()))); + } + } else { + directCategoryResourceIdentifiers.add(categoryResourceIdentifier); + } } + return fetchAndResolveCategoryReferences( + draftBuilder, categoryKeys, directCategoryResourceIdentifiers); + } + + @Nonnull + private CompletionStage fetchAndResolveCategoryReferences( + @Nonnull final ProductDraftBuilder draftBuilder, + @Nonnull final Set categoryKeys, + @Nonnull final List> directCategoryReferences) { + + final Map categoryOrderHintsMap = new HashMap<>(); + final CategoryOrderHints categoryOrderHints = draftBuilder.getCategoryOrderHints(); + final Map keyToCategory = new HashMap<>(); + return categoryService + .fetchMatchingCategoriesByKeys(categoryKeys) + .thenApply( + categories -> + categories.stream() + .map( + category -> { + keyToCategory.put(category.getKey(), category); + if (categoryOrderHints != null) { + ofNullable(categoryOrderHints.get(category.getKey())) + .ifPresent( + orderHintValue -> + categoryOrderHintsMap.put( + category.getId(), orderHintValue)); + } + + return Category.referenceOfId(category.getId()).toResourceIdentifier(); + }) + .collect(toSet())) + .thenCompose( + categoryReferences -> { + String keysNotExists = + categoryKeys.stream() + .filter(categoryKey -> !keyToCategory.containsKey(categoryKey)) + .collect(joining(", ")); + + if (!isBlank(keysNotExists)) { + final String errorMessage = format(CATEGORIES_DO_NOT_EXIST, keysNotExists); + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + Category.resourceTypeId(), + draftBuilder.getKey(), + errorMessage))); + } - @Nonnull - private CompletionStage resolveVariantsReferences( - @Nonnull final ProductDraftBuilder draftBuilder) { - final List productDraftVariants = draftBuilder.getVariants(); + categoryReferences.addAll(directCategoryReferences); - return mapValuesToFutureOfCompletedValues(productDraftVariants, - variantReferenceResolver::resolveReferences, toList()).thenApply(draftBuilder::variants); + return completedFuture( + draftBuilder + .categories(categoryReferences) + .categoryOrderHints(CategoryOrderHints.of(categoryOrderHintsMap))); + }); + } + + /** + * Given a {@link ProductDraftBuilder} this method attempts to resolve the tax category to return + * a {@link CompletionStage} which contains a new instance of the builder with the resolved tax + * category reference. + * + * @param draftBuilder the productDraft to resolve its tax category reference. + * @return a {@link CompletionStage} that contains as a result a new builder instance with + * resolved tax category reference or, in case an error occurs during reference resolution, a + * {@link ReferenceResolutionException}. + */ + @Nonnull + public CompletionStage resolveTaxCategoryReference( + @Nonnull final ProductDraftBuilder draftBuilder) { + final ResourceIdentifier taxCategoryResourceIdentifier = + draftBuilder.getTaxCategory(); + if (taxCategoryResourceIdentifier != null && taxCategoryResourceIdentifier.getId() == null) { + String taxCategoryKey; + try { + taxCategoryKey = getKeyFromResourceIdentifier(taxCategoryResourceIdentifier); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + TaxCategory.referenceTypeId(), + draftBuilder.getKey(), + referenceResolutionException.getMessage()))); + } + + return fetchAndResolveTaxCategoryReference(draftBuilder, taxCategoryKey); } - - /** - * Given a {@link ProductDraftBuilder} this method attempts to resolve the product type to return a - * {@link CompletionStage} which contains a new instance of the builder with the resolved product type reference. - * - * @param draftBuilder the productDraft to resolve its product type reference. - * @return a {@link CompletionStage} that contains as a result a new builder instance with resolved product type - * reference or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Nonnull - public CompletionStage resolveProductTypeReference( - @Nonnull final ProductDraftBuilder draftBuilder) { - - final ResourceIdentifier productTypeReference = draftBuilder.getProductType(); - if (productTypeReference != null && productTypeReference.getId() == null) { - String productTypeKey; - try { - productTypeKey = getKeyFromResourceIdentifier(productTypeReference); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, ProductType.referenceTypeId(), draftBuilder.getKey(), - referenceResolutionException.getMessage()))); - } - - return fetchAndResolveProductTypeReference(draftBuilder, productTypeKey); - } - return completedFuture(draftBuilder); + return completedFuture(draftBuilder); + } + + @Nonnull + private CompletionStage fetchAndResolveTaxCategoryReference( + @Nonnull final ProductDraftBuilder draftBuilder, @Nonnull final String taxCategoryKey) { + + return taxCategoryService + .fetchCachedTaxCategoryId(taxCategoryKey) + .thenCompose( + resolvedTaxCategoryIdOptional -> + resolvedTaxCategoryIdOptional + .map( + resolvedTaxCategoryId -> + completedFuture( + draftBuilder.taxCategory( + TaxCategory.referenceOfId(resolvedTaxCategoryId) + .toResourceIdentifier()))) + .orElseGet( + () -> { + final String errorMessage = + format(TAX_CATEGORY_DOES_NOT_EXIST, taxCategoryKey); + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + TaxCategory.referenceTypeId(), + draftBuilder.getKey(), + errorMessage))); + })); + } + + /** + * Given a {@link ProductDraftBuilder} this method attempts to resolve the state to return a + * {@link CompletionStage} which contains a new instance of the builder with the resolved state + * reference. + * + *

Note: The key of the state reference taken from the value of the id field of the reference. + * + * @param draftBuilder the productDraft to resolve its state reference. + * @return a {@link CompletionStage} that contains as a result a new builder instance with + * resolved state reference or, in case an error occurs during reference resolution, the + * future is completed exceptionally with a {@link ReferenceResolutionException}. + */ + @Nonnull + public CompletionStage resolveStateReference( + @Nonnull final ProductDraftBuilder draftBuilder) { + final ResourceIdentifier stateReference = draftBuilder.getState(); + if (stateReference != null && stateReference.getId() == null) { + String stateKey; + try { + stateKey = getKeyFromResourceIdentifier(stateReference); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + State.referenceTypeId(), + draftBuilder.getKey(), + referenceResolutionException.getMessage()))); + } + + return fetchAndResolveStateReference(draftBuilder, stateKey); } - - @Nonnull - private CompletionStage fetchAndResolveProductTypeReference( - @Nonnull final ProductDraftBuilder draftBuilder, - @Nonnull final String productTypeKey) { - - return productTypeService - .fetchCachedProductTypeId(productTypeKey) - .thenCompose(resolvedProductTypeIdOptional -> resolvedProductTypeIdOptional - .map(resolvedProductTypeId -> - completedFuture(draftBuilder.productType( - ProductType.referenceOfId(resolvedProductTypeId).toResourceIdentifier()))) - .orElseGet(() -> { - final String errorMessage = format(PRODUCT_TYPE_DOES_NOT_EXIST, productTypeKey); - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, ProductType.referenceTypeId(), draftBuilder.getKey(), - errorMessage))); - })); + return completedFuture(draftBuilder); + } + + @Nonnull + private CompletionStage fetchAndResolveStateReference( + @Nonnull final ProductDraftBuilder draftBuilder, @Nonnull final String stateKey) { + + return stateService + .fetchCachedStateId(stateKey) + .thenCompose( + resolvedStateIdOptional -> + resolvedStateIdOptional + .map( + resolvedStateId -> + completedFuture( + draftBuilder.state(State.referenceOfId(resolvedStateId)))) + .orElseGet( + () -> { + final String errorMessage = format(STATE_DOES_NOT_EXIST, stateKey); + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + State.referenceTypeId(), + draftBuilder.getKey(), + errorMessage))); + })); + } + + /** + * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys (i.e product + * type, product attribute) from the commercetools to populate caches for the reference + * resolution. + * + *

Note: This method is meant be only used internally by the library to improve performance. + * + * @param referencedKeys a wrapper for the product references to fetch and cache the id's for. + * @return {@link CompletionStage}<{@link Map}<{@link String}>{@link String}>> in + * which the results of it's completions contains a map of requested references keys -> ids + * of product references. + */ + @Nonnull + public CompletableFuture> populateKeyToIdCachesForReferencedKeys( + @Nonnull final ProductBatchValidator.ReferencedKeys referencedKeys) { + + final List>> futures = new ArrayList<>(); + + final Set productKeys = referencedKeys.getProductKeys(); + if (!productKeys.isEmpty()) { + futures.add(productService.cacheKeysToIds(productKeys)); } - /** - * Given a {@link ProductDraftBuilder} this method attempts to resolve the categories and categoryOrderHints to - * return a {@link CompletionStage} which contains a new instance of the builder with the resolved references. - * - * @param draftBuilder the productDraft to resolve its category and categoryOrderHints references. - * @return a {@link CompletionStage} that contains as a result a new builder instance with resolved references or, - * in case an error occurs during reference resolution, a {@link ReferenceResolutionException}. - */ - @Nonnull - public CompletionStage resolveCategoryReferences( - @Nonnull final ProductDraftBuilder draftBuilder) { - - final Set> categoryResourceIdentifiers = draftBuilder.getCategories(); - final Set categoryKeys = new HashSet<>(); - final List> directCategoryResourceIdentifiers = new ArrayList<>(); - for (ResourceIdentifier categoryResourceIdentifier : categoryResourceIdentifiers) { - if (categoryResourceIdentifier != null && categoryResourceIdentifier.getId() == null) { - try { - final String categoryKey = getKeyFromResourceIdentifier(categoryResourceIdentifier); - categoryKeys.add(categoryKey); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture( - new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, Category.referenceTypeId(), - draftBuilder.getKey(), referenceResolutionException.getMessage()))); - } - } else { - directCategoryResourceIdentifiers.add(categoryResourceIdentifier); - } - } - return fetchAndResolveCategoryReferences(draftBuilder, categoryKeys, directCategoryResourceIdentifiers); + final Set productTypeKeys = referencedKeys.getProductTypeKeys(); + if (!productTypeKeys.isEmpty()) { + futures.add(productTypeService.cacheKeysToIds(productTypeKeys)); } - @Nonnull - private CompletionStage fetchAndResolveCategoryReferences( - @Nonnull final ProductDraftBuilder draftBuilder, - @Nonnull final Set categoryKeys, - @Nonnull final List> directCategoryReferences) { - - final Map categoryOrderHintsMap = new HashMap<>(); - final CategoryOrderHints categoryOrderHints = draftBuilder.getCategoryOrderHints(); - final Map keyToCategory = new HashMap<>(); - return categoryService - .fetchMatchingCategoriesByKeys(categoryKeys) - .thenApply(categories -> categories - .stream() - .map(category -> { - keyToCategory.put(category.getKey(), category); - if (categoryOrderHints != null) { - ofNullable(categoryOrderHints.get(category.getKey())) - .ifPresent(orderHintValue -> - categoryOrderHintsMap.put(category.getId(), orderHintValue)); - } - - return Category.referenceOfId(category.getId()).toResourceIdentifier(); - }) - .collect(toSet())) - .thenCompose(categoryReferences -> { - String keysNotExists = categoryKeys - .stream() - .filter(categoryKey -> !keyToCategory.containsKey(categoryKey)) - .collect(joining(", ")); - - if (!isBlank(keysNotExists)) { - final String errorMessage = format(CATEGORIES_DO_NOT_EXIST, keysNotExists); - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, Category.resourceTypeId(), draftBuilder.getKey(), - errorMessage))); - } - - categoryReferences.addAll(directCategoryReferences); - - return completedFuture(draftBuilder - .categories(categoryReferences) - .categoryOrderHints(CategoryOrderHints.of(categoryOrderHintsMap))); - }); + final Set categoryKeys = referencedKeys.getCategoryKeys(); + if (!categoryKeys.isEmpty()) { + futures.add(categoryService.cacheKeysToIds(categoryKeys)); } - /** - * Given a {@link ProductDraftBuilder} this method attempts to resolve the tax category to return a - * {@link CompletionStage} which contains a new instance of the builder with the resolved tax category reference. - * - * @param draftBuilder the productDraft to resolve its tax category reference. - * @return a {@link CompletionStage} that contains as a result a new builder instance with resolved tax category - * reference or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Nonnull - public CompletionStage resolveTaxCategoryReference( - @Nonnull final ProductDraftBuilder draftBuilder) { - final ResourceIdentifier taxCategoryResourceIdentifier = - draftBuilder.getTaxCategory(); - if (taxCategoryResourceIdentifier != null && taxCategoryResourceIdentifier.getId() == null) { - String taxCategoryKey; - try { - taxCategoryKey = getKeyFromResourceIdentifier(taxCategoryResourceIdentifier); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, TaxCategory.referenceTypeId(), draftBuilder.getKey(), - referenceResolutionException.getMessage()))); - } - - return fetchAndResolveTaxCategoryReference(draftBuilder, taxCategoryKey); - } - return completedFuture(draftBuilder); + final Set taxCategoryKeys = referencedKeys.getTaxCategoryKeys(); + if (!taxCategoryKeys.isEmpty()) { + futures.add(taxCategoryService.cacheKeysToIds(taxCategoryKeys)); } - @Nonnull - private CompletionStage fetchAndResolveTaxCategoryReference( - @Nonnull final ProductDraftBuilder draftBuilder, - @Nonnull final String taxCategoryKey) { - - return taxCategoryService - .fetchCachedTaxCategoryId(taxCategoryKey) - .thenCompose(resolvedTaxCategoryIdOptional -> resolvedTaxCategoryIdOptional - .map(resolvedTaxCategoryId -> - completedFuture(draftBuilder.taxCategory( - TaxCategory.referenceOfId(resolvedTaxCategoryId).toResourceIdentifier()))) - .orElseGet(() -> { - final String errorMessage = format(TAX_CATEGORY_DOES_NOT_EXIST, taxCategoryKey); - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, TaxCategory.referenceTypeId(), draftBuilder.getKey(), - errorMessage))); - })); + final Set typeKeys = referencedKeys.getTypeKeys(); + if (!typeKeys.isEmpty()) { + futures.add(typeService.cacheKeysToIds(typeKeys)); } - /** - * Given a {@link ProductDraftBuilder} this method attempts to resolve the state to return a {@link CompletionStage} - * which contains a new instance of the builder with the resolved state reference. - * - *

Note: The key of the state reference taken from the value of the id field of the reference. - * - * @param draftBuilder the productDraft to resolve its state reference. - * @return a {@link CompletionStage} that contains as a result a new builder instance with resolved state - * reference or, in case an error occurs during reference resolution, the future is completed exceptionally - * with a {@link ReferenceResolutionException}. - */ - @Nonnull - public CompletionStage resolveStateReference( - @Nonnull final ProductDraftBuilder draftBuilder) { - final ResourceIdentifier stateReference = draftBuilder.getState(); - if (stateReference != null && stateReference.getId() == null) { - String stateKey; - try { - stateKey = getKeyFromResourceIdentifier(stateReference); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, State.referenceTypeId(), draftBuilder.getKey(), - referenceResolutionException.getMessage()))); - } - - return fetchAndResolveStateReference(draftBuilder, stateKey); - } - return completedFuture(draftBuilder); + final Set channelKeys = referencedKeys.getChannelKeys(); + if (!channelKeys.isEmpty()) { + futures.add(channelService.cacheKeysToIds(typeKeys)); } - @Nonnull - private CompletionStage fetchAndResolveStateReference( - @Nonnull final ProductDraftBuilder draftBuilder, - @Nonnull final String stateKey) { - - return stateService - .fetchCachedStateId(stateKey) - .thenCompose(resolvedStateIdOptional -> resolvedStateIdOptional - .map(resolvedStateId -> - completedFuture(draftBuilder.state(State.referenceOfId(resolvedStateId)))) - .orElseGet(() -> { - final String errorMessage = format(STATE_DOES_NOT_EXIST, stateKey); - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, State.referenceTypeId(), draftBuilder.getKey(), - errorMessage))); - })); + final Set stateKeys = referencedKeys.getStateKeys(); + if (!stateKeys.isEmpty()) { + futures.add(stateService.cacheKeysToIds(stateKeys)); } - /** - * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys - * (i.e product type, product attribute) from the commercetools to populate caches for the reference resolution. - * - *

Note: This method is meant be only used internally by the library to improve performance. - * - * @param referencedKeys a wrapper for the product references to fetch and cache the id's for. - * @return {@link CompletionStage}<{@link Map}<{@link String}>{@link String}>> in which the results - * of it's completions contains a map of requested references keys -> ids of product references. - */ - @Nonnull - public CompletableFuture> populateKeyToIdCachesForReferencedKeys( - @Nonnull final ProductBatchValidator.ReferencedKeys referencedKeys) { - - final List>> futures = new ArrayList<>(); - - final Set productKeys = referencedKeys.getProductKeys(); - if (!productKeys.isEmpty()) { - futures.add(productService.cacheKeysToIds(productKeys)); - } - - final Set productTypeKeys = referencedKeys.getProductTypeKeys(); - if (!productTypeKeys.isEmpty()) { - futures.add(productTypeService.cacheKeysToIds(productTypeKeys)); - } - - final Set categoryKeys = referencedKeys.getCategoryKeys(); - if (!categoryKeys.isEmpty()) { - futures.add(categoryService.cacheKeysToIds(categoryKeys)); - } - - final Set taxCategoryKeys = referencedKeys.getTaxCategoryKeys(); - if (!taxCategoryKeys.isEmpty()) { - futures.add(taxCategoryService.cacheKeysToIds(taxCategoryKeys)); - } - - final Set typeKeys = referencedKeys.getTypeKeys(); - if (!typeKeys.isEmpty()) { - futures.add(typeService.cacheKeysToIds(typeKeys)); - } - - final Set channelKeys = referencedKeys.getChannelKeys(); - if (!channelKeys.isEmpty()) { - futures.add(channelService.cacheKeysToIds(typeKeys)); - } - - final Set stateKeys = referencedKeys.getStateKeys(); - if (!stateKeys.isEmpty()) { - futures.add(stateService.cacheKeysToIds(stateKeys)); - } - - final Set customerGroupKeys = referencedKeys.getCustomerGroupKeys(); - if (!customerGroupKeys.isEmpty()) { - futures.add(customerGroupService.cacheKeysToIds(customerGroupKeys)); - } - - final Set customObjectCompositeIdentifiers = - referencedKeys.getCustomObjectCompositeIdentifiers(); - if (!customObjectCompositeIdentifiers.isEmpty()) { - futures.add(customObjectService.cacheKeysToIds(customObjectCompositeIdentifiers)); - } + final Set customerGroupKeys = referencedKeys.getCustomerGroupKeys(); + if (!customerGroupKeys.isEmpty()) { + futures.add(customerGroupService.cacheKeysToIds(customerGroupKeys)); + } - return collectionOfFuturesToFutureOfCollection(futures, toList()) - .thenCompose(ignored -> productService.cacheKeysToIds(Collections.emptySet())); // return all cache. + final Set customObjectCompositeIdentifiers = + referencedKeys.getCustomObjectCompositeIdentifiers(); + if (!customObjectCompositeIdentifiers.isEmpty()) { + futures.add(customObjectService.cacheKeysToIds(customObjectCompositeIdentifiers)); } + + return collectionOfFuturesToFutureOfCollection(futures, toList()) + .thenCompose( + ignored -> productService.cacheKeysToIds(Collections.emptySet())); // return all cache. + } } diff --git a/src/main/java/com/commercetools/sync/products/helpers/ProductSyncStatistics.java b/src/main/java/com/commercetools/sync/products/helpers/ProductSyncStatistics.java index a288435af8..bf268d4209 100644 --- a/src/main/java/com/commercetools/sync/products/helpers/ProductSyncStatistics.java +++ b/src/main/java/com/commercetools/sync/products/helpers/ProductSyncStatistics.java @@ -1,80 +1,87 @@ package com.commercetools.sync.products.helpers; -import com.commercetools.sync.commons.helpers.BaseSyncStatistics; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.lang.String.format; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import com.commercetools.sync.commons.helpers.BaseSyncStatistics; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; - -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.lang.String.format; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class ProductSyncStatistics extends BaseSyncStatistics { - /** - * The following {@link Map} ({@code productKeysWithMissingParents}) represents products with - * missing parents (other referenced products). - * - *

    - *
  • key: key of the missing parent product
  • - *
  • value: a set of the parent's children product keys
  • - *
- * - *

The map is thread-safe (by instantiating it with {@link ConcurrentHashMap}). - * - */ - private ConcurrentHashMap> productKeysWithMissingParents = new ConcurrentHashMap<>(); + /** + * The following {@link Map} ({@code productKeysWithMissingParents}) represents products with + * missing parents (other referenced products). + * + *

    + *
  • key: key of the missing parent product + *
  • value: a set of the parent's children product keys + *
+ * + *

The map is thread-safe (by instantiating it with {@link ConcurrentHashMap}). + */ + private ConcurrentHashMap> productKeysWithMissingParents = + new ConcurrentHashMap<>(); - /** - * Builds a summary of the product sync statistics instance that looks like the following example: - * - *

"Summary: 4 product(s) were processed in total (1 created, 1 updated, 1 failed to sync - * and 1 product(s) with a missing reference(s))." - * - * @return a summary message of the product sync statistics instance. - */ - @Override - public String getReportMessage() { - return format("Summary: %s product(s) were processed in total " - + "(%s created, %s updated, %s failed to sync and %s product(s) with missing reference(s)).", - getProcessed(), getCreated(), getUpdated(), getFailed(), getNumberOfProductsWithMissingParents()); - } + /** + * Builds a summary of the product sync statistics instance that looks like the following example: + * + *

"Summary: 4 product(s) were processed in total (1 created, 1 updated, 1 failed to sync and 1 + * product(s) with a missing reference(s))." + * + * @return a summary message of the product sync statistics instance. + */ + @Override + public String getReportMessage() { + return format( + "Summary: %s product(s) were processed in total " + + "(%s created, %s updated, %s failed to sync and %s product(s) with missing reference(s)).", + getProcessed(), + getCreated(), + getUpdated(), + getFailed(), + getNumberOfProductsWithMissingParents()); + } - /** - * Returns the total number of products with missing parents. - * - * @return the total number of products with missing parents. - */ - public int getNumberOfProductsWithMissingParents() { - return (int) productKeysWithMissingParents - .values() - .stream() + /** + * Returns the total number of products with missing parents. + * + * @return the total number of products with missing parents. + */ + public int getNumberOfProductsWithMissingParents() { + return (int) + productKeysWithMissingParents.values().stream() .flatMap(Collection::stream) .distinct() .count(); - } + } - /** - * This method checks if there is an entry with the key of the {@code missingParentCategoryKey} in the - * {@code productKeysWithMissingParents}, if there isn't it creates a new entry with this parent key and as a value - * a new set containing the {@code childKey}. Otherwise, if there is already, it just adds the - * {@code childKey} to the existing set. - * - * @param parentKey the key of the missing parent. - * @param childKey the key of the product with a missing parent. - */ - public void addMissingDependency(@Nonnull final String parentKey, @Nonnull final String childKey) { - productKeysWithMissingParents.merge(parentKey, asSet(childKey), (existingSet, newChildAsSet) -> { - existingSet.addAll(newChildAsSet); - return existingSet; + /** + * This method checks if there is an entry with the key of the {@code missingParentCategoryKey} in + * the {@code productKeysWithMissingParents}, if there isn't it creates a new entry with this + * parent key and as a value a new set containing the {@code childKey}. Otherwise, if there is + * already, it just adds the {@code childKey} to the existing set. + * + * @param parentKey the key of the missing parent. + * @param childKey the key of the product with a missing parent. + */ + public void addMissingDependency( + @Nonnull final String parentKey, @Nonnull final String childKey) { + productKeysWithMissingParents.merge( + parentKey, + asSet(childKey), + (existingSet, newChildAsSet) -> { + existingSet.addAll(newChildAsSet); + return existingSet; }); - } + } - @Nullable - public Set removeAndGetReferencingKeys(@Nonnull final String key) { - return productKeysWithMissingParents.remove(key); - } + @Nullable + public Set removeAndGetReferencingKeys(@Nonnull final String key) { + return productKeysWithMissingParents.remove(key); + } } diff --git a/src/main/java/com/commercetools/sync/products/helpers/VariantReferenceResolver.java b/src/main/java/com/commercetools/sync/products/helpers/VariantReferenceResolver.java index e6147e2a06..094eb695fb 100644 --- a/src/main/java/com/commercetools/sync/products/helpers/VariantReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/products/helpers/VariantReferenceResolver.java @@ -1,5 +1,12 @@ package com.commercetools.sync.products.helpers; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.isReferenceOfType; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.AssetReferenceResolver; import com.commercetools.sync.commons.helpers.BaseReferenceResolver; @@ -25,197 +32,202 @@ import io.sphere.sdk.products.ProductVariantDraftBuilder; import io.sphere.sdk.products.attributes.AttributeDraft; import io.sphere.sdk.producttypes.ProductType; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; +import javax.annotation.Nonnull; -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.isReferenceOfType; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; - - -public final class VariantReferenceResolver extends BaseReferenceResolver { - private final PriceReferenceResolver priceReferenceResolver; - private final AssetReferenceResolver assetReferenceResolver; - private final ProductService productService; - private final ProductTypeService productTypeService; - private final CategoryService categoryService; - private final CustomObjectService customObjectService; - - /** - * Instantiates a {@link VariantReferenceResolver} instance that could be used to resolve the variants of product - * drafts in the CTP project specified in the injected {@link ProductSyncOptions} instance. - * - * @param productSyncOptions the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - * @param typeService the service to fetch the custom types for reference resolution. - * @param channelService the service to fetch the channels for reference resolution. - * @param customerGroupService the service to fetch the customer groups for reference resolution. - * @param productService the service to fetch the products for reference resolution. - * @param productTypeService the service to fetch the productTypes for reference resolution. - * @param categoryService the service to fetch the categories for reference resolution. - * @param customObjectService the service to fetch the custom objects for reference resolution. - */ - public VariantReferenceResolver(@Nonnull final ProductSyncOptions productSyncOptions, - @Nonnull final TypeService typeService, - @Nonnull final ChannelService channelService, - @Nonnull final CustomerGroupService customerGroupService, - @Nonnull final ProductService productService, - @Nonnull final ProductTypeService productTypeService, - @Nonnull final CategoryService categoryService, - @Nonnull final CustomObjectService customObjectService) { - super(productSyncOptions); - this.priceReferenceResolver = new PriceReferenceResolver(productSyncOptions, typeService, channelService, - customerGroupService); - this.assetReferenceResolver = new AssetReferenceResolver(productSyncOptions, typeService); - this.productService = productService; - this.categoryService = categoryService; - this.productTypeService = productTypeService; - this.customObjectService = customObjectService; - } - - - /** - * Given a {@link ProductVariantDraft} this method attempts to resolve the prices, assets and attributes to - * return a {@link CompletionStage} which contains a new instance of the draft with the resolved - * references. - * - *

Note: this method will filter out any null sub resources (e.g. prices, attributes or assets) under the - * returned resolved variant. - * - * @param productVariantDraft the product variant draft to resolve it's references. - * @return a {@link CompletionStage} that contains as a result a new productDraft instance with resolved references - * or, in case an error occurs during reference resolution, a {@link ReferenceResolutionException}. - */ - @Override - public CompletionStage resolveReferences( - @Nonnull final ProductVariantDraft productVariantDraft) { - return resolvePricesReferences(ProductVariantDraftBuilder.of(productVariantDraft)) - .thenCompose(this::resolveAssetsReferences) - .thenCompose(this::resolveAttributesReferences) - .thenApply(ProductVariantDraftBuilder::build); +public final class VariantReferenceResolver + extends BaseReferenceResolver { + private final PriceReferenceResolver priceReferenceResolver; + private final AssetReferenceResolver assetReferenceResolver; + private final ProductService productService; + private final ProductTypeService productTypeService; + private final CategoryService categoryService; + private final CustomObjectService customObjectService; + + /** + * Instantiates a {@link VariantReferenceResolver} instance that could be used to resolve the + * variants of product drafts in the CTP project specified in the injected {@link + * ProductSyncOptions} instance. + * + * @param productSyncOptions the container of all the options of the sync process including the + * CTP project client and/or configuration and other sync-specific options. + * @param typeService the service to fetch the custom types for reference resolution. + * @param channelService the service to fetch the channels for reference resolution. + * @param customerGroupService the service to fetch the customer groups for reference resolution. + * @param productService the service to fetch the products for reference resolution. + * @param productTypeService the service to fetch the productTypes for reference resolution. + * @param categoryService the service to fetch the categories for reference resolution. + * @param customObjectService the service to fetch the custom objects for reference resolution. + */ + public VariantReferenceResolver( + @Nonnull final ProductSyncOptions productSyncOptions, + @Nonnull final TypeService typeService, + @Nonnull final ChannelService channelService, + @Nonnull final CustomerGroupService customerGroupService, + @Nonnull final ProductService productService, + @Nonnull final ProductTypeService productTypeService, + @Nonnull final CategoryService categoryService, + @Nonnull final CustomObjectService customObjectService) { + super(productSyncOptions); + this.priceReferenceResolver = + new PriceReferenceResolver( + productSyncOptions, typeService, channelService, customerGroupService); + this.assetReferenceResolver = new AssetReferenceResolver(productSyncOptions, typeService); + this.productService = productService; + this.categoryService = categoryService; + this.productTypeService = productTypeService; + this.customObjectService = customObjectService; + } + + /** + * Given a {@link ProductVariantDraft} this method attempts to resolve the prices, assets and + * attributes to return a {@link CompletionStage} which contains a new instance of the draft with + * the resolved references. + * + *

Note: this method will filter out any null sub resources (e.g. prices, attributes or assets) + * under the returned resolved variant. + * + * @param productVariantDraft the product variant draft to resolve it's references. + * @return a {@link CompletionStage} that contains as a result a new productDraft instance with + * resolved references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Override + public CompletionStage resolveReferences( + @Nonnull final ProductVariantDraft productVariantDraft) { + return resolvePricesReferences(ProductVariantDraftBuilder.of(productVariantDraft)) + .thenCompose(this::resolveAssetsReferences) + .thenCompose(this::resolveAttributesReferences) + .thenApply(ProductVariantDraftBuilder::build); + } + + @Nonnull + CompletionStage resolveAssetsReferences( + @Nonnull final ProductVariantDraftBuilder productVariantDraftBuilder) { + + final List productVariantDraftAssets = productVariantDraftBuilder.getAssets(); + if (productVariantDraftAssets == null) { + return completedFuture(productVariantDraftBuilder); } - @Nonnull - CompletionStage resolveAssetsReferences( - @Nonnull final ProductVariantDraftBuilder productVariantDraftBuilder) { + return mapValuesToFutureOfCompletedValues( + productVariantDraftAssets, assetReferenceResolver::resolveReferences, toList()) + .thenApply(productVariantDraftBuilder::assets); + } - final List productVariantDraftAssets = productVariantDraftBuilder.getAssets(); - if (productVariantDraftAssets == null) { - return completedFuture(productVariantDraftBuilder); - } + @Nonnull + CompletionStage resolvePricesReferences( + @Nonnull final ProductVariantDraftBuilder productVariantDraftBuilder) { - return mapValuesToFutureOfCompletedValues(productVariantDraftAssets, - assetReferenceResolver::resolveReferences, toList()).thenApply(productVariantDraftBuilder::assets); + final List productVariantDraftPrices = productVariantDraftBuilder.getPrices(); + if (productVariantDraftPrices == null) { + return completedFuture(productVariantDraftBuilder); } - @Nonnull - CompletionStage resolvePricesReferences( - @Nonnull final ProductVariantDraftBuilder productVariantDraftBuilder) { + return mapValuesToFutureOfCompletedValues( + productVariantDraftPrices, priceReferenceResolver::resolveReferences, toList()) + .thenApply(productVariantDraftBuilder::prices); + } - final List productVariantDraftPrices = productVariantDraftBuilder.getPrices(); - if (productVariantDraftPrices == null) { - return completedFuture(productVariantDraftBuilder); - } + @Nonnull + private CompletionStage resolveAttributesReferences( + @Nonnull final ProductVariantDraftBuilder productVariantDraftBuilder) { - return mapValuesToFutureOfCompletedValues(productVariantDraftPrices, - priceReferenceResolver::resolveReferences, toList()) - .thenApply(productVariantDraftBuilder::prices); + final List attributeDrafts = productVariantDraftBuilder.getAttributes(); + if (attributeDrafts == null) { + return completedFuture(productVariantDraftBuilder); } - @Nonnull - private CompletionStage resolveAttributesReferences( - @Nonnull final ProductVariantDraftBuilder productVariantDraftBuilder) { + return mapValuesToFutureOfCompletedValues( + attributeDrafts, this::resolveAttributeReference, toList()) + .thenApply(productVariantDraftBuilder::attributes); + } + + @Nonnull + private CompletionStage resolveAttributeReference( + @Nonnull final AttributeDraft attributeDraft) { - final List attributeDrafts = productVariantDraftBuilder.getAttributes(); - if (attributeDrafts == null) { - return completedFuture(productVariantDraftBuilder); - } + final JsonNode attributeDraftValue = attributeDraft.getValue(); - return mapValuesToFutureOfCompletedValues(attributeDrafts, this::resolveAttributeReference, toList()) - .thenApply(productVariantDraftBuilder::attributes); + if (attributeDraftValue == null) { + return CompletableFuture.completedFuture(attributeDraft); } - @Nonnull - private CompletionStage resolveAttributeReference(@Nonnull final AttributeDraft attributeDraft) { + final JsonNode attributeDraftValueClone = attributeDraftValue.deepCopy(); - final JsonNode attributeDraftValue = attributeDraft.getValue(); + final List allAttributeReferences = + attributeDraftValueClone.findParents(REFERENCE_TYPE_ID_FIELD); - if (attributeDraftValue == null) { - return CompletableFuture.completedFuture(attributeDraft); - } + if (!allAttributeReferences.isEmpty()) { + return mapValuesToFutureOfCompletedValues( + allAttributeReferences, this::resolveReference, toList()) + .thenApply( + ignoredResult -> + AttributeDraft.of(attributeDraft.getName(), attributeDraftValueClone)); + } - final JsonNode attributeDraftValueClone = attributeDraftValue.deepCopy(); + return CompletableFuture.completedFuture(attributeDraft); + } - final List allAttributeReferences = attributeDraftValueClone.findParents(REFERENCE_TYPE_ID_FIELD); + @Nonnull + private CompletionStage resolveReference(@Nonnull final JsonNode referenceValue) { + return getResolvedId(referenceValue) + .thenAccept( + optionalId -> + optionalId.ifPresent( + id -> ((ObjectNode) referenceValue).put(REFERENCE_ID_FIELD, id))); + } - if (!allAttributeReferences.isEmpty()) { - return mapValuesToFutureOfCompletedValues(allAttributeReferences, this::resolveReference, toList()) - .thenApply(ignoredResult -> AttributeDraft.of(attributeDraft.getName(), attributeDraftValueClone)); - } + @Nonnull + private CompletionStage> getResolvedId(@Nonnull final JsonNode referenceValue) { - return CompletableFuture.completedFuture(attributeDraft); + if (isReferenceOfType(referenceValue, Product.referenceTypeId())) { + return getResolvedIdFromKeyInReference(referenceValue, productService::getIdFromCacheOrFetch); } - @Nonnull - private CompletionStage resolveReference(@Nonnull final JsonNode referenceValue) { - return getResolvedId(referenceValue) - .thenAccept(optionalId -> - optionalId.ifPresent(id -> ((ObjectNode) referenceValue).put(REFERENCE_ID_FIELD, id))); + if (isReferenceOfType(referenceValue, Category.referenceTypeId())) { + return getResolvedIdFromKeyInReference( + referenceValue, categoryService::fetchCachedCategoryId); } - @Nonnull - private CompletionStage> getResolvedId(@Nonnull final JsonNode referenceValue) { - - if (isReferenceOfType(referenceValue, Product.referenceTypeId())) { - return getResolvedIdFromKeyInReference(referenceValue, productService::getIdFromCacheOrFetch); - } + if (isReferenceOfType(referenceValue, ProductType.referenceTypeId())) { + return getResolvedIdFromKeyInReference( + referenceValue, productTypeService::fetchCachedProductTypeId); + } - if (isReferenceOfType(referenceValue, Category.referenceTypeId())) { - return getResolvedIdFromKeyInReference(referenceValue, categoryService::fetchCachedCategoryId); - } + if (isReferenceOfType(referenceValue, CustomObject.referenceTypeId())) { + return getResolvedIdFromKeyInReference(referenceValue, this::resolveCustomObjectReference); + } - if (isReferenceOfType(referenceValue, ProductType.referenceTypeId())) { - return getResolvedIdFromKeyInReference(referenceValue, productTypeService::fetchCachedProductTypeId); - } + return CompletableFuture.completedFuture(Optional.empty()); + } - if (isReferenceOfType(referenceValue, CustomObject.referenceTypeId())) { - return getResolvedIdFromKeyInReference(referenceValue, this::resolveCustomObjectReference); - } + @Nonnull + private CompletionStage> getResolvedIdFromKeyInReference( + @Nonnull final JsonNode referenceValue, + @Nonnull final Function>> resolvedIdFetcher) { - return CompletableFuture.completedFuture(Optional.empty()); - } + final JsonNode idField = referenceValue.get(REFERENCE_ID_FIELD); + return idField != null && !Objects.equals(idField, NullNode.getInstance()) + ? resolvedIdFetcher.apply(idField.asText()) + : CompletableFuture.completedFuture(Optional.empty()); + } - @Nonnull - private CompletionStage> getResolvedIdFromKeyInReference( - @Nonnull final JsonNode referenceValue, - @Nonnull final Function>> resolvedIdFetcher) { + private CompletionStage> resolveCustomObjectReference( + @Nonnull final String resolvedIdText) { - final JsonNode idField = referenceValue.get(REFERENCE_ID_FIELD); - return idField != null && !Objects.equals(idField, NullNode.getInstance()) - ? resolvedIdFetcher.apply(idField.asText()) - : CompletableFuture.completedFuture(Optional.empty()); + if (SyncUtils.isUuid(resolvedIdText)) { + return completedFuture(Optional.empty()); } - private CompletionStage> resolveCustomObjectReference( - @Nonnull final String resolvedIdText) { - - if (SyncUtils.isUuid(resolvedIdText)) { - return completedFuture(Optional.empty()); - } + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(resolvedIdText); - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = - CustomObjectCompositeIdentifier.of(resolvedIdText); - - return customObjectService.fetchCachedCustomObjectId(customObjectCompositeIdentifier); - } + return customObjectService.fetchCachedCustomObjectId(customObjectCompositeIdentifier); + } } - diff --git a/src/main/java/com/commercetools/sync/products/templates/beforeupdatecallback/KeepOtherVariantsSync.java b/src/main/java/com/commercetools/sync/products/templates/beforeupdatecallback/KeepOtherVariantsSync.java index bb0b0c7c5a..5feb08f583 100644 --- a/src/main/java/com/commercetools/sync/products/templates/beforeupdatecallback/KeepOtherVariantsSync.java +++ b/src/main/java/com/commercetools/sync/products/templates/beforeupdatecallback/KeepOtherVariantsSync.java @@ -4,30 +4,31 @@ import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.RemoveVariant; - -import javax.annotation.Nonnull; import java.util.List; import java.util.stream.Collectors; +import javax.annotation.Nonnull; public class KeepOtherVariantsSync { - /** - * Takes product update actions, a new {@link ProductDraft}, an old existing {@link Product}. - * This method filters out the update action if it is a {@link RemoveVariant} update action. - * - *

Using this method as a BeforeUpdateCallback would prevent the removal of not existing variants in the target - * product. - * - * @param updateActions the update action built from comparing {@code newProductDraft} and {@code oldProduct}. - * @param newProductDraft the new {@link ProductDraft} being synced. - * @param oldProduct the old existing {@link Product}. - * @return the same list of supplied {@code updateActions} without {@link RemoveVariant} update actions. - */ - public static List> keepOtherVariants( - @Nonnull final List> updateActions, - @Nonnull final ProductDraft newProductDraft, - @Nonnull final Product oldProduct) { - return updateActions.stream() - .filter(updateAction -> !(updateAction instanceof RemoveVariant)) - .collect(Collectors.toList()); - } + /** + * Takes product update actions, a new {@link ProductDraft}, an old existing {@link Product}. This + * method filters out the update action if it is a {@link RemoveVariant} update action. + * + *

Using this method as a BeforeUpdateCallback would prevent the removal of not existing + * variants in the target product. + * + * @param updateActions the update action built from comparing {@code newProductDraft} and {@code + * oldProduct}. + * @param newProductDraft the new {@link ProductDraft} being synced. + * @param oldProduct the old existing {@link Product}. + * @return the same list of supplied {@code updateActions} without {@link RemoveVariant} update + * actions. + */ + public static List> keepOtherVariants( + @Nonnull final List> updateActions, + @Nonnull final ProductDraft newProductDraft, + @Nonnull final Product oldProduct) { + return updateActions.stream() + .filter(updateAction -> !(updateAction instanceof RemoveVariant)) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/commercetools/sync/products/templates/beforeupdatecallback/SyncSingleLocale.java b/src/main/java/com/commercetools/sync/products/templates/beforeupdatecallback/SyncSingleLocale.java index f1b075903e..552d0f6f3c 100644 --- a/src/main/java/com/commercetools/sync/products/templates/beforeupdatecallback/SyncSingleLocale.java +++ b/src/main/java/com/commercetools/sync/products/templates/beforeupdatecallback/SyncSingleLocale.java @@ -1,5 +1,10 @@ package com.commercetools.sync.products.templates.beforeupdatecallback; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.LocalizedStringEntry; @@ -13,231 +18,284 @@ import io.sphere.sdk.products.commands.updateactions.SetMetaDescription; import io.sphere.sdk.products.commands.updateactions.SetMetaKeywords; import io.sphere.sdk.products.commands.updateactions.SetMetaTitle; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.function.Function; - -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class SyncSingleLocale { - /** - * Takes in a {@link List} of product update actions that was built from comparing a {@code newProductDraft} and an - * {@code oldProduct} and maps the update actions so that only localizations with value {@link Locale#FRENCH} - * are synced and all the other locales are left untouched. - * - * @param updateActions the update actions built from comparing {@code newProductDraft} and {@code oldProduct}. - * @param newProductDraft the new {@link ProductDraft} being synced. - * @param oldProduct the old existing {@link Product}. - * @return a new list of update actions that corresponds to changes on French localizations only. - */ - @Nonnull - public static List> syncFrenchDataOnly( - @Nonnull final List> updateActions, - @Nonnull final ProductDraft newProductDraft, - @Nonnull final Product oldProduct) { - - final List>> optionalActions = - updateActions.stream() - .map(action -> filterSingleLocalization(action, newProductDraft, oldProduct, Locale.FRENCH)) - .collect(toList()); - - return filterEmptyOptionals(optionalActions); - } + /** + * Takes in a {@link List} of product update actions that was built from comparing a {@code + * newProductDraft} and an {@code oldProduct} and maps the update actions so that only + * localizations with value {@link Locale#FRENCH} are synced and all the other locales are left + * untouched. + * + * @param updateActions the update actions built from comparing {@code newProductDraft} and {@code + * oldProduct}. + * @param newProductDraft the new {@link ProductDraft} being synced. + * @param oldProduct the old existing {@link Product}. + * @return a new list of update actions that corresponds to changes on French localizations only. + */ + @Nonnull + public static List> syncFrenchDataOnly( + @Nonnull final List> updateActions, + @Nonnull final ProductDraft newProductDraft, + @Nonnull final Product oldProduct) { - /** - * Takes a product update action, a new {@link ProductDraft}, an old existing {@link Product} and a {@link Locale}. - * This method checks if the update action is either one of the following update actions: - *

    - *
  • {@link ChangeName}
  • - *
  • {@link SetMetaKeywords}
  • - *
  • {@link SetMetaDescription}
  • - *
  • {@link SetMetaTitle}
  • - *
  • {@link ChangeSlug}
  • - *
  • {@link SetDescription}
  • - *
- * If the update action is one of the aforementioned update actions, the method checks if the change in - * localization generated from the update action corresponds to a change in the supplied {@link Locale} value and - * maps the update action so that it only corresponds to such change in the localized field. Namely, if the - * change was only in another locale value other than the supplied one, then no update action is needed. On the - * other hand, if the change was in the supplied locale value then the update action is modified so that it only - * corresponds to the change in that locale value. - * - * @param updateAction the update action built from comparing {@code newProductDraft} and {@code oldProduct}. - * @param newProductDraft the new {@link ProductDraft} being synced. - * @param oldProduct the old existing {@link Product}. - * @param locale the locale value to only compare and map the update action to accordingly. - * @return an optional containing the mapped update action or empty value if an update action is not needed. - */ - @Nonnull - private static Optional> filterSingleLocalization( - @Nonnull final UpdateAction updateAction, - @Nonnull final ProductDraft newProductDraft, - @Nonnull final Product oldProduct, - //TODO: ProductType should be passes to for attribute comparison. GITHUB ISSUE #189 - @Nonnull final Locale locale) { - - if (updateAction instanceof ChangeName) { - return filterLocalizedField(newProductDraft, oldProduct, locale, ProductDraft::getName, - ProductData::getName, ChangeName::of); - } - if (updateAction instanceof SetDescription) { - return filterLocalizedField(newProductDraft, oldProduct, locale, ProductDraft::getDescription, - ProductData::getDescription, SetDescription::of); - } - if (updateAction instanceof ChangeSlug) { - return filterLocalizedField(newProductDraft, oldProduct, locale, ProductDraft::getSlug, - ProductData::getSlug, ChangeSlug::of); - } - if (updateAction instanceof SetMetaTitle) { - return filterLocalizedField(newProductDraft, oldProduct, locale, ProductDraft::getMetaTitle, - ProductData::getMetaTitle, SetMetaTitle::of); - } - if (updateAction instanceof SetMetaDescription) { - return filterLocalizedField(newProductDraft, oldProduct, locale, ProductDraft::getMetaDescription, - ProductData::getMetaDescription, SetMetaDescription::of); - } - if (updateAction instanceof SetMetaKeywords) { - return filterLocalizedField(newProductDraft, oldProduct, locale, ProductDraft::getMetaKeywords, - ProductData::getMetaKeywords, SetMetaKeywords::of); - } - return Optional.of(updateAction); - } + final List>> optionalActions = + updateActions.stream() + .map( + action -> + filterSingleLocalization(action, newProductDraft, oldProduct, Locale.FRENCH)) + .collect(toList()); - /** - * Checks if the localized field value of the supplied {@link Locale} is different between the old and the new - * resource, if it is different, then an update action is generated with that change only of this localized field. - * if the values are not different, then an empty optional is returned. - * - * @param newDraft the new product draft. - * @param oldProduct the old existing product. - * @param locale the locale of the localized field to sync. - * @param newLocalizedFieldMapper mapper function to access the localized field on the new product draft. - * @param oldLocalizedFieldMapper mapper function to access the localized field on the old existing product. - * @param updateActionMapper mapper function to build the update action to sync the localized field of the - * product. - * @return an optional containing an update action if the localized field with the specific locale has changed or - * empty otherwise. - */ - @Nonnull - private static Optional> filterLocalizedField(@Nonnull final ProductDraft newDraft, - @Nonnull final Product oldProduct, - @Nonnull final Locale locale, - @Nonnull final Function newLocalizedFieldMapper, - @Nonnull final Function oldLocalizedFieldMapper, - @Nonnull final Function> updateActionMapper) { - final LocalizedString newLocalizedField = newLocalizedFieldMapper.apply(newDraft); - final LocalizedString oldLocalizedField = oldLocalizedFieldMapper.apply(oldProduct.getMasterData().getStaged()); - if (oldLocalizedField != null && newLocalizedField != null) { - // if both old and new localized fields are set, only update if the locale values are not equal. - final String newLocaleValue = newLocalizedField.get(locale); - final String oldLocaleValue = oldLocalizedField.get(locale); - - // We are sure that both old locale and new locale have different values in this method. - // if old locale value is set, remove it from old localized field - final LocalizedString withLocaleChange = ofNullable(oldLocaleValue) - .map(value -> LocalizedString.of( - oldLocalizedField.stream() - .filter(localization -> !localization.getLocale().equals(locale)) - .collect(toMap(LocalizedStringEntry::getLocale, - LocalizedStringEntry::getValue)))) - .orElse(oldLocalizedField); - - // Only if old locale value is not set and the new locale value is set, - // update the old localized field with the new locale value - return ofNullable(ofNullable(newLocaleValue) - .map(val -> updateActionMapper.apply(withLocaleChange.plus(locale, val))) - .orElseGet(() -> updateActionMapper.apply(withLocaleChange))); - } else { - if (oldLocalizedField != null) { - // If old localized field is set but the new one is unset, only update if the locale value is set in - // the old field. - return ofNullable(oldLocalizedField.get(locale)) - .map(localValue -> updateActionMapper.apply(LocalizedString.empty())); - } else { - // If old localized field is unset but the new one is set, only update if the locale value is set in - // the new field. - return ofNullable(newLocalizedField.get(locale)) - .map(newLocalValue -> updateActionMapper.apply(LocalizedString.of(locale, newLocalValue))); - } - } - } + return filterEmptyOptionals(optionalActions); + } - /** - * Takes a {@link ProductDraft} and filters out any localization other than {@link Locale#FRENCH} from - * {@link LocalizedString} fields of the supplied {@link ProductDraft}. Note: This method will only - * filter the {@link LocalizedString} fields on the {@link ProductDraft} level, so it will not filter out {@link - * LocalizedString} fields on the variant level. - * - * @param productDraft the product draft to filter the localizations from. - * @return a new product draft with {@link LocalizedString} fields that have only entries of the - * {@link Locale#FRENCH} locale. - */ - @Nonnull - public static ProductDraft filterFrenchLocales(@Nonnull final ProductDraft productDraft) { - return filterLocalizedStrings(productDraft, Locale.FRENCH); - } + /** + * Takes a product update action, a new {@link ProductDraft}, an old existing {@link Product} and + * a {@link Locale}. This method checks if the update action is either one of the following update + * actions: + * + *
    + *
  • {@link ChangeName} + *
  • {@link SetMetaKeywords} + *
  • {@link SetMetaDescription} + *
  • {@link SetMetaTitle} + *
  • {@link ChangeSlug} + *
  • {@link SetDescription} + *
+ * + * If the update action is one of the aforementioned update actions, the method checks if the + * change in localization generated from the update action corresponds to a change in the supplied + * {@link Locale} value and maps the update action so that it only corresponds to such change in + * the localized field. Namely, if the change was only in another locale value other than the + * supplied one, then no update action is needed. On the other hand, if the change was in the + * supplied locale value then the update action is modified so that it only corresponds to the + * change in that locale value. + * + * @param updateAction the update action built from comparing {@code newProductDraft} and {@code + * oldProduct}. + * @param newProductDraft the new {@link ProductDraft} being synced. + * @param oldProduct the old existing {@link Product}. + * @param locale the locale value to only compare and map the update action to accordingly. + * @return an optional containing the mapped update action or empty value if an update action is + * not needed. + */ + @Nonnull + private static Optional> filterSingleLocalization( + @Nonnull final UpdateAction updateAction, + @Nonnull final ProductDraft newProductDraft, + @Nonnull final Product oldProduct, + // TODO: ProductType should be passes to for attribute comparison. GITHUB ISSUE #189 + @Nonnull final Locale locale) { - /** - * Takes a {@link ProductDraft} and a locale and filters out any localization other than the specified one in the - * locale from {@link LocalizedString} fields of the supplied {@link ProductDraft}. Note: This method will only - * filter the {@link LocalizedString} fields on the {@link ProductDraft} level, so it will not filter out {@link - * LocalizedString} fields on the variant level. - * - * @param productDraft the product draft to filter the localizations from. - * @param locale the locale to filter in. - * @return a new product draft with {@link LocalizedString} fields that have only entries of the supplied locale. - */ - @Nonnull - private static ProductDraft filterLocalizedStrings(@Nonnull final ProductDraft productDraft, - @Nonnull final Locale locale) { - - final LocalizedString name = productDraft.getName(); - final LocalizedString slug = productDraft.getSlug(); - final LocalizedString description = productDraft.getDescription(); - final LocalizedString metaDescription = productDraft.getMetaDescription(); - final LocalizedString metaKeywords = productDraft.getMetaKeywords(); - final LocalizedString metaTitle = productDraft.getMetaTitle(); - - - return ProductDraftBuilder.of(productDraft) - .name(filterLocale(name, locale)) - .slug(filterLocale(slug, locale)) - .description(filterLocale(description, locale)) - .metaDescription(filterLocale(metaDescription, locale)) - .metaKeywords(filterLocale(metaKeywords, locale)) - .metaTitle(filterLocale(metaTitle, locale)) - .build(); + if (updateAction instanceof ChangeName) { + return filterLocalizedField( + newProductDraft, + oldProduct, + locale, + ProductDraft::getName, + ProductData::getName, + ChangeName::of); } - - /** - * Takes a {@link LocalizedString} and a locale and filters out any localization other than the specified locale. - * - * @param localizedString the localizedString to filter the localizations from. - * @param locale the locale to filter in. - * @return a new {@link LocalizedString} with only entries of the supplied locale. - */ - @Nullable - private static LocalizedString filterLocale(@Nullable final LocalizedString localizedString, - @Nonnull final Locale locale) { - return ofNullable(localizedString) - .map(lText -> lText.stream() - .filter(localizedStringEntry -> Objects.equals(localizedStringEntry.getLocale(), locale)) - .collect(toMap(LocalizedStringEntry::getLocale, LocalizedStringEntry::getValue))) - .map(LocalizedString::of) - .orElse(null); + if (updateAction instanceof SetDescription) { + return filterLocalizedField( + newProductDraft, + oldProduct, + locale, + ProductDraft::getDescription, + ProductData::getDescription, + SetDescription::of); + } + if (updateAction instanceof ChangeSlug) { + return filterLocalizedField( + newProductDraft, + oldProduct, + locale, + ProductDraft::getSlug, + ProductData::getSlug, + ChangeSlug::of); + } + if (updateAction instanceof SetMetaTitle) { + return filterLocalizedField( + newProductDraft, + oldProduct, + locale, + ProductDraft::getMetaTitle, + ProductData::getMetaTitle, + SetMetaTitle::of); + } + if (updateAction instanceof SetMetaDescription) { + return filterLocalizedField( + newProductDraft, + oldProduct, + locale, + ProductDraft::getMetaDescription, + ProductData::getMetaDescription, + SetMetaDescription::of); + } + if (updateAction instanceof SetMetaKeywords) { + return filterLocalizedField( + newProductDraft, + oldProduct, + locale, + ProductDraft::getMetaKeywords, + ProductData::getMetaKeywords, + SetMetaKeywords::of); } + return Optional.of(updateAction); + } - private SyncSingleLocale() { + /** + * Checks if the localized field value of the supplied {@link Locale} is different between the old + * and the new resource, if it is different, then an update action is generated with that change + * only of this localized field. if the values are not different, then an empty optional is + * returned. + * + * @param newDraft the new product draft. + * @param oldProduct the old existing product. + * @param locale the locale of the localized field to sync. + * @param newLocalizedFieldMapper mapper function to access the localized field on the new product + * draft. + * @param oldLocalizedFieldMapper mapper function to access the localized field on the old + * existing product. + * @param updateActionMapper mapper function to build the update action to sync the localized + * field of the product. + * @return an optional containing an update action if the localized field with the specific locale + * has changed or empty otherwise. + */ + @Nonnull + private static Optional> filterLocalizedField( + @Nonnull final ProductDraft newDraft, + @Nonnull final Product oldProduct, + @Nonnull final Locale locale, + @Nonnull final Function newLocalizedFieldMapper, + @Nonnull final Function oldLocalizedFieldMapper, + @Nonnull final Function> updateActionMapper) { + final LocalizedString newLocalizedField = newLocalizedFieldMapper.apply(newDraft); + final LocalizedString oldLocalizedField = + oldLocalizedFieldMapper.apply(oldProduct.getMasterData().getStaged()); + if (oldLocalizedField != null && newLocalizedField != null) { + // if both old and new localized fields are set, only update if the locale values are not + // equal. + final String newLocaleValue = newLocalizedField.get(locale); + final String oldLocaleValue = oldLocalizedField.get(locale); + + // We are sure that both old locale and new locale have different values in this method. + // if old locale value is set, remove it from old localized field + final LocalizedString withLocaleChange = + ofNullable(oldLocaleValue) + .map( + value -> + LocalizedString.of( + oldLocalizedField.stream() + .filter(localization -> !localization.getLocale().equals(locale)) + .collect( + toMap( + LocalizedStringEntry::getLocale, + LocalizedStringEntry::getValue)))) + .orElse(oldLocalizedField); + + // Only if old locale value is not set and the new locale value is set, + // update the old localized field with the new locale value + return ofNullable( + ofNullable(newLocaleValue) + .map(val -> updateActionMapper.apply(withLocaleChange.plus(locale, val))) + .orElseGet(() -> updateActionMapper.apply(withLocaleChange))); + } else { + if (oldLocalizedField != null) { + // If old localized field is set but the new one is unset, only update if the locale value + // is set in + // the old field. + return ofNullable(oldLocalizedField.get(locale)) + .map(localValue -> updateActionMapper.apply(LocalizedString.empty())); + } else { + // If old localized field is unset but the new one is set, only update if the locale value + // is set in + // the new field. + return ofNullable(newLocalizedField.get(locale)) + .map( + newLocalValue -> + updateActionMapper.apply(LocalizedString.of(locale, newLocalValue))); + } } + } + + /** + * Takes a {@link ProductDraft} and filters out any localization other than {@link Locale#FRENCH} + * from {@link LocalizedString} fields of the supplied {@link ProductDraft}. Note: This method + * will only filter the {@link LocalizedString} fields on the {@link ProductDraft} level, so it + * will not filter out {@link LocalizedString} fields on the variant level. + * + * @param productDraft the product draft to filter the localizations from. + * @return a new product draft with {@link LocalizedString} fields that have only entries of the + * {@link Locale#FRENCH} locale. + */ + @Nonnull + public static ProductDraft filterFrenchLocales(@Nonnull final ProductDraft productDraft) { + return filterLocalizedStrings(productDraft, Locale.FRENCH); + } + + /** + * Takes a {@link ProductDraft} and a locale and filters out any localization other than the + * specified one in the locale from {@link LocalizedString} fields of the supplied {@link + * ProductDraft}. Note: This method will only filter the {@link LocalizedString} fields on the + * {@link ProductDraft} level, so it will not filter out {@link LocalizedString} fields on the + * variant level. + * + * @param productDraft the product draft to filter the localizations from. + * @param locale the locale to filter in. + * @return a new product draft with {@link LocalizedString} fields that have only entries of the + * supplied locale. + */ + @Nonnull + private static ProductDraft filterLocalizedStrings( + @Nonnull final ProductDraft productDraft, @Nonnull final Locale locale) { + + final LocalizedString name = productDraft.getName(); + final LocalizedString slug = productDraft.getSlug(); + final LocalizedString description = productDraft.getDescription(); + final LocalizedString metaDescription = productDraft.getMetaDescription(); + final LocalizedString metaKeywords = productDraft.getMetaKeywords(); + final LocalizedString metaTitle = productDraft.getMetaTitle(); + + return ProductDraftBuilder.of(productDraft) + .name(filterLocale(name, locale)) + .slug(filterLocale(slug, locale)) + .description(filterLocale(description, locale)) + .metaDescription(filterLocale(metaDescription, locale)) + .metaKeywords(filterLocale(metaKeywords, locale)) + .metaTitle(filterLocale(metaTitle, locale)) + .build(); + } + + /** + * Takes a {@link LocalizedString} and a locale and filters out any localization other than the + * specified locale. + * + * @param localizedString the localizedString to filter the localizations from. + * @param locale the locale to filter in. + * @return a new {@link LocalizedString} with only entries of the supplied locale. + */ + @Nullable + private static LocalizedString filterLocale( + @Nullable final LocalizedString localizedString, @Nonnull final Locale locale) { + return ofNullable(localizedString) + .map( + lText -> + lText.stream() + .filter( + localizedStringEntry -> + Objects.equals(localizedStringEntry.getLocale(), locale)) + .collect( + toMap(LocalizedStringEntry::getLocale, LocalizedStringEntry::getValue))) + .map(LocalizedString::of) + .orElse(null); + } + + private SyncSingleLocale() {} } diff --git a/src/main/java/com/commercetools/sync/products/utils/ProductReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/products/utils/ProductReferenceResolutionUtils.java index edc461f79a..7503ccb448 100644 --- a/src/main/java/com/commercetools/sync/products/utils/ProductReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/products/utils/ProductReferenceResolutionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; +import static java.util.stream.Collectors.toList; + import com.commercetools.sync.commons.helpers.CategoryReferencePair; import io.sphere.sdk.categories.Category; import io.sphere.sdk.channels.Channel; @@ -23,8 +26,6 @@ import io.sphere.sdk.states.State; import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -33,232 +34,244 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class ProductReferenceResolutionUtils { - /** - * Returns an {@link List}<{@link ProductDraft}> consisting of the results of applying the - * mapping from {@link Product} to {@link ProductDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
productType{@link Reference}<{@link ProductType}>{@link ResourceIdentifier}<{@link ProductType}>
categories{@link Set}<{@link Reference}<{@link Category}>>{@link Set}<{@link ResourceIdentifier}<{@link Category}>>
variants.prices.channel{@link Reference}<{@link Channel}>{@link ResourceIdentifier}<{@link Channel}>
variants.prices.customerGroup *{@link Reference}<{@link CustomerGroup}>{@link Reference}<{@link CustomerGroup}> (with key replaced with id field)
variants.prices.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
variants.assets.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
variants.attributes on {@link List}<{@link Attribute} *{@link Reference}<{@link ProductType}> (example for ProductType){@link Reference}<{@link ProductType}> (with key replaced with id field)
taxCategory{@link Reference}<{@link TaxCategory}>{@link ResourceIdentifier}<{@link TaxCategory}>
state *{@link Reference}<{@link State}>{@link ResourceIdentifier}<{@link State}>
- * - *

Note: The aforementioned references should be expanded with a key. - * Any reference that is not expanded will have its id in place and not replaced by the key will be - * considered as existing resources on the target commercetools project and - * the library will issues an update/create API request without reference resolution. - * - * @param products the products with expanded references. - * @return a {@link List} of {@link ProductDraft} built from the - * supplied {@link List} of {@link Product}. - */ - @Nonnull - public static List mapToProductDrafts(@Nonnull final List products) { - return products - .stream() - .filter(Objects::nonNull) - .map(product -> { - final ProductDraft productDraft = getDraftBuilderFromStagedProduct(product).build(); + /** + * Returns an {@link List}<{@link ProductDraft}> consisting of the results of applying the + * mapping from {@link Product} to {@link ProductDraft} with considering reference resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
productType{@link Reference}<{@link ProductType}>{@link ResourceIdentifier}<{@link ProductType}>
categories{@link Set}<{@link Reference}<{@link Category}>>{@link Set}<{@link ResourceIdentifier}<{@link Category}>>
variants.prices.channel{@link Reference}<{@link Channel}>{@link ResourceIdentifier}<{@link Channel}>
variants.prices.customerGroup *{@link Reference}<{@link CustomerGroup}>{@link Reference}<{@link CustomerGroup}> (with key replaced with id field)
variants.prices.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
variants.assets.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
variants.attributes on {@link List}<{@link Attribute} *{@link Reference}<{@link ProductType}> (example for ProductType){@link Reference}<{@link ProductType}> (with key replaced with id field)
taxCategory{@link Reference}<{@link TaxCategory}>{@link ResourceIdentifier}<{@link TaxCategory}>
state *{@link Reference}<{@link State}>{@link ResourceIdentifier}<{@link State}>
+ * + *

Note: The aforementioned references should be expanded with a key. Any reference that + * is not expanded will have its id in place and not replaced by the key will be considered as + * existing resources on the target commercetools project and the library will issues an + * update/create API request without reference resolution. + * + * @param products the products with expanded references. + * @return a {@link List} of {@link ProductDraft} built from the supplied {@link List} of {@link + * Product}. + */ + @Nonnull + public static List mapToProductDrafts(@Nonnull final List products) { + return products.stream() + .filter(Objects::nonNull) + .map( + product -> { + final ProductDraft productDraft = getDraftBuilderFromStagedProduct(product).build(); - final CategoryReferencePair categoryReferencePair = mapToCategoryReferencePair(product); - final Set> categoryResourceIdentifiers = - categoryReferencePair.getCategoryResourceIdentifiers(); - final CategoryOrderHints categoryOrderHintsWithKeys = categoryReferencePair.getCategoryOrderHints(); + final CategoryReferencePair categoryReferencePair = + mapToCategoryReferencePair(product); + final Set> categoryResourceIdentifiers = + categoryReferencePair.getCategoryResourceIdentifiers(); + final CategoryOrderHints categoryOrderHintsWithKeys = + categoryReferencePair.getCategoryOrderHints(); - final List allVariants = product.getMasterData().getStaged().getAllVariants(); - final List variantDraftsWithKeys = - VariantReferenceResolutionUtils.mapToProductVariantDrafts(allVariants); - final ProductVariantDraft masterVariantDraftWithKeys = variantDraftsWithKeys.remove(0); + final List allVariants = + product.getMasterData().getStaged().getAllVariants(); + final List variantDraftsWithKeys = + VariantReferenceResolutionUtils.mapToProductVariantDrafts(allVariants); + final ProductVariantDraft masterVariantDraftWithKeys = + variantDraftsWithKeys.remove(0); - return ProductDraftBuilder.of(productDraft) - .masterVariant(masterVariantDraftWithKeys) - .variants(variantDraftsWithKeys) - .productType(getResourceIdentifierWithKey(product.getProductType())) - .categories(categoryResourceIdentifiers) - .categoryOrderHints(categoryOrderHintsWithKeys) - .taxCategory(getResourceIdentifierWithKey(product.getTaxCategory())) - .state(getResourceIdentifierWithKey(product.getState())) - .build(); + return ProductDraftBuilder.of(productDraft) + .masterVariant(masterVariantDraftWithKeys) + .variants(variantDraftsWithKeys) + .productType(getResourceIdentifierWithKey(product.getProductType())) + .categories(categoryResourceIdentifiers) + .categoryOrderHints(categoryOrderHintsWithKeys) + .taxCategory(getResourceIdentifierWithKey(product.getTaxCategory())) + .state(getResourceIdentifierWithKey(product.getState())) + .build(); }) - .collect(Collectors.toList()); - } + .collect(Collectors.toList()); + } - /** - * Given a {@link Product} this method creates a {@link ProductDraftBuilder} based on the staged projection - * values of the supplied product. - * - * @param product the product to create a {@link ProductDraftBuilder} based on it's staged data. - * @return a {@link ProductDraftBuilder} based on the staged projection values of the supplied product. - */ - @Nonnull - public static ProductDraftBuilder getDraftBuilderFromStagedProduct(@Nonnull final Product product) { - final ProductData productData = product.getMasterData().getStaged(); - final List allVariants = productData - .getAllVariants().stream() + /** + * Given a {@link Product} this method creates a {@link ProductDraftBuilder} based on the staged + * projection values of the supplied product. + * + * @param product the product to create a {@link ProductDraftBuilder} based on it's staged data. + * @return a {@link ProductDraftBuilder} based on the staged projection values of the supplied + * product. + */ + @Nonnull + public static ProductDraftBuilder getDraftBuilderFromStagedProduct( + @Nonnull final Product product) { + final ProductData productData = product.getMasterData().getStaged(); + final List allVariants = + productData.getAllVariants().stream() .map(productVariant -> ProductVariantDraftBuilder.of(productVariant).build()) .collect(toList()); - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder - .of(product.getMasterData().getStaged().getMasterVariant()).build(); + final ProductVariantDraft masterVariant = + ProductVariantDraftBuilder.of(product.getMasterData().getStaged().getMasterVariant()) + .build(); - return ProductDraftBuilder - .of(product.getProductType(), productData.getName(), productData.getSlug(), allVariants) - .masterVariant(masterVariant) - .metaDescription(productData.getMetaDescription()) - .metaKeywords(productData.getMetaKeywords()) - .metaTitle(productData.getMetaTitle()) - .description(productData.getDescription()) - .searchKeywords(productData.getSearchKeywords()) - .taxCategory(product.getTaxCategory()) - .state(product.getState()) - .key(product.getKey()) - .publish(product.getMasterData().isPublished()) - .categories(new ArrayList<>(productData.getCategories())) - .categoryOrderHints(productData.getCategoryOrderHints()); - } + return ProductDraftBuilder.of( + product.getProductType(), productData.getName(), productData.getSlug(), allVariants) + .masterVariant(masterVariant) + .metaDescription(productData.getMetaDescription()) + .metaKeywords(productData.getMetaKeywords()) + .metaTitle(productData.getMetaTitle()) + .description(productData.getDescription()) + .searchKeywords(productData.getSearchKeywords()) + .taxCategory(product.getTaxCategory()) + .state(product.getState()) + .key(product.getKey()) + .publish(product.getMasterData().isPublished()) + .categories(new ArrayList<>(productData.getCategories())) + .categoryOrderHints(productData.getCategoryOrderHints()); + } - @Nonnull - static CategoryReferencePair mapToCategoryReferencePair(@Nonnull final Product product) { - final Set> categoryReferences = product.getMasterData().getStaged().getCategories(); - final Set> categoryResourceIdentifiers = new HashSet<>(); + @Nonnull + static CategoryReferencePair mapToCategoryReferencePair(@Nonnull final Product product) { + final Set> categoryReferences = + product.getMasterData().getStaged().getCategories(); + final Set> categoryResourceIdentifiers = new HashSet<>(); - final CategoryOrderHints categoryOrderHints = product.getMasterData().getStaged().getCategoryOrderHints(); - final Map categoryOrderHintsMapWithKeys = new HashMap<>(); + final CategoryOrderHints categoryOrderHints = + product.getMasterData().getStaged().getCategoryOrderHints(); + final Map categoryOrderHintsMapWithKeys = new HashMap<>(); - categoryReferences.forEach(categoryReference -> { - if (categoryReference != null) { - if (categoryReference.getObj() != null) { - final String categoryId = categoryReference.getId(); - final String categoryKey = categoryReference.getObj().getKey(); + categoryReferences.forEach( + categoryReference -> { + if (categoryReference != null) { + if (categoryReference.getObj() != null) { + final String categoryId = categoryReference.getId(); + final String categoryKey = categoryReference.getObj().getKey(); - if (categoryOrderHints != null) { - final String categoryOrderHintValue = categoryOrderHints.get(categoryId); - if (categoryOrderHintValue != null) { - categoryOrderHintsMapWithKeys.put(categoryKey, categoryOrderHintValue); - } - } - categoryResourceIdentifiers.add(ResourceIdentifier.ofKey(categoryKey)); - } else { - categoryResourceIdentifiers.add(ResourceIdentifier.ofId(categoryReference.getId())); + if (categoryOrderHints != null) { + final String categoryOrderHintValue = categoryOrderHints.get(categoryId); + if (categoryOrderHintValue != null) { + categoryOrderHintsMapWithKeys.put(categoryKey, categoryOrderHintValue); } + } + categoryResourceIdentifiers.add(ResourceIdentifier.ofKey(categoryKey)); + } else { + categoryResourceIdentifiers.add(ResourceIdentifier.ofId(categoryReference.getId())); } + } }); - final CategoryOrderHints categoryOrderHintsWithKeys = categoryOrderHintsMapWithKeys.isEmpty() - ? categoryOrderHints : CategoryOrderHints.of(categoryOrderHintsMapWithKeys); - return CategoryReferencePair.of(categoryResourceIdentifiers, categoryOrderHintsWithKeys); - } + final CategoryOrderHints categoryOrderHintsWithKeys = + categoryOrderHintsMapWithKeys.isEmpty() + ? categoryOrderHints + : CategoryOrderHints.of(categoryOrderHintsMapWithKeys); + return CategoryReferencePair.of(categoryResourceIdentifiers, categoryOrderHintsWithKeys); + } - /** - * Builds a {@link ProductQuery} for fetching products from a source CTP project with all the needed references - * expanded for the sync: - *

    - *
  • Product Type
  • - *
  • Tax Category
  • - *
  • Product State
  • - *
  • Staged Assets' Custom Types
  • - *
  • Staged Product Categories
  • - *
  • Staged Prices' Channels
  • - *
  • Staged Prices' Customer Groups
  • - *
  • Staged Prices' Custom Types
  • - *
  • Reference Attributes
  • - *
  • Reference Set Attributes
  • - *
- * - *

Note: Please only use this util if you desire to sync all the aforementioned references from - * a source commercetools project. Otherwise, it is more efficient to build the query without expansions, if they - * are not needed, to avoid unnecessarily bigger payloads fetched from the source project. - * - * @return the query for fetching products from the source CTP project with all the aforementioned references - * expanded. - */ - @Nonnull - public static ProductQuery buildProductQuery() { - return ProductQuery.of() - .withLimit(QueryExecutionUtils.DEFAULT_PAGE_SIZE) - .withExpansionPaths(ProductExpansionModel::productType) - .plusExpansionPaths(ProductExpansionModel::taxCategory) - .plusExpansionPaths(ExpansionPath.of("state")) - .plusExpansionPaths(expansionModel -> - expansionModel.masterData().staged().categories()) - .plusExpansionPaths(expansionModel -> - expansionModel.masterData().staged().allVariants().prices().channel()) - .plusExpansionPaths(expansionModel -> - expansionModel.masterData().staged().allVariants().prices().customerGroup()) - .plusExpansionPaths( - ExpansionPath.of("masterData.staged.masterVariant.prices[*].custom.type")) - .plusExpansionPaths( - ExpansionPath.of("masterData.staged.variants[*].prices[*].custom.type")) - .plusExpansionPaths(expansionModel -> - expansionModel.masterData().staged().allVariants().attributes().value()) - .plusExpansionPaths(expansionModel -> - expansionModel.masterData().staged().allVariants().attributes().valueSet()) - .plusExpansionPaths( - ExpansionPath.of("masterData.staged.masterVariant.assets[*].custom.type")) - .plusExpansionPaths( - ExpansionPath.of("masterData.staged.variants[*].assets[*].custom.type")); - } + /** + * Builds a {@link ProductQuery} for fetching products from a source CTP project with all the + * needed references expanded for the sync: + * + *

    + *
  • Product Type + *
  • Tax Category + *
  • Product State + *
  • Staged Assets' Custom Types + *
  • Staged Product Categories + *
  • Staged Prices' Channels + *
  • Staged Prices' Customer Groups + *
  • Staged Prices' Custom Types + *
  • Reference Attributes + *
  • Reference Set Attributes + *
+ * + *

Note: Please only use this util if you desire to sync all the aforementioned references from + * a source commercetools project. Otherwise, it is more efficient to build the query without + * expansions, if they are not needed, to avoid unnecessarily bigger payloads fetched from the + * source project. + * + * @return the query for fetching products from the source CTP project with all the aforementioned + * references expanded. + */ + @Nonnull + public static ProductQuery buildProductQuery() { + return ProductQuery.of() + .withLimit(QueryExecutionUtils.DEFAULT_PAGE_SIZE) + .withExpansionPaths(ProductExpansionModel::productType) + .plusExpansionPaths(ProductExpansionModel::taxCategory) + .plusExpansionPaths(ExpansionPath.of("state")) + .plusExpansionPaths(expansionModel -> expansionModel.masterData().staged().categories()) + .plusExpansionPaths( + expansionModel -> expansionModel.masterData().staged().allVariants().prices().channel()) + .plusExpansionPaths( + expansionModel -> + expansionModel.masterData().staged().allVariants().prices().customerGroup()) + .plusExpansionPaths( + ExpansionPath.of("masterData.staged.masterVariant.prices[*].custom.type")) + .plusExpansionPaths(ExpansionPath.of("masterData.staged.variants[*].prices[*].custom.type")) + .plusExpansionPaths( + expansionModel -> + expansionModel.masterData().staged().allVariants().attributes().value()) + .plusExpansionPaths( + expansionModel -> + expansionModel.masterData().staged().allVariants().attributes().valueSet()) + .plusExpansionPaths( + ExpansionPath.of("masterData.staged.masterVariant.assets[*].custom.type")) + .plusExpansionPaths( + ExpansionPath.of("masterData.staged.variants[*].assets[*].custom.type")); + } - private ProductReferenceResolutionUtils() { - } + private ProductReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/products/utils/ProductSyncUtils.java b/src/main/java/com/commercetools/sync/products/utils/ProductSyncUtils.java index 766d653f40..35a9553aac 100644 --- a/src/main/java/com/commercetools/sync/products/utils/ProductSyncUtils.java +++ b/src/main/java/com/commercetools/sync/products/utils/ProductSyncUtils.java @@ -1,21 +1,5 @@ package com.commercetools.sync.products.utils; -import com.commercetools.sync.products.ActionGroup; -import com.commercetools.sync.products.AttributeMetaData; -import com.commercetools.sync.products.ProductSyncOptions; -import com.commercetools.sync.products.SyncFilter; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.products.Product; -import io.sphere.sdk.products.ProductDraft; -import io.sphere.sdk.products.commands.updateactions.RemoveVariant; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; -import java.util.stream.Collectors; - import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildActionIfPassesFilter; @@ -35,171 +19,203 @@ import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildTransitionStateUpdateAction; import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildVariantsUpdateActions; -public final class ProductSyncUtils { +import com.commercetools.sync.products.ActionGroup; +import com.commercetools.sync.products.AttributeMetaData; +import com.commercetools.sync.products.ProductSyncOptions; +import com.commercetools.sync.products.SyncFilter; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.products.Product; +import io.sphere.sdk.products.ProductDraft; +import io.sphere.sdk.products.commands.updateactions.RemoveVariant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; - private static final String REMOVE_VARIANT_ACTION_NAME = "removeVariant"; - private static final String SET_ATTRIBUTE_IN_ALL_VARIANTS_ACTION_NAME = "setAttributeInAllVariants"; - private static final String ADD_VARIANT_ACTION_NAME = "addVariant"; - private static final String CHANGE_MASTER_VARIANT_ACTION_NAME = "changeMasterVariant"; - - /** - * Compares all the fields (including the variants see - * {@link ProductUpdateActionUtils#buildVariantsUpdateActions(Product, ProductDraft, ProductSyncOptions, Map)}) - * of a {@link Product} and a {@link ProductDraft}, given that each of these fields pass the - * specified {@link SyncFilter}. It returns a {@link List} of {@link UpdateAction}<{@link Product}> as a - * result. If no update action is needed, for example in case where both the {@link Product} and the - * {@link ProductDraft} have the same names, an empty {@link List} is returned. Then it applies a specified filter - * function in the {@link ProductSyncOptions} instance on the resultant list and returns this result. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new data. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied by - * the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link ProductSyncOptions} - * for more info). - * @param attributesMetaData a map of attribute name -> {@link AttributeMetaData}; which defines each attribute's - * information: its name and whether it has the constraint "SameForAll" or not. - * @return A list of product-specific update actions. - */ - @Nonnull - public static List> buildActions(@Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final ProductSyncOptions syncOptions, - @Nonnull final Map - attributesMetaData) { - - final SyncFilter syncFilter = syncOptions.getSyncFilter(); - - final List> updateActions = new ArrayList<>( +public final class ProductSyncUtils { + private static final String REMOVE_VARIANT_ACTION_NAME = "removeVariant"; + private static final String SET_ATTRIBUTE_IN_ALL_VARIANTS_ACTION_NAME = + "setAttributeInAllVariants"; + private static final String ADD_VARIANT_ACTION_NAME = "addVariant"; + private static final String CHANGE_MASTER_VARIANT_ACTION_NAME = "changeMasterVariant"; + + /** + * Compares all the fields (including the variants see {@link + * ProductUpdateActionUtils#buildVariantsUpdateActions(Product, ProductDraft, ProductSyncOptions, + * Map)}) of a {@link Product} and a {@link ProductDraft}, given that each of these fields pass + * the specified {@link SyncFilter}. It returns a {@link List} of {@link UpdateAction}<{@link + * Product}> as a result. If no update action is needed, for example in case where both the + * {@link Product} and the {@link ProductDraft} have the same names, an empty {@link List} is + * returned. Then it applies a specified filter function in the {@link ProductSyncOptions} + * instance on the resultant list and returns this result. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new data. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * ProductSyncOptions} for more info). + * @param attributesMetaData a map of attribute name -> {@link AttributeMetaData}; which + * defines each attribute's information: its name and whether it has the constraint + * "SameForAll" or not. + * @return A list of product-specific update actions. + */ + @Nonnull + public static List> buildActions( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final ProductSyncOptions syncOptions, + @Nonnull final Map attributesMetaData) { + + final SyncFilter syncFilter = syncOptions.getSyncFilter(); + + final List> updateActions = + new ArrayList<>( filterEmptyOptionals( - - buildActionIfPassesFilter(syncFilter, ActionGroup.NAME, () -> - buildChangeNameUpdateAction(oldProduct, newProduct)), - - buildActionIfPassesFilter(syncFilter, ActionGroup.DESCRIPTION, () -> - buildSetDescriptionUpdateAction(oldProduct, newProduct)), - - buildActionIfPassesFilter(syncFilter, ActionGroup.SLUG, () -> - buildChangeSlugUpdateAction(oldProduct, newProduct)), - - buildActionIfPassesFilter(syncFilter, ActionGroup.SEARCHKEYWORDS, () -> - buildSetSearchKeywordsUpdateAction(oldProduct, newProduct)), - - buildActionIfPassesFilter(syncFilter, ActionGroup.METATITLE, () -> - buildSetMetaTitleUpdateAction(oldProduct, newProduct)), - - buildActionIfPassesFilter(syncFilter, ActionGroup.METADESCRIPTION, () -> - buildSetMetaDescriptionUpdateAction(oldProduct, newProduct)), - - buildActionIfPassesFilter(syncFilter, ActionGroup.METAKEYWORDS, () -> - buildSetMetaKeywordsUpdateAction(oldProduct, newProduct)), - - buildActionIfPassesFilter(syncFilter, ActionGroup.TAXCATEGORY, () -> - buildSetTaxCategoryUpdateAction(oldProduct, newProduct) - .map(action -> (UpdateAction) action)), - - buildActionIfPassesFilter(syncFilter, ActionGroup.STATE, () -> - buildTransitionStateUpdateAction(oldProduct, newProduct) - .map(action -> (UpdateAction) action)) - )); - - final List> productCategoryUpdateActions = - buildActionsIfPassesFilter(syncFilter, ActionGroup.CATEGORIES, () -> - buildCategoryActions(oldProduct, newProduct)); - updateActions.addAll(productCategoryUpdateActions); - - updateActions.addAll(buildVariantsUpdateActions(oldProduct, newProduct, syncOptions, attributesMetaData)); - - // lastly publish/unpublish product - final boolean hasNewUpdateActions = updateActions.size() > 0; - buildPublishOrUnpublishUpdateAction(oldProduct, newProduct, hasNewUpdateActions).ifPresent(updateActions::add); - - return prioritizeUpdateActions(updateActions, - oldProduct.getMasterData().getStaged().getMasterVariant().getId()); - } - - /** - * - * @param updateActions All generated update actions for all variants - * @param oldMasterVariantId The masterVariant of the old product fetched from Target - * @return An ordered list of UpdateActions as follows: - * 1 removeVariant actions except master - * 2 sameForAll Actions before executing addVariant to avoid possible errors - * 3 addVariant actions - * 4 changeMasterVariant if any - * 5 removeVariant for old master - * 6 the rest comes in - */ - private static List> prioritizeUpdateActions( - final List> updateActions, final Integer oldMasterVariantId) { - - final RemoveVariant removeMasterVariantUpdateAction = RemoveVariant.ofVariantId(oldMasterVariantId); - - final List> removeVariantUpdateActionsNoMaster = - getActionsByActionName(updateActions, action -> action.getAction().equals(REMOVE_VARIANT_ACTION_NAME) - && !action.equals(removeMasterVariantUpdateAction)); - - final List> sameForAllUpdateActions = - getActionsByActionName(updateActions, action -> - action.getAction().equals(SET_ATTRIBUTE_IN_ALL_VARIANTS_ACTION_NAME)); - - final List> addVariantUpdateActions = - getActionsByActionName(updateActions, action -> - action.getAction().equals(ADD_VARIANT_ACTION_NAME)); - - final List> changeMasterUpdateActions = - getActionsByActionName(updateActions, action -> - action.getAction().equals(CHANGE_MASTER_VARIANT_ACTION_NAME)); - - final List> removeOldMasterVariantUpdateAction = - getActionsByActionName(updateActions, action -> - action.getAction().equals(REMOVE_VARIANT_ACTION_NAME) - && action.equals(removeMasterVariantUpdateAction)); - - final List> updateActionList = new ArrayList<>(removeVariantUpdateActionsNoMaster); - updateActionList.addAll(sameForAllUpdateActions); - updateActionList.addAll(addVariantUpdateActions); - updateActionList.addAll(changeMasterUpdateActions); - updateActionList.addAll(removeOldMasterVariantUpdateAction); - updateActionList.addAll(updateActions); - - return updateActionList; - } - - private static List> getActionsByActionName( - final List> updateActions, - final Predicate> updateActionPredicate) { - - final List> filteredUpdateActions = emptyIfNull(updateActions) - .stream() - .filter(updateActionPredicate) - .collect(Collectors.toList()); - updateActions.removeAll(filteredUpdateActions); - - return filteredUpdateActions; - } - - /** - * Compares the categories of a {@link Product} and a {@link ProductDraft}. It returns a {@link List} of - * {@link UpdateAction}<{@link Product}> as a result. If no update action is needed, for example in - * case where both the {@link Product} and the {@link ProductDraft} have the identical categories, an empty - * {@link List} is returned. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new data. - * @return A list of product category-related update actions. - */ - @Nonnull - public static List> buildCategoryActions(@Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final List> updateActions = new ArrayList<>(); - updateActions.addAll(buildAddToCategoryUpdateActions(oldProduct, newProduct)); - updateActions.addAll(buildSetCategoryOrderHintUpdateActions(oldProduct, newProduct)); - updateActions.addAll(buildRemoveFromCategoryUpdateActions(oldProduct, newProduct)); - return updateActions; - } - - private ProductSyncUtils() { - } + buildActionIfPassesFilter( + syncFilter, + ActionGroup.NAME, + () -> buildChangeNameUpdateAction(oldProduct, newProduct)), + buildActionIfPassesFilter( + syncFilter, + ActionGroup.DESCRIPTION, + () -> buildSetDescriptionUpdateAction(oldProduct, newProduct)), + buildActionIfPassesFilter( + syncFilter, + ActionGroup.SLUG, + () -> buildChangeSlugUpdateAction(oldProduct, newProduct)), + buildActionIfPassesFilter( + syncFilter, + ActionGroup.SEARCHKEYWORDS, + () -> buildSetSearchKeywordsUpdateAction(oldProduct, newProduct)), + buildActionIfPassesFilter( + syncFilter, + ActionGroup.METATITLE, + () -> buildSetMetaTitleUpdateAction(oldProduct, newProduct)), + buildActionIfPassesFilter( + syncFilter, + ActionGroup.METADESCRIPTION, + () -> buildSetMetaDescriptionUpdateAction(oldProduct, newProduct)), + buildActionIfPassesFilter( + syncFilter, + ActionGroup.METAKEYWORDS, + () -> buildSetMetaKeywordsUpdateAction(oldProduct, newProduct)), + buildActionIfPassesFilter( + syncFilter, + ActionGroup.TAXCATEGORY, + () -> + buildSetTaxCategoryUpdateAction(oldProduct, newProduct) + .map(action -> (UpdateAction) action)), + buildActionIfPassesFilter( + syncFilter, + ActionGroup.STATE, + () -> + buildTransitionStateUpdateAction(oldProduct, newProduct) + .map(action -> (UpdateAction) action)))); + + final List> productCategoryUpdateActions = + buildActionsIfPassesFilter( + syncFilter, ActionGroup.CATEGORIES, () -> buildCategoryActions(oldProduct, newProduct)); + updateActions.addAll(productCategoryUpdateActions); + + updateActions.addAll( + buildVariantsUpdateActions(oldProduct, newProduct, syncOptions, attributesMetaData)); + + // lastly publish/unpublish product + final boolean hasNewUpdateActions = updateActions.size() > 0; + buildPublishOrUnpublishUpdateAction(oldProduct, newProduct, hasNewUpdateActions) + .ifPresent(updateActions::add); + + return prioritizeUpdateActions( + updateActions, oldProduct.getMasterData().getStaged().getMasterVariant().getId()); + } + + /** + * @param updateActions All generated update actions for all variants + * @param oldMasterVariantId The masterVariant of the old product fetched from Target + * @return An ordered list of UpdateActions as follows: 1 removeVariant actions except master 2 + * sameForAll Actions before executing addVariant to avoid possible errors 3 addVariant + * actions 4 changeMasterVariant if any 5 removeVariant for old master 6 the rest comes in + */ + private static List> prioritizeUpdateActions( + final List> updateActions, final Integer oldMasterVariantId) { + + final RemoveVariant removeMasterVariantUpdateAction = + RemoveVariant.ofVariantId(oldMasterVariantId); + + final List> removeVariantUpdateActionsNoMaster = + getActionsByActionName( + updateActions, + action -> + action.getAction().equals(REMOVE_VARIANT_ACTION_NAME) + && !action.equals(removeMasterVariantUpdateAction)); + + final List> sameForAllUpdateActions = + getActionsByActionName( + updateActions, + action -> action.getAction().equals(SET_ATTRIBUTE_IN_ALL_VARIANTS_ACTION_NAME)); + + final List> addVariantUpdateActions = + getActionsByActionName( + updateActions, action -> action.getAction().equals(ADD_VARIANT_ACTION_NAME)); + + final List> changeMasterUpdateActions = + getActionsByActionName( + updateActions, action -> action.getAction().equals(CHANGE_MASTER_VARIANT_ACTION_NAME)); + + final List> removeOldMasterVariantUpdateAction = + getActionsByActionName( + updateActions, + action -> + action.getAction().equals(REMOVE_VARIANT_ACTION_NAME) + && action.equals(removeMasterVariantUpdateAction)); + + final List> updateActionList = + new ArrayList<>(removeVariantUpdateActionsNoMaster); + updateActionList.addAll(sameForAllUpdateActions); + updateActionList.addAll(addVariantUpdateActions); + updateActionList.addAll(changeMasterUpdateActions); + updateActionList.addAll(removeOldMasterVariantUpdateAction); + updateActionList.addAll(updateActions); + + return updateActionList; + } + + private static List> getActionsByActionName( + final List> updateActions, + final Predicate> updateActionPredicate) { + + final List> filteredUpdateActions = + emptyIfNull(updateActions).stream() + .filter(updateActionPredicate) + .collect(Collectors.toList()); + updateActions.removeAll(filteredUpdateActions); + + return filteredUpdateActions; + } + + /** + * Compares the categories of a {@link Product} and a {@link ProductDraft}. It returns a {@link + * List} of {@link UpdateAction}<{@link Product}> as a result. If no update action is + * needed, for example in case where both the {@link Product} and the {@link ProductDraft} have + * the identical categories, an empty {@link List} is returned. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new data. + * @return A list of product category-related update actions. + */ + @Nonnull + public static List> buildCategoryActions( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final List> updateActions = new ArrayList<>(); + updateActions.addAll(buildAddToCategoryUpdateActions(oldProduct, newProduct)); + updateActions.addAll(buildSetCategoryOrderHintUpdateActions(oldProduct, newProduct)); + updateActions.addAll(buildRemoveFromCategoryUpdateActions(oldProduct, newProduct)); + return updateActions; + } + + private ProductSyncUtils() {} } diff --git a/src/main/java/com/commercetools/sync/products/utils/ProductUpdateActionUtils.java b/src/main/java/com/commercetools/sync/products/utils/ProductUpdateActionUtils.java index 8f462f39a0..59de161003 100644 --- a/src/main/java/com/commercetools/sync/products/utils/ProductUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/products/utils/ProductUpdateActionUtils.java @@ -1,5 +1,33 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToMap; +import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; +import static com.commercetools.sync.commons.utils.CollectionUtils.filterCollection; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActions; +import static com.commercetools.sync.commons.utils.FilterUtils.executeSupplierIfPassesFilter; +import static com.commercetools.sync.internals.utils.UnorderedCollectionSyncUtils.buildRemoveUpdateActions; +import static com.commercetools.sync.products.ActionGroup.ASSETS; +import static com.commercetools.sync.products.ActionGroup.ATTRIBUTES; +import static com.commercetools.sync.products.ActionGroup.IMAGES; +import static com.commercetools.sync.products.ActionGroup.PRICES; +import static com.commercetools.sync.products.ActionGroup.SKU; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantAssetsUpdateActions; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantAttributesUpdateActions; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantImagesUpdateActions; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantPricesUpdateActions; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantSkuUpdateAction; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Objects.nonNull; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.apache.commons.lang3.BooleanUtils.toBoolean; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.products.ActionGroup; @@ -39,8 +67,6 @@ import io.sphere.sdk.products.commands.updateactions.Unpublish; import io.sphere.sdk.search.SearchKeywords; import io.sphere.sdk.states.State; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -51,879 +77,964 @@ import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToMap; -import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; -import static com.commercetools.sync.commons.utils.CollectionUtils.filterCollection; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActions; -import static com.commercetools.sync.commons.utils.FilterUtils.executeSupplierIfPassesFilter; -import static com.commercetools.sync.internals.utils.UnorderedCollectionSyncUtils.buildRemoveUpdateActions; -import static com.commercetools.sync.products.ActionGroup.ASSETS; -import static com.commercetools.sync.products.ActionGroup.ATTRIBUTES; -import static com.commercetools.sync.products.ActionGroup.IMAGES; -import static com.commercetools.sync.products.ActionGroup.PRICES; -import static com.commercetools.sync.products.ActionGroup.SKU; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantAssetsUpdateActions; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantAttributesUpdateActions; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantImagesUpdateActions; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantPricesUpdateActions; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantSkuUpdateAction; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Objects.nonNull; -import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.apache.commons.lang3.BooleanUtils.toBoolean; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; public final class ProductUpdateActionUtils { - private static final String BLANK_VARIANT_KEY = "The variant key is blank."; - private static final String NULL_VARIANT = "The variant is null."; - static final String BLANK_OLD_MASTER_VARIANT_KEY = "Old master variant key is blank."; - static final String BLANK_NEW_MASTER_VARIANT_KEY = "New master variant null or has blank key."; - static final String BLANK_NEW_MASTER_VARIANT_SKU = "New master variant has blank SKU."; - - /** - * Compares the {@link LocalizedString} names of a {@link ProductDraft} and a {@link Product}. It returns an - * {@link ChangeName} as a result in an {@link Optional}. If both the {@link Product} and the {@link ProductDraft} - * have the same name, then no update action is needed and hence an empty {@link Optional} is returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new name. - * @return A filled optional with the update action or an empty optional if the names are identical. - */ - @Nonnull - public static Optional> buildChangeNameUpdateAction(@Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final LocalizedString newName = newProduct.getName(); - final LocalizedString oldName = oldProduct.getMasterData().getStaged().getName(); - return buildUpdateAction(oldName, newName, () -> ChangeName.of(newName, true)); - } - - /** - * Compares the {@link LocalizedString} descriptions of a {@link ProductDraft} and a {@link Product}. It returns an - * {@link SetDescription} as a result in an {@link Optional}. If both the {@link Product} and the - * {@link ProductDraft} have the same description, then no update action is needed and hence an empty - * {@link Optional} is returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new description. - * @return A filled optional with the update action or an empty optional if the descriptions are identical. - */ - @Nonnull - public static Optional> buildSetDescriptionUpdateAction( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final LocalizedString newDescription = newProduct.getDescription(); - final LocalizedString oldDescription = oldProduct.getMasterData().getStaged().getDescription(); - return buildUpdateAction(oldDescription, newDescription, () -> SetDescription.of(newDescription, true)); - } - - /** - * Compares the {@link LocalizedString} slugs of a {@link ProductDraft} and a {@link Product}. It returns a - * {@link ChangeSlug} update action as a result in an {@link Optional}. If both the {@link Product} and the - * {@link ProductDraft} have the same slug, then no update action is needed and hence an empty {@link Optional} is - * returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new slug. - * @return A filled optional with the update action or an empty optional if the slugs are identical. - */ - @Nonnull - public static Optional> buildChangeSlugUpdateAction(@Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final LocalizedString newSlug = newProduct.getSlug(); - final LocalizedString oldSlug = oldProduct.getMasterData().getStaged().getSlug(); - return buildUpdateAction(oldSlug, newSlug, () -> ChangeSlug.of(newSlug, true)); - } - - /** - * Compares the {@link Set} of {@link Category} {@link Reference}s of a {@link ProductDraft} and a {@link Product}. - * It returns a {@link List} of {@link AddToCategory} update actions as a result, if the old product - * needs to be added to a category to have the same set of categories as the new product. - * If both the {@link Product} and the {@link ProductDraft} have the same set of categories, then no update actions - * are needed and hence an empty {@link List} is returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new slug. - * @return A list containing the update actions or an empty list if the category sets are identical. - */ - @Nonnull - public static List> buildAddToCategoryUpdateActions(@Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final Set> newCategories = newProduct.getCategories(); - final Set> oldCategories = oldProduct.getMasterData().getStaged().getCategories(); - return buildUpdateActions(oldCategories, newCategories, - () -> { - final List> updateActions = new ArrayList<>(); - final List> newCategoriesResourceIdentifiers = - filterCollection(newCategories, newCategoryReference -> - oldCategories.stream() - .map(Reference::toResourceIdentifier) - .noneMatch(oldResourceIdentifier -> - oldResourceIdentifier.equals(newCategoryReference))) - .collect(toList()); - newCategoriesResourceIdentifiers.forEach(categoryResourceIdentifier -> - updateActions.add(AddToCategory.of(categoryResourceIdentifier, true))); - return updateActions; - }); - } - - /** - * Compares the {@link CategoryOrderHints} of a {@link ProductDraft} and a {@link Product}. It returns a - * {@link SetCategoryOrderHint} update action as a result in an {@link List}. If both the {@link Product} and the - * {@link ProductDraft} have the same categoryOrderHints, then no update actions are needed and hence an empty - * {@link List} is returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new categoryOrderHints. - * @return A list containing the update actions or an empty list if the categoryOrderHints are identical. - */ - @Nonnull - public static List> buildSetCategoryOrderHintUpdateActions( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final CategoryOrderHints newCategoryOrderHints = newProduct.getCategoryOrderHints(); - final CategoryOrderHints oldCategoryOrderHints = oldProduct.getMasterData().getStaged().getCategoryOrderHints(); - return buildUpdateActions(oldCategoryOrderHints, newCategoryOrderHints, () -> { - - final Set newCategoryIds = newProduct.getCategories().stream() - .map(ResourceIdentifier::getId) - .collect(toSet()); - - final List> updateActions = new ArrayList<>(); - - final Map newMap = nonNull(newCategoryOrderHints) ? newCategoryOrderHints - .getAsMap() : emptyMap(); - final Map oldMap = nonNull(oldCategoryOrderHints) ? oldCategoryOrderHints - .getAsMap() : emptyMap(); - - // remove category hints present in old product if they are absent in draft but only if product - // is or will be assigned to given category - oldMap.forEach((categoryId, value) -> { + private static final String BLANK_VARIANT_KEY = "The variant key is blank."; + private static final String NULL_VARIANT = "The variant is null."; + static final String BLANK_OLD_MASTER_VARIANT_KEY = "Old master variant key is blank."; + static final String BLANK_NEW_MASTER_VARIANT_KEY = "New master variant null or has blank key."; + static final String BLANK_NEW_MASTER_VARIANT_SKU = "New master variant has blank SKU."; + + /** + * Compares the {@link LocalizedString} names of a {@link ProductDraft} and a {@link Product}. It + * returns an {@link ChangeName} as a result in an {@link Optional}. If both the {@link Product} + * and the {@link ProductDraft} have the same name, then no update action is needed and hence an + * empty {@link Optional} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new name. + * @return A filled optional with the update action or an empty optional if the names are + * identical. + */ + @Nonnull + public static Optional> buildChangeNameUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final LocalizedString newName = newProduct.getName(); + final LocalizedString oldName = oldProduct.getMasterData().getStaged().getName(); + return buildUpdateAction(oldName, newName, () -> ChangeName.of(newName, true)); + } + + /** + * Compares the {@link LocalizedString} descriptions of a {@link ProductDraft} and a {@link + * Product}. It returns an {@link SetDescription} as a result in an {@link Optional}. If both the + * {@link Product} and the {@link ProductDraft} have the same description, then no update action + * is needed and hence an empty {@link Optional} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new description. + * @return A filled optional with the update action or an empty optional if the descriptions are + * identical. + */ + @Nonnull + public static Optional> buildSetDescriptionUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final LocalizedString newDescription = newProduct.getDescription(); + final LocalizedString oldDescription = oldProduct.getMasterData().getStaged().getDescription(); + return buildUpdateAction( + oldDescription, newDescription, () -> SetDescription.of(newDescription, true)); + } + + /** + * Compares the {@link LocalizedString} slugs of a {@link ProductDraft} and a {@link Product}. It + * returns a {@link ChangeSlug} update action as a result in an {@link Optional}. If both the + * {@link Product} and the {@link ProductDraft} have the same slug, then no update action is + * needed and hence an empty {@link Optional} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new slug. + * @return A filled optional with the update action or an empty optional if the slugs are + * identical. + */ + @Nonnull + public static Optional> buildChangeSlugUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final LocalizedString newSlug = newProduct.getSlug(); + final LocalizedString oldSlug = oldProduct.getMasterData().getStaged().getSlug(); + return buildUpdateAction(oldSlug, newSlug, () -> ChangeSlug.of(newSlug, true)); + } + + /** + * Compares the {@link Set} of {@link Category} {@link Reference}s of a {@link ProductDraft} and a + * {@link Product}. It returns a {@link List} of {@link AddToCategory} update actions as a result, + * if the old product needs to be added to a category to have the same set of categories as the + * new product. If both the {@link Product} and the {@link ProductDraft} have the same set of + * categories, then no update actions are needed and hence an empty {@link List} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new slug. + * @return A list containing the update actions or an empty list if the category sets are + * identical. + */ + @Nonnull + public static List> buildAddToCategoryUpdateActions( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final Set> newCategories = newProduct.getCategories(); + final Set> oldCategories = + oldProduct.getMasterData().getStaged().getCategories(); + return buildUpdateActions( + oldCategories, + newCategories, + () -> { + final List> updateActions = new ArrayList<>(); + final List> newCategoriesResourceIdentifiers = + filterCollection( + newCategories, + newCategoryReference -> + oldCategories.stream() + .map(Reference::toResourceIdentifier) + .noneMatch( + oldResourceIdentifier -> + oldResourceIdentifier.equals(newCategoryReference))) + .collect(toList()); + newCategoriesResourceIdentifiers.forEach( + categoryResourceIdentifier -> + updateActions.add(AddToCategory.of(categoryResourceIdentifier, true))); + return updateActions; + }); + } + + /** + * Compares the {@link CategoryOrderHints} of a {@link ProductDraft} and a {@link Product}. It + * returns a {@link SetCategoryOrderHint} update action as a result in an {@link List}. If both + * the {@link Product} and the {@link ProductDraft} have the same categoryOrderHints, then no + * update actions are needed and hence an empty {@link List} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new categoryOrderHints. + * @return A list containing the update actions or an empty list if the categoryOrderHints are + * identical. + */ + @Nonnull + public static List> buildSetCategoryOrderHintUpdateActions( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final CategoryOrderHints newCategoryOrderHints = newProduct.getCategoryOrderHints(); + final CategoryOrderHints oldCategoryOrderHints = + oldProduct.getMasterData().getStaged().getCategoryOrderHints(); + return buildUpdateActions( + oldCategoryOrderHints, + newCategoryOrderHints, + () -> { + final Set newCategoryIds = + newProduct.getCategories().stream().map(ResourceIdentifier::getId).collect(toSet()); + + final List> updateActions = new ArrayList<>(); + + final Map newMap = + nonNull(newCategoryOrderHints) ? newCategoryOrderHints.getAsMap() : emptyMap(); + final Map oldMap = + nonNull(oldCategoryOrderHints) ? oldCategoryOrderHints.getAsMap() : emptyMap(); + + // remove category hints present in old product if they are absent in draft but only if + // product + // is or will be assigned to given category + oldMap.forEach( + (categoryId, value) -> { if (!newMap.containsKey(categoryId) && newCategoryIds.contains(categoryId)) { - updateActions.add(SetCategoryOrderHint.of(categoryId, null, true)); + updateActions.add(SetCategoryOrderHint.of(categoryId, null, true)); } - }); + }); - // add category hints present in draft if they are absent or changed in old product - newMap.forEach((key, value) -> { + // add category hints present in draft if they are absent or changed in old product + newMap.forEach( + (key, value) -> { if (!oldMap.containsKey(key) || !Objects.equals(oldMap.get(key), value)) { - updateActions.add(SetCategoryOrderHint.of(key, value, true)); + updateActions.add(SetCategoryOrderHint.of(key, value, true)); } - }); + }); - return updateActions; + return updateActions; }); - } - - /** - * Compares the {@link Set} of {@link Category} {@link Reference}s of a {@link ProductDraft} and a {@link Product}. - * It returns a {@link List} of {@link RemoveFromCategory} update actions as a result, if the old product - * needs to be removed from a category to have the same set of categories as the new product. - * If both the {@link Product} and the {@link ProductDraft} have the same set of categories, then no update actions - * are needed and hence an empty {@link List} is returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new slug. - * @return A list containing the update actions or an empty list if the category sets are identical. - */ - @Nonnull - public static List> buildRemoveFromCategoryUpdateActions( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final Set> newCategories = newProduct.getCategories(); - final Set> oldCategories = oldProduct.getMasterData().getStaged().getCategories(); - return buildUpdateActions(oldCategories, newCategories, () -> { - final List> updateActions = new ArrayList<>(); - filterCollection(oldCategories, oldCategoryReference -> - !newCategories.contains(oldCategoryReference.toResourceIdentifier())) - .forEach(categoryReference -> - updateActions.add(RemoveFromCategory.of(categoryReference.toResourceIdentifier(), true)) - ); - return updateActions; + } + + /** + * Compares the {@link Set} of {@link Category} {@link Reference}s of a {@link ProductDraft} and a + * {@link Product}. It returns a {@link List} of {@link RemoveFromCategory} update actions as a + * result, if the old product needs to be removed from a category to have the same set of + * categories as the new product. If both the {@link Product} and the {@link ProductDraft} have + * the same set of categories, then no update actions are needed and hence an empty {@link List} + * is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new slug. + * @return A list containing the update actions or an empty list if the category sets are + * identical. + */ + @Nonnull + public static List> buildRemoveFromCategoryUpdateActions( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final Set> newCategories = newProduct.getCategories(); + final Set> oldCategories = + oldProduct.getMasterData().getStaged().getCategories(); + return buildUpdateActions( + oldCategories, + newCategories, + () -> { + final List> updateActions = new ArrayList<>(); + filterCollection( + oldCategories, + oldCategoryReference -> + !newCategories.contains(oldCategoryReference.toResourceIdentifier())) + .forEach( + categoryReference -> + updateActions.add( + RemoveFromCategory.of(categoryReference.toResourceIdentifier(), true))); + return updateActions; }); + } + + /** + * Compares the {@link SearchKeywords} of a {@link ProductDraft} and a {@link Product}. It returns + * a {@link SetSearchKeywords} update action as a result in an {@link Optional}. If both the + * {@link Product} and the {@link ProductDraft} have the same search keywords, then no update + * action is needed and hence an empty {@link Optional} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new search keywords. + * @return A filled optional with the update action or an empty optional if the search keywords + * are identical. + */ + @Nonnull + public static Optional> buildSetSearchKeywordsUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final SearchKeywords newSearchKeywords = newProduct.getSearchKeywords(); + final SearchKeywords oldSearchKeywords = + oldProduct.getMasterData().getStaged().getSearchKeywords(); + return buildUpdateAction( + oldSearchKeywords, newSearchKeywords, () -> SetSearchKeywords.of(newSearchKeywords, true)); + } + + /** + * Compares the {@link LocalizedString} meta descriptions of a {@link ProductDraft} and a {@link + * Product}. It returns a {@link SetMetaDescription} update action as a result in an {@link + * Optional}. If both the {@link Product} and the {@link ProductDraft} have the same meta + * description, then no update action is needed and hence an empty {@link Optional} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new meta description. + * @return A filled optional with the update action or an empty optional if the meta descriptions + * are identical. + */ + @Nonnull + public static Optional> buildSetMetaDescriptionUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final LocalizedString newMetaDescription = newProduct.getMetaDescription(); + final LocalizedString oldMetaDescription = + oldProduct.getMasterData().getStaged().getMetaDescription(); + return buildUpdateAction( + oldMetaDescription, newMetaDescription, () -> SetMetaDescription.of(newMetaDescription)); + } + + /** + * Compares the {@link LocalizedString} meta keywordss of a {@link ProductDraft} and a {@link + * Product}. It returns a {@link SetMetaKeywords} update action as a result in an {@link + * Optional}. If both the {@link Product} and the {@link ProductDraft} have the same meta + * keywords, then no update action is needed and hence an empty {@link Optional} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new meta keywords. + * @return A filled optional with the update action or an empty optional if the meta keywords are + * identical. + */ + @Nonnull + public static Optional> buildSetMetaKeywordsUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final LocalizedString newMetaKeywords = newProduct.getMetaKeywords(); + final LocalizedString oldMetaKeywords = + oldProduct.getMasterData().getStaged().getMetaKeywords(); + return buildUpdateAction( + oldMetaKeywords, newMetaKeywords, () -> SetMetaKeywords.of(newMetaKeywords)); + } + + /** + * Compares the {@link LocalizedString} meta titles of a {@link ProductDraft} and a {@link + * Product}. It returns a {@link SetMetaTitle} update action as a result in an {@link Optional}. + * If both the {@link Product} and the {@link ProductDraft} have the same meta title, then no + * update action is needed and hence an empty {@link Optional} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new meta title. + * @return A filled optional with the update action or an empty optional if the meta titles are + * identical. + */ + @Nonnull + public static Optional> buildSetMetaTitleUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + final LocalizedString newMetaTitle = newProduct.getMetaTitle(); + final LocalizedString oldMetaTitle = oldProduct.getMasterData().getStaged().getMetaTitle(); + return buildUpdateAction(oldMetaTitle, newMetaTitle, () -> SetMetaTitle.of(newMetaTitle)); + } + + /** + * Compares the variants (including the master variants) of a {@link ProductDraft} and a {@link + * Product}. It returns a {@link List} of variant related update actions. For example: + * + *

    + *
  • {@link AddVariant} + *
  • {@link RemoveVariant} + *
  • {@link ChangeMasterVariant} + *
  • {@link io.sphere.sdk.products.commands.updateactions.SetAttribute} + *
  • {@link io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants} + *
  • {@link io.sphere.sdk.products.commands.updateactions.SetSku} + *
  • {@link io.sphere.sdk.products.commands.updateactions.AddExternalImage} + *
  • {@link io.sphere.sdk.products.commands.updateactions.RemoveImage} + *
  • {@link io.sphere.sdk.products.commands.updateactions.AddPrice} + *
  • ... and more variant level update actions. + *
+ * + * If both the {@link Product} and the {@link ProductDraft} have identical variants, then no + * update actions are needed and hence an empty {@link List} is returned. + * + *

NOTE: Comparison is done against the staged projection of the old product. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new meta title. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * ProductSyncOptions} for more info). + * @param attributesMetaData a map of attribute name -> {@link AttributeMetaData}; which + * defines attribute information: its name and whether it has the constraint "SameForAll" or + * not. + * @return A list of product variant-specific update actions. + */ + @Nonnull + public static List> buildVariantsUpdateActions( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final ProductSyncOptions syncOptions, + @Nonnull final Map attributesMetaData) { + + if (haveInvalidMasterVariants(oldProduct, newProduct, syncOptions)) { + return emptyList(); } - /** - * Compares the {@link SearchKeywords} of a {@link ProductDraft} and a {@link Product}. It returns a - * {@link SetSearchKeywords} update action as a result in an {@link Optional}. If both the {@link Product} and the - * {@link ProductDraft} have the same search keywords, then no update action is needed and hence an empty - * {@link Optional} is returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new search keywords. - * @return A filled optional with the update action or an empty optional if the search keywords are identical. - */ - @Nonnull - public static Optional> buildSetSearchKeywordsUpdateAction( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final SearchKeywords newSearchKeywords = newProduct.getSearchKeywords(); - final SearchKeywords oldSearchKeywords = oldProduct.getMasterData().getStaged().getSearchKeywords(); - return buildUpdateAction(oldSearchKeywords, newSearchKeywords, - () -> SetSearchKeywords.of(newSearchKeywords, true)); - } - - /** - * Compares the {@link LocalizedString} meta descriptions of a {@link ProductDraft} and a {@link Product}. It - * returns a {@link SetMetaDescription} update action as a result in an {@link Optional}. If both the - * {@link Product} and the {@link ProductDraft} have the same meta description, then no update action is needed and - * hence an empty {@link Optional} is returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new meta description. - * @return A filled optional with the update action or an empty optional if the meta descriptions are identical. - */ - @Nonnull - public static Optional> buildSetMetaDescriptionUpdateAction( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final LocalizedString newMetaDescription = newProduct.getMetaDescription(); - final LocalizedString oldMetaDescription = oldProduct.getMasterData().getStaged().getMetaDescription(); - return buildUpdateAction(oldMetaDescription, newMetaDescription, - () -> SetMetaDescription.of(newMetaDescription)); - } - - /** - * Compares the {@link LocalizedString} meta keywordss of a {@link ProductDraft} and a {@link Product}. It returns - * a {@link SetMetaKeywords} update action as a result in an {@link Optional}. If both the {@link Product} and the - * {@link ProductDraft} have the same meta keywords, then no update action is needed and hence an empty - * {@link Optional} is returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new meta keywords. - * @return A filled optional with the update action or an empty optional if the meta keywords are identical. - */ - @Nonnull - public static Optional> buildSetMetaKeywordsUpdateAction( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final LocalizedString newMetaKeywords = newProduct.getMetaKeywords(); - final LocalizedString oldMetaKeywords = oldProduct.getMasterData().getStaged().getMetaKeywords(); - return buildUpdateAction(oldMetaKeywords, newMetaKeywords, () -> SetMetaKeywords.of(newMetaKeywords)); - } - - /** - * Compares the {@link LocalizedString} meta titles of a {@link ProductDraft} and a {@link Product}. It returns a - * {@link SetMetaTitle} update action as a result in an {@link Optional}. If both the {@link Product} and the - * {@link ProductDraft} have the same meta title, then no update action is needed and hence an empty - * {@link Optional} is returned. - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new meta title. - * @return A filled optional with the update action or an empty optional if the meta titles are identical. - */ - @Nonnull - public static Optional> buildSetMetaTitleUpdateAction( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - final LocalizedString newMetaTitle = newProduct.getMetaTitle(); - final LocalizedString oldMetaTitle = oldProduct.getMasterData().getStaged().getMetaTitle(); - return buildUpdateAction(oldMetaTitle, newMetaTitle, () -> SetMetaTitle.of(newMetaTitle)); - } - - /** - * Compares the variants (including the master variants) of a {@link ProductDraft} and a {@link Product}. It returns - * a {@link List} of variant related update actions. For example: - * - *

    - *
  • {@link AddVariant}
  • - *
  • {@link RemoveVariant}
  • - *
  • {@link ChangeMasterVariant}
  • - *
  • {@link io.sphere.sdk.products.commands.updateactions.SetAttribute}
  • - *
  • {@link io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants}
  • - *
  • {@link io.sphere.sdk.products.commands.updateactions.SetSku}
  • - *
  • {@link io.sphere.sdk.products.commands.updateactions.AddExternalImage}
  • - *
  • {@link io.sphere.sdk.products.commands.updateactions.RemoveImage}
  • - *
  • {@link io.sphere.sdk.products.commands.updateactions.AddPrice}
  • - *
  • ... and more variant level update actions.
  • - *
- * If both the {@link Product} and the {@link ProductDraft} have identical variants, then no update actions are - * needed and hence an empty {@link List} is returned. - * - * - *

NOTE: Comparison is done against the staged projection of the old product. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new meta title. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied by - * the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link ProductSyncOptions} - * for more info). - * @param attributesMetaData a map of attribute name -> {@link AttributeMetaData}; which defines attribute - * information: its name and whether it has the constraint "SameForAll" or not. - * @return A list of product variant-specific update actions. - */ - @Nonnull - public static List> buildVariantsUpdateActions( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final ProductSyncOptions syncOptions, - @Nonnull final Map attributesMetaData) { - - if (haveInvalidMasterVariants(oldProduct, newProduct, syncOptions)) { - return emptyList(); - } - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - - final List oldProductVariantsWithoutMaster = - oldProduct.getMasterData().getStaged().getVariants(); - - final Map oldProductVariantsNoMaster = - collectionToMap(oldProductVariantsWithoutMaster, ProductVariant::getKey); - - final Map oldProductVariantsWithMaster = new HashMap<>(oldProductVariantsNoMaster); - oldProductVariantsWithMaster.put(oldMasterVariant.getKey(), oldMasterVariant); - - // TODO: Should use getAllVariants as soon as https://github.com/commercetools/commercetools-sync-java/issues/13 - // is fixed. - final List newAllProductVariants = new ArrayList<>(newProduct.getVariants()); - newAllProductVariants.add(newProduct.getMasterVariant()); - - // Remove missing variants, but keep master variant (MV can't be removed) - final List> updateActions = - buildRemoveUpdateActions(oldProductVariantsWithoutMaster, newAllProductVariants, ProductVariant::getKey, - ProductVariantDraft::getKey, variant -> RemoveVariant.ofVariantId(variant.getId(), true)); - - emptyIfNull(newAllProductVariants).forEach(newProductVariant -> { - if (newProductVariant == null) { + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + + final List oldProductVariantsWithoutMaster = + oldProduct.getMasterData().getStaged().getVariants(); + + final Map oldProductVariantsNoMaster = + collectionToMap(oldProductVariantsWithoutMaster, ProductVariant::getKey); + + final Map oldProductVariantsWithMaster = + new HashMap<>(oldProductVariantsNoMaster); + oldProductVariantsWithMaster.put(oldMasterVariant.getKey(), oldMasterVariant); + + // TODO: Should use getAllVariants as soon as + // https://github.com/commercetools/commercetools-sync-java/issues/13 + // is fixed. + final List newAllProductVariants = + new ArrayList<>(newProduct.getVariants()); + newAllProductVariants.add(newProduct.getMasterVariant()); + + // Remove missing variants, but keep master variant (MV can't be removed) + final List> updateActions = + buildRemoveUpdateActions( + oldProductVariantsWithoutMaster, + newAllProductVariants, + ProductVariant::getKey, + ProductVariantDraft::getKey, + variant -> RemoveVariant.ofVariantId(variant.getId(), true)); + + emptyIfNull(newAllProductVariants) + .forEach( + newProductVariant -> { + if (newProductVariant == null) { handleBuildVariantsUpdateActionsError(oldProduct, NULL_VARIANT, syncOptions); - } else { + } else { final String newProductVariantKey = newProductVariant.getKey(); if (isBlank(newProductVariantKey)) { - handleBuildVariantsUpdateActionsError(oldProduct, BLANK_VARIANT_KEY, syncOptions); + handleBuildVariantsUpdateActionsError(oldProduct, BLANK_VARIANT_KEY, syncOptions); } else { - final ProductVariant matchingOldVariant = oldProductVariantsWithMaster.get(newProductVariantKey); - final List> updateOrAddVariant = ofNullable(matchingOldVariant) - .map(oldVariant -> collectAllVariantUpdateActions(getSameForAllUpdateActions(updateActions), - oldProduct, newProduct, oldVariant, newProductVariant, attributesMetaData, syncOptions)) - .orElseGet(() -> buildAddVariantUpdateActionFromDraft(newProductVariant)); - updateActions.addAll(updateOrAddVariant); + final ProductVariant matchingOldVariant = + oldProductVariantsWithMaster.get(newProductVariantKey); + final List> updateOrAddVariant = + ofNullable(matchingOldVariant) + .map( + oldVariant -> + collectAllVariantUpdateActions( + getSameForAllUpdateActions(updateActions), + oldProduct, + newProduct, + oldVariant, + newProductVariant, + attributesMetaData, + syncOptions)) + .orElseGet(() -> buildAddVariantUpdateActionFromDraft(newProductVariant)); + updateActions.addAll(updateOrAddVariant); } - } - }); - - updateActions.addAll(buildChangeMasterVariantUpdateAction(oldProduct, newProduct, syncOptions)); - return updateActions; - } - - private static List> getSameForAllUpdateActions( - final List> updateActions) { - return emptyIfNull(updateActions).stream().filter(productUpdateAction -> - productUpdateAction instanceof SetAttributeInAllVariants) - .collect(toList()); - } - - /** - * Returns a list containing all the variants (including the master variant) of the supplied {@link ProductDraft}. - * @param productDraft the product draft that has the variants and master variant that should be returned. - * @return a list containing all the variants (including the master variant) of the supplied {@link ProductDraft}. - */ - @Nonnull - public static List getAllVariants(@Nonnull final ProductDraft productDraft) { - final List allVariants = new ArrayList<>(1 + productDraft.getVariants().size()); - allVariants.add(productDraft.getMasterVariant()); - allVariants.addAll(productDraft.getVariants()); - return allVariants; - } - - private static boolean hasDuplicateSameForAllAction( - final List> sameForAllUpdateActions, - final UpdateAction collectedUpdateAction) { - - return !(collectedUpdateAction instanceof SetAttributeInAllVariants) - || isSameForAllActionNew(sameForAllUpdateActions, collectedUpdateAction); - } - - private static boolean isSameForAllActionNew( - final List> sameForAllUpdateActions, - final UpdateAction productUpdateAction) { - - return sameForAllUpdateActions.stream() - .noneMatch(previouslyAddedAction -> - previouslyAddedAction instanceof SetAttributeInAllVariants - && previouslyAddedAction.getAction().equals(productUpdateAction.getAction())); - } + } + }); - @Nonnull - private static List> collectAllVariantUpdateActions( - @Nonnull final List> sameForAllUpdateActions, - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final ProductVariant oldProductVariant, - @Nonnull final ProductVariantDraft newProductVariant, - @Nonnull final Map attributesMetaData, - @Nonnull final ProductSyncOptions syncOptions) { - - final ArrayList> updateActions = new ArrayList<>(); - final SyncFilter syncFilter = syncOptions.getSyncFilter(); - - updateActions.addAll( - buildActionsIfPassesFilter(syncFilter, ATTRIBUTES, () -> - emptyIfNull(buildProductVariantAttributesUpdateActions(oldProduct, newProduct, oldProductVariant, - newProductVariant, attributesMetaData, syncOptions)).stream() - .filter(collectedUpdateAction -> - hasDuplicateSameForAllAction(sameForAllUpdateActions, collectedUpdateAction)) - .collect(Collectors.toList()))); - - updateActions.addAll( - buildActionsIfPassesFilter(syncFilter, IMAGES, () -> - buildProductVariantImagesUpdateActions(oldProductVariant, newProductVariant))); - - updateActions.addAll( - buildActionsIfPassesFilter(syncFilter, PRICES, () -> - buildProductVariantPricesUpdateActions(oldProduct, newProduct, oldProductVariant, - newProductVariant, syncOptions))); - - updateActions.addAll( - buildActionsIfPassesFilter(syncFilter, ASSETS, () -> - buildProductVariantAssetsUpdateActions(oldProduct, newProduct, oldProductVariant, newProductVariant, - syncOptions))); - - buildActionIfPassesFilter(syncFilter, SKU, () -> - buildProductVariantSkuUpdateAction(oldProductVariant, newProductVariant)) - .ifPresent(updateActions::add); - - return updateActions; + updateActions.addAll(buildChangeMasterVariantUpdateAction(oldProduct, newProduct, syncOptions)); + return updateActions; + } + + private static List> getSameForAllUpdateActions( + final List> updateActions) { + return emptyIfNull(updateActions).stream() + .filter(productUpdateAction -> productUpdateAction instanceof SetAttributeInAllVariants) + .collect(toList()); + } + + /** + * Returns a list containing all the variants (including the master variant) of the supplied + * {@link ProductDraft}. + * + * @param productDraft the product draft that has the variants and master variant that should be + * returned. + * @return a list containing all the variants (including the master variant) of the supplied + * {@link ProductDraft}. + */ + @Nonnull + public static List getAllVariants(@Nonnull final ProductDraft productDraft) { + final List allVariants = + new ArrayList<>(1 + productDraft.getVariants().size()); + allVariants.add(productDraft.getMasterVariant()); + allVariants.addAll(productDraft.getVariants()); + return allVariants; + } + + private static boolean hasDuplicateSameForAllAction( + final List> sameForAllUpdateActions, + final UpdateAction collectedUpdateAction) { + + return !(collectedUpdateAction instanceof SetAttributeInAllVariants) + || isSameForAllActionNew(sameForAllUpdateActions, collectedUpdateAction); + } + + private static boolean isSameForAllActionNew( + final List> sameForAllUpdateActions, + final UpdateAction productUpdateAction) { + + return sameForAllUpdateActions.stream() + .noneMatch( + previouslyAddedAction -> + previouslyAddedAction instanceof SetAttributeInAllVariants + && previouslyAddedAction.getAction().equals(productUpdateAction.getAction())); + } + + @Nonnull + private static List> collectAllVariantUpdateActions( + @Nonnull final List> sameForAllUpdateActions, + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final ProductVariant oldProductVariant, + @Nonnull final ProductVariantDraft newProductVariant, + @Nonnull final Map attributesMetaData, + @Nonnull final ProductSyncOptions syncOptions) { + + final ArrayList> updateActions = new ArrayList<>(); + final SyncFilter syncFilter = syncOptions.getSyncFilter(); + + updateActions.addAll( + buildActionsIfPassesFilter( + syncFilter, + ATTRIBUTES, + () -> + emptyIfNull( + buildProductVariantAttributesUpdateActions( + oldProduct, + newProduct, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions)) + .stream() + .filter( + collectedUpdateAction -> + hasDuplicateSameForAllAction( + sameForAllUpdateActions, collectedUpdateAction)) + .collect(Collectors.toList()))); + + updateActions.addAll( + buildActionsIfPassesFilter( + syncFilter, + IMAGES, + () -> buildProductVariantImagesUpdateActions(oldProductVariant, newProductVariant))); + + updateActions.addAll( + buildActionsIfPassesFilter( + syncFilter, + PRICES, + () -> + buildProductVariantPricesUpdateActions( + oldProduct, newProduct, oldProductVariant, newProductVariant, syncOptions))); + + updateActions.addAll( + buildActionsIfPassesFilter( + syncFilter, + ASSETS, + () -> + buildProductVariantAssetsUpdateActions( + oldProduct, newProduct, oldProductVariant, newProductVariant, syncOptions))); + + buildActionIfPassesFilter( + syncFilter, + SKU, + () -> buildProductVariantSkuUpdateAction(oldProductVariant, newProductVariant)) + .ifPresent(updateActions::add); + + return updateActions; + } + + /** + * Compares the 'published' field of a {@link ProductDraft} and a {@link Product} with the new + * update actions and hasStagedChanges of the old product. Accordingly it returns a {@link + * Publish} or {@link Unpublish} update action as a result in an {@link Optional}. Check the + * calculation table below for all different combinations named as states. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
StateNew draft publishOld product publishNew update actionsOld product hasStagedChangesAction
State 1falsefalsefalsefalse-
State 2falsefalsefalsetrue-
State 3falsefalsetruefalse-
State 4falsefalsetruetrue-
State 5falsetruefalsefalseunpublish
State 6falsetruefalsetrueunpublish
State 7falsetruetruefalseunpublish
State 8falsetruetruetrueunpublish
State 9truefalsefalsefalsepublish
State 10truefalsefalsetruepublish
State 11truefalsetruefalsepublish
State 12truefalsetruetruepublish
State 13truetruefalsefalse-
State 14truetruefalsetruepublish
State 15truetruetruefalsepublish
State 16truetruetruetruepublish
+ * + *

NOTE: Comparison is done against the staged projection of the old product. If the new + * product's 'published' field is null, then the default false value is assumed. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft where we get the new published field value. + * @param hasNewUpdateActions the product draft has other update actions set. + * @return A filled optional with the update action or an empty optional if the flag values are + * identical. + */ + @Nonnull + public static Optional> buildPublishOrUnpublishUpdateAction( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + final boolean hasNewUpdateActions) { + + final boolean isNewProductPublished = toBoolean(newProduct.isPublish()); + final boolean isOldProductPublished = toBoolean(oldProduct.getMasterData().isPublished()); + + if (isNewProductPublished) { + if (isOldProductPublished + && (hasNewUpdateActions || oldProduct.getMasterData().hasStagedChanges())) { + // covers the state 14, state 15 and state 16. + return Optional.of(Publish.of()); + } + return buildUpdateAction(isOldProductPublished, true, Publish::of); } - - /** - * Compares the 'published' field of a {@link ProductDraft} and a {@link Product} with the new update actions - * and hasStagedChanges of the old product. Accordingly it returns a {@link Publish} or {@link Unpublish} - * update action as a result in an {@link Optional}. - * Check the calculation table below for all different combinations named as states. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
StateNew draft publishOld product publishNew update actionsOld product hasStagedChangesAction
State 1falsefalsefalsefalse-
State 2falsefalsefalsetrue-
State 3falsefalsetruefalse-
State 4falsefalsetruetrue-
State 5falsetruefalsefalseunpublish
State 6falsetruefalsetrueunpublish
State 7falsetruetruefalseunpublish
State 8falsetruetruetrueunpublish
State 9truefalsefalsefalsepublish
State 10truefalsefalsetruepublish
State 11truefalsetruefalsepublish
State 12truefalsetruetruepublish
State 13truetruefalsefalse-
State 14truetruefalsetruepublish
State 15truetruetruefalsepublish
State 16truetruetruetruepublish
- * - *

NOTE: Comparison is done against the staged projection of the old product. If the new product's 'published' - * field is null, then the default false value is assumed.

- * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft where we get the new published field value. - * @param hasNewUpdateActions the product draft has other update actions set. - * @return A filled optional with the update action or an empty optional if the flag values are identical. - */ - @Nonnull - public static Optional> buildPublishOrUnpublishUpdateAction( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - final boolean hasNewUpdateActions) { - - final boolean isNewProductPublished = toBoolean(newProduct.isPublish()); - final boolean isOldProductPublished = toBoolean(oldProduct.getMasterData().isPublished()); - - if (isNewProductPublished) { - if (isOldProductPublished && (hasNewUpdateActions || oldProduct.getMasterData().hasStagedChanges())) { - // covers the state 14, state 15 and state 16. - return Optional.of(Publish.of()); - } - return buildUpdateAction(isOldProductPublished, true, Publish::of); - } - return buildUpdateAction(isOldProductPublished, false, Unpublish::of); + return buildUpdateAction(isOldProductPublished, false, Unpublish::of); + } + + /** + * Create update action, if {@code newProduct} has {@code #masterVariant#key} different than + * {@code oldProduct} staged {@code #masterVariant#key}. + * + *

If update action is created - it is created of {@link ProductVariantDraft + * newProduct.getMasterVariant().getSku()} + * + *

If old master variant is missing in the new variants list - add {@link RemoveVariant} action + * at the end. + * + * @param oldProduct old product with variants + * @param newProduct new product draft with variants with resolved references prices + * references + * @param syncOptions the sync options wrapper which contains options related to the sync process + * @return a list of maximum two elements: {@link ChangeMasterVariant} if the keys are different, + * optionally followed by {@link RemoveVariant} if the changed variant does not exist in the + * new variants list. + */ + @Nonnull + public static List> buildChangeMasterVariantUpdateAction( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final ProductSyncOptions syncOptions) { + final String newKey = newProduct.getMasterVariant().getKey(); + final String oldKey = oldProduct.getMasterData().getStaged().getMasterVariant().getKey(); + + if (haveInvalidMasterVariants(oldProduct, newProduct, syncOptions)) { + return emptyList(); } - - /** - * Create update action, if {@code newProduct} has {@code #masterVariant#key} different than {@code oldProduct} - * staged {@code #masterVariant#key}. - * - *

If update action is created - it is created of - * {@link ProductVariantDraft newProduct.getMasterVariant().getSku()} - * - *

If old master variant is missing in the new variants list - add {@link RemoveVariant} action at the end. - * - * @param oldProduct old product with variants - * @param newProduct new product draft with variants with resolved references prices references - * @param syncOptions the sync options wrapper which contains options related to the sync process - * @return a list of maximum two elements: {@link ChangeMasterVariant} if the keys are different, - * optionally followed by {@link RemoveVariant} if the changed variant does not exist in the new variants list. - */ - @Nonnull - public static List> buildChangeMasterVariantUpdateAction( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final ProductSyncOptions syncOptions) { - final String newKey = newProduct.getMasterVariant().getKey(); - final String oldKey = oldProduct.getMasterData().getStaged().getMasterVariant().getKey(); - - if (haveInvalidMasterVariants(oldProduct, newProduct, syncOptions)) { + return buildUpdateActions( + newKey, + oldKey, + // it might be that the new master variant is from new added variants, so CTP variantId is + // not set yet, + // thus we can't use ChangeMasterVariant.ofVariantId(), + // but it could be re-factored as soon as ChangeMasterVariant.ofKey() happens in the SDK + () -> { + final String newSku = newProduct.getMasterVariant().getSku(); + if (isBlank(newSku)) { + handleBuildVariantsUpdateActionsError( + oldProduct, BLANK_NEW_MASTER_VARIANT_SKU, syncOptions); return emptyList(); - } - - return buildUpdateActions(newKey, oldKey, - // it might be that the new master variant is from new added variants, so CTP variantId is not set yet, - // thus we can't use ChangeMasterVariant.ofVariantId(), - // but it could be re-factored as soon as ChangeMasterVariant.ofKey() happens in the SDK - () -> { - - final String newSku = newProduct.getMasterVariant().getSku(); - if (isBlank(newSku)) { - handleBuildVariantsUpdateActionsError(oldProduct, BLANK_NEW_MASTER_VARIANT_SKU, syncOptions); - return emptyList(); - } - - final List> updateActions = new ArrayList<>(2); - updateActions.add(ChangeMasterVariant.ofSku(newSku, true)); - - // verify whether the old master variant should be removed: - // if the new variant list doesn't contain the old master variant key. - // Since we can't remove a variant, if it is master, we have to change MV first, and then remove it - // (if it does not exist in the new variants list). - // We don't need to include new master variant to the iteration stream iteration, - // because this body is called only if newKey != oldKey - if (newProduct.getVariants().stream() - .noneMatch(variant -> Objects.equals(variant.getKey(), oldKey))) { - updateActions.add(RemoveVariant.of(oldProduct.getMasterData().getStaged().getMasterVariant())); - } - return updateActions; - }); - } - - - /** - * Compares the {@link io.sphere.sdk.taxcategories.TaxCategory} references of an old {@link Product} and - * new {@link ProductDraft}. If they are different - return {@link SetTaxCategory} update action. - * - *

If the old value is set, but the new one is empty - the command will unset the tax category. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft with new {@link io.sphere.sdk.taxcategories.TaxCategory} reference. - * @return An optional with {@link SetTaxCategory} update action. - */ - @Nonnull - public static Optional buildSetTaxCategoryUpdateAction( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - return buildUpdateActionForReferences(oldProduct.getTaxCategory(), newProduct.getTaxCategory(), - () -> SetTaxCategory.of(newProduct.getTaxCategory())); - } - - /** - * Compares the {@link State} references of an old {@link Product} and - * new {@link ProductDraft}. If they are different - return {@link TransitionState} update action. - * - *

If the old value is set, but the new one is empty - return empty object, because unset transition state is - * not possible. - * - *

Note: the transition state action is called with force == true, i.e. the platform won't verify - * transition - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft with new {@link State} reference. - * @return An optional with {@link TransitionState} update action. - */ - @Nonnull - public static Optional buildTransitionStateUpdateAction( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct) { - return ofNullable(newProduct.getState() != null && !Objects.equals(oldProduct.getState(), newProduct.getState()) + } + + final List> updateActions = new ArrayList<>(2); + updateActions.add(ChangeMasterVariant.ofSku(newSku, true)); + + // verify whether the old master variant should be removed: + // if the new variant list doesn't contain the old master variant key. + // Since we can't remove a variant, if it is master, we have to change MV first, and then + // remove it + // (if it does not exist in the new variants list). + // We don't need to include new master variant to the iteration stream iteration, + // because this body is called only if newKey != oldKey + if (newProduct.getVariants().stream() + .noneMatch(variant -> Objects.equals(variant.getKey(), oldKey))) { + updateActions.add( + RemoveVariant.of(oldProduct.getMasterData().getStaged().getMasterVariant())); + } + return updateActions; + }); + } + + /** + * Compares the {@link io.sphere.sdk.taxcategories.TaxCategory} references of an old {@link + * Product} and new {@link ProductDraft}. If they are different - return {@link SetTaxCategory} + * update action. + * + *

If the old value is set, but the new one is empty - the command will unset the tax category. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft with new {@link io.sphere.sdk.taxcategories.TaxCategory} + * reference. + * @return An optional with {@link SetTaxCategory} update action. + */ + @Nonnull + public static Optional buildSetTaxCategoryUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + return buildUpdateActionForReferences( + oldProduct.getTaxCategory(), + newProduct.getTaxCategory(), + () -> SetTaxCategory.of(newProduct.getTaxCategory())); + } + + /** + * Compares the {@link State} references of an old {@link Product} and new {@link ProductDraft}. + * If they are different - return {@link TransitionState} update action. + * + *

If the old value is set, but the new one is empty - return empty object, because unset + * transition state is not possible. + * + *

Note: the transition state action is called with force == true, i.e. the + * platform won't verify transition + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft with new {@link State} reference. + * @return An optional with {@link TransitionState} update action. + */ + @Nonnull + public static Optional buildTransitionStateUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final ProductDraft newProduct) { + return ofNullable( + newProduct.getState() != null + && !Objects.equals(oldProduct.getState(), newProduct.getState()) ? TransitionState.of(mapResourceIdentifierToReferencable(newProduct.getState()), true) : null); - } - - - @Nonnull // TODO (JVM-SDK), see: SUPPORT-10336 TransitionState needs to be created with a ResourceIdentifier - private static Referenceable mapResourceIdentifierToReferencable( - @Nonnull final ResourceIdentifier resourceIdentifier) { - return new ResourceImpl(null, null, null, null) { - @Override - public Reference toReference() { - return Reference.of(State.referenceTypeId(), resourceIdentifier.getId()); - } - }; - } - - /** - * Factory method to create {@link AddVariant} action from {@link ProductVariantDraft} instance. If the supplied - * {@link ProductVariantDraft} contains assets, this method will append an {@link AddAsset} action for each - * new asset. - * - *

The {@link AddVariant} will include:

    - *
  • sku
  • - *
  • keys
  • - *
  • attributes
  • - *
  • prices
  • - *
  • images
  • - *
- * - * @param draft {@link ProductVariantDraft} which to add. - * @return a list of actions which contains an {@link AddVariant} update action with properties from {@code draft}, - * and {@link AddAsset} actions for each asset in the new variant. - */ - @Nonnull - static List> buildAddVariantUpdateActionFromDraft(@Nonnull final ProductVariantDraft draft) { - - final ArrayList> actions = new ArrayList<>(); - - final UpdateAction addVariant = AddVariant - .of(draft.getAttributes(), draft.getPrices(), draft.getSku(), true) + } + + @Nonnull // TODO (JVM-SDK), see: SUPPORT-10336 TransitionState needs to be created with a + // ResourceIdentifier + private static Referenceable mapResourceIdentifierToReferencable( + @Nonnull final ResourceIdentifier resourceIdentifier) { + return new ResourceImpl(null, null, null, null) { + @Override + public Reference toReference() { + return Reference.of(State.referenceTypeId(), resourceIdentifier.getId()); + } + }; + } + + /** + * Factory method to create {@link AddVariant} action from {@link ProductVariantDraft} instance. + * If the supplied {@link ProductVariantDraft} contains assets, this method will append an {@link + * AddAsset} action for each new asset. + * + *

The {@link AddVariant} will include: + * + *

    + *
  • sku + *
  • keys + *
  • attributes + *
  • prices + *
  • images + *
+ * + * @param draft {@link ProductVariantDraft} which to add. + * @return a list of actions which contains an {@link AddVariant} update action with properties + * from {@code draft}, and {@link AddAsset} actions for each asset in the new variant. + */ + @Nonnull + static List> buildAddVariantUpdateActionFromDraft( + @Nonnull final ProductVariantDraft draft) { + + final ArrayList> actions = new ArrayList<>(); + + final UpdateAction addVariant = + AddVariant.of(draft.getAttributes(), draft.getPrices(), draft.getSku(), true) .withKey(draft.getKey()) .withImages(draft.getImages()); - actions.add(addVariant); - - ofNullable(draft.getAssets()) - .map(assetDrafts -> assetDrafts.stream() - .map(assetDraft -> (UpdateAction) - AddAsset.ofSku(draft.getSku(), assetDraft).withStaged(true)) - .collect(toList())) - .ifPresent(actions::addAll); - - return actions; - } - - /** - * Util that checks if the supplied {@link ActionGroup} passes the {@link SyncFilter}, then it returns the result - * of the {@code updateActionSupplier} execution, otherwise it returns an empty {@link Optional}. - * - * @param syncFilter the sync filter to check the {@code actionGroup} against. - * @param actionGroup the action group to check against the filter. - * @param updateActionSupplier the supplier to execute if the {@code actionGroup} passes the filter. - * @param the type of the content of the returned {@link Optional}. - * @return the result of the {@code updateActionSupplier} execution if the {@code actionGroup} passes the - * {@code syncFilter}, otherwise it returns an empty {@link Optional}. - */ - @Nonnull - static Optional buildActionIfPassesFilter( - @Nonnull final SyncFilter syncFilter, - @Nonnull final ActionGroup actionGroup, - @Nonnull final Supplier> updateActionSupplier) { - return executeSupplierIfPassesFilter(syncFilter, actionGroup, updateActionSupplier, Optional::empty); - } - - /** - * Util that checks if the supplied {@link ActionGroup} passes the {@link SyncFilter}, then it returns the result - * of the {@code updateActionSupplier} execution, otherwise it returns an empty {@link List}. - * - * @param syncFilter the sync filter to check the {@code actionGroup} against. - * @param actionGroup the action group to check against the filter. - * @param updateActionSupplier the supplier to execute if the {@code actionGroup} passes the filter. - * @param the type of the content of the returned {@link List}. - * @return the result of the {@code updateActionSupplier} execution if the {@code actionGroup} passes the - * {@code syncFilter}, otherwise it returns an empty {@link List}. - */ - @Nonnull - static List buildActionsIfPassesFilter( - @Nonnull final SyncFilter syncFilter, - @Nonnull final ActionGroup actionGroup, - @Nonnull final Supplier> updateActionSupplier) { - return executeSupplierIfPassesFilter(syncFilter, actionGroup, updateActionSupplier, Collections::emptyList); - } - - /** - * Validate both old and new product have master variant with significant key. - * - *

If at least on of the master variants key not found - the error is reported to {@code syncOptions} and - * true is returned. - * - * @param oldProduct old product to verify - * @param newProduct new product to verify - * @param syncOptions {@link BaseSyncOptions#applyErrorCallback(String) applyErrorCallback} holder - * @return true if at least one of the products have invalid (null/blank) master variant or key. - */ - private static boolean haveInvalidMasterVariants(@Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final ProductSyncOptions syncOptions) { - boolean hasError = false; - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - if (isBlank(oldMasterVariant.getKey())) { - handleBuildVariantsUpdateActionsError(oldProduct, BLANK_OLD_MASTER_VARIANT_KEY, syncOptions); - hasError = true; - } - - final ProductVariantDraft newMasterVariant = newProduct.getMasterVariant(); - if (newMasterVariant == null || isBlank(newMasterVariant.getKey())) { - handleBuildVariantsUpdateActionsError(oldProduct, BLANK_NEW_MASTER_VARIANT_KEY, syncOptions); - hasError = true; - } - - return hasError; + actions.add(addVariant); + + ofNullable(draft.getAssets()) + .map( + assetDrafts -> + assetDrafts.stream() + .map( + assetDraft -> + (UpdateAction) + AddAsset.ofSku(draft.getSku(), assetDraft).withStaged(true)) + .collect(toList())) + .ifPresent(actions::addAll); + + return actions; + } + + /** + * Util that checks if the supplied {@link ActionGroup} passes the {@link SyncFilter}, then it + * returns the result of the {@code updateActionSupplier} execution, otherwise it returns an empty + * {@link Optional}. + * + * @param syncFilter the sync filter to check the {@code actionGroup} against. + * @param actionGroup the action group to check against the filter. + * @param updateActionSupplier the supplier to execute if the {@code actionGroup} passes the + * filter. + * @param the type of the content of the returned {@link Optional}. + * @return the result of the {@code updateActionSupplier} execution if the {@code actionGroup} + * passes the {@code syncFilter}, otherwise it returns an empty {@link Optional}. + */ + @Nonnull + static Optional buildActionIfPassesFilter( + @Nonnull final SyncFilter syncFilter, + @Nonnull final ActionGroup actionGroup, + @Nonnull final Supplier> updateActionSupplier) { + return executeSupplierIfPassesFilter( + syncFilter, actionGroup, updateActionSupplier, Optional::empty); + } + + /** + * Util that checks if the supplied {@link ActionGroup} passes the {@link SyncFilter}, then it + * returns the result of the {@code updateActionSupplier} execution, otherwise it returns an empty + * {@link List}. + * + * @param syncFilter the sync filter to check the {@code actionGroup} against. + * @param actionGroup the action group to check against the filter. + * @param updateActionSupplier the supplier to execute if the {@code actionGroup} passes the + * filter. + * @param the type of the content of the returned {@link List}. + * @return the result of the {@code updateActionSupplier} execution if the {@code actionGroup} + * passes the {@code syncFilter}, otherwise it returns an empty {@link List}. + */ + @Nonnull + static List buildActionsIfPassesFilter( + @Nonnull final SyncFilter syncFilter, + @Nonnull final ActionGroup actionGroup, + @Nonnull final Supplier> updateActionSupplier) { + return executeSupplierIfPassesFilter( + syncFilter, actionGroup, updateActionSupplier, Collections::emptyList); + } + + /** + * Validate both old and new product have master variant with significant key. + * + *

If at least on of the master variants key not found - the error is reported to {@code + * syncOptions} and true is returned. + * + * @param oldProduct old product to verify + * @param newProduct new product to verify + * @param syncOptions {@link BaseSyncOptions#applyErrorCallback(String) applyErrorCallback} holder + * @return true if at least one of the products have invalid (null/blank) master variant or + * key. + */ + private static boolean haveInvalidMasterVariants( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final ProductSyncOptions syncOptions) { + boolean hasError = false; + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + if (isBlank(oldMasterVariant.getKey())) { + handleBuildVariantsUpdateActionsError(oldProduct, BLANK_OLD_MASTER_VARIANT_KEY, syncOptions); + hasError = true; } - /** - * Apply error message to the {@code syncOptions}, reporting the product key and {@code reason} - * - * @param oldProduct product which has sync error - * @param reason reason to specify in the error message. - * @param syncOptions {@link BaseSyncOptions#applyErrorCallback(SyncException, Object, Object, List)} holder - */ - private static void handleBuildVariantsUpdateActionsError(@Nonnull final Product oldProduct, - @Nonnull final String reason, - @Nonnull final ProductSyncOptions syncOptions) { - syncOptions.applyErrorCallback( - new SyncException(format("Failed to build variants update actions on the product with key '%s'. " - + "Reason: %s", - oldProduct.getKey(), reason)), oldProduct, null, null); + final ProductVariantDraft newMasterVariant = newProduct.getMasterVariant(); + if (newMasterVariant == null || isBlank(newMasterVariant.getKey())) { + handleBuildVariantsUpdateActionsError(oldProduct, BLANK_NEW_MASTER_VARIANT_KEY, syncOptions); + hasError = true; } - private ProductUpdateActionUtils() { - } + return hasError; + } + + /** + * Apply error message to the {@code syncOptions}, reporting the product key and {@code reason} + * + * @param oldProduct product which has sync error + * @param reason reason to specify in the error message. + * @param syncOptions {@link BaseSyncOptions#applyErrorCallback(SyncException, Object, Object, + * List)} holder + */ + private static void handleBuildVariantsUpdateActionsError( + @Nonnull final Product oldProduct, + @Nonnull final String reason, + @Nonnull final ProductSyncOptions syncOptions) { + syncOptions.applyErrorCallback( + new SyncException( + format( + "Failed to build variants update actions on the product with key '%s'. " + + "Reason: %s", + oldProduct.getKey(), reason)), + oldProduct, + null, + null); + } + + private ProductUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/products/utils/ProductVariantAssetUpdateActionUtils.java b/src/main/java/com/commercetools/sync/products/utils/ProductVariantAssetUpdateActionUtils.java index 029d0a73fc..e8c6434982 100644 --- a/src/main/java/com/commercetools/sync/products/utils/ProductVariantAssetUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/products/utils/ProductVariantAssetUpdateActionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; + import com.commercetools.sync.commons.utils.CustomUpdateActionUtils; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.helpers.AssetCustomActionBuilder; @@ -13,175 +16,188 @@ import io.sphere.sdk.products.commands.updateactions.SetAssetDescription; import io.sphere.sdk.products.commands.updateactions.SetAssetSources; import io.sphere.sdk.products.commands.updateactions.SetAssetTags; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import javax.annotation.Nonnull; public final class ProductVariantAssetUpdateActionUtils { - /** - * Compares all the fields of an {@link Asset} and an {@link AssetDraft} and returns a list of - * {@link UpdateAction}<{@link Product}> as a result. If both the {@link Asset} and the {@link AssetDraft} - * have identical fields, then no update action is needed and hence an empty {@link List} is returned. - * @param Type of the mainresource draft - * @param oldResource mainresource, whose asset should be updated. - * @param newResource new mainresource draft, which contains the asset to update. - * @param variantId the variantId needed for building the update action. - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new fields. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the update actions or an empty list if the asset fields are identical. - */ - @Nonnull - public static List> buildActions( - @Nonnull final Resource oldResource, - @Nonnull final D newResource, - @Nonnull final Integer variantId, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset, - @Nonnull final ProductSyncOptions syncOptions) { - - final List> updateActions = - filterEmptyOptionals( - buildChangeAssetNameUpdateAction(variantId, oldAsset, newAsset), - buildSetAssetDescriptionUpdateAction(variantId, oldAsset, newAsset), - buildSetAssetTagsUpdateAction(variantId, oldAsset, newAsset), - buildSetAssetSourcesUpdateAction(variantId, oldAsset, newAsset) - ); + /** + * Compares all the fields of an {@link Asset} and an {@link AssetDraft} and returns a list of + * {@link UpdateAction}<{@link Product}> as a result. If both the {@link Asset} and the + * {@link AssetDraft} have identical fields, then no update action is needed and hence an empty + * {@link List} is returned. + * + * @param Type of the mainresource draft + * @param oldResource mainresource, whose asset should be updated. + * @param newResource new mainresource draft, which contains the asset to update. + * @param variantId the variantId needed for building the update action. + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new fields. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the update actions or an empty list if the asset fields are identical. + */ + @Nonnull + public static List> buildActions( + @Nonnull final Resource oldResource, + @Nonnull final D newResource, + @Nonnull final Integer variantId, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAsset, + @Nonnull final ProductSyncOptions syncOptions) { - updateActions.addAll(buildCustomUpdateActions(oldResource, newResource, variantId, oldAsset, newAsset, - syncOptions)); - return updateActions; - } + final List> updateActions = + filterEmptyOptionals( + buildChangeAssetNameUpdateAction(variantId, oldAsset, newAsset), + buildSetAssetDescriptionUpdateAction(variantId, oldAsset, newAsset), + buildSetAssetTagsUpdateAction(variantId, oldAsset, newAsset), + buildSetAssetSourcesUpdateAction(variantId, oldAsset, newAsset)); - /** - * Compares the {@link LocalizedString} names of an {@link Asset} and an {@link AssetDraft} and returns an - * {@link UpdateAction}<{@link Product}> as a result in an {@link Optional}. If both the {@link Asset} and - * the {@link AssetDraft} have the same name, then no update action is needed and hence an empty {@link Optional} - * is returned. - * - * @param variantId the variantId needed for building the update action. - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new name. - * @return A filled optional with the update action or an empty optional if the names are identical. - */ - @Nonnull - public static Optional> buildChangeAssetNameUpdateAction( - @Nonnull final Integer variantId, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset) { - return buildUpdateAction(oldAsset.getName(), newAsset.getName(), - () -> ChangeAssetName.ofAssetKeyAndVariantId(variantId, oldAsset.getKey(), - newAsset.getName(), true)); - } + updateActions.addAll( + buildCustomUpdateActions( + oldResource, newResource, variantId, oldAsset, newAsset, syncOptions)); + return updateActions; + } - /** - * Compares the {@link LocalizedString} descriptions of an {@link Asset} and an {@link AssetDraft} and returns an - * {@link UpdateAction}<{@link Product}> as a result in an {@link Optional}. If both the {@link Asset} and - * the {@link AssetDraft} have the same description, then no update action is needed and hence an empty - * {@link Optional} is returned. - * - * @param variantId the variantId needed for building the update action. - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new description. - * @return A filled optional with the update action or an empty optional if the descriptions are identical. - */ - @Nonnull - public static Optional> buildSetAssetDescriptionUpdateAction( - @Nonnull final Integer variantId, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset) { - return buildUpdateAction(oldAsset.getDescription(), newAsset.getDescription(), - () -> SetAssetDescription.ofVariantIdAndAssetKey(variantId, oldAsset.getKey(), - newAsset.getDescription(), true)); - } + /** + * Compares the {@link LocalizedString} names of an {@link Asset} and an {@link AssetDraft} and + * returns an {@link UpdateAction}<{@link Product}> as a result in an {@link Optional}. If + * both the {@link Asset} and the {@link AssetDraft} have the same name, then no update action is + * needed and hence an empty {@link Optional} is returned. + * + * @param variantId the variantId needed for building the update action. + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new name. + * @return A filled optional with the update action or an empty optional if the names are + * identical. + */ + @Nonnull + public static Optional> buildChangeAssetNameUpdateAction( + @Nonnull final Integer variantId, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAsset) { + return buildUpdateAction( + oldAsset.getName(), + newAsset.getName(), + () -> + ChangeAssetName.ofAssetKeyAndVariantId( + variantId, oldAsset.getKey(), newAsset.getName(), true)); + } - /** - * Compares the tags of an {@link Asset} and an {@link AssetDraft} and returns an - * {@link UpdateAction}<{@link Product}> as a result in an {@link Optional}. If both the {@link Asset} and - * the {@link AssetDraft} have the same tags, then no update action is needed and hence an empty {@link Optional} is - * returned. - * - * @param variantId the variantId needed for building the update action. - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new tags. - * @return A filled optional with the update action or an empty optional if the tags are identical. - */ - @Nonnull - public static Optional> buildSetAssetTagsUpdateAction( - @Nonnull final Integer variantId, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset) { - return buildUpdateAction(oldAsset.getTags(), newAsset.getTags(), - () -> SetAssetTags.ofVariantIdAndAssetKey(variantId, oldAsset.getKey(), - newAsset.getTags(), true)); - } + /** + * Compares the {@link LocalizedString} descriptions of an {@link Asset} and an {@link AssetDraft} + * and returns an {@link UpdateAction}<{@link Product}> as a result in an {@link Optional}. + * If both the {@link Asset} and the {@link AssetDraft} have the same description, then no update + * action is needed and hence an empty {@link Optional} is returned. + * + * @param variantId the variantId needed for building the update action. + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new description. + * @return A filled optional with the update action or an empty optional if the descriptions are + * identical. + */ + @Nonnull + public static Optional> buildSetAssetDescriptionUpdateAction( + @Nonnull final Integer variantId, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAsset) { + return buildUpdateAction( + oldAsset.getDescription(), + newAsset.getDescription(), + () -> + SetAssetDescription.ofVariantIdAndAssetKey( + variantId, oldAsset.getKey(), newAsset.getDescription(), true)); + } - /** - * Compares the sources of an {@link Asset} and an {@link AssetDraft} and returns an - * {@link UpdateAction}<{@link Product}> as a result in an {@link Optional}. If both the {@link Asset} and - * the {@link AssetDraft} have the same sources, then no update action is needed and hence an empty {@link Optional} - * is returned. - * - * @param variantId the variantId needed for building the update action. - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new sources. - * @return A filled optional with the update action or an empty optional if the sources are identical. - */ - @Nonnull - public static Optional> buildSetAssetSourcesUpdateAction( - @Nonnull final Integer variantId, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset) { - return buildUpdateAction(oldAsset.getSources(), newAsset.getSources(), - () -> SetAssetSources.ofVariantIdAndAssetKey(variantId, oldAsset.getKey(), - newAsset.getSources(), true)); - } + /** + * Compares the tags of an {@link Asset} and an {@link AssetDraft} and returns an {@link + * UpdateAction}<{@link Product}> as a result in an {@link Optional}. If both the {@link + * Asset} and the {@link AssetDraft} have the same tags, then no update action is needed and hence + * an empty {@link Optional} is returned. + * + * @param variantId the variantId needed for building the update action. + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new tags. + * @return A filled optional with the update action or an empty optional if the tags are + * identical. + */ + @Nonnull + public static Optional> buildSetAssetTagsUpdateAction( + @Nonnull final Integer variantId, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAsset) { + return buildUpdateAction( + oldAsset.getTags(), + newAsset.getTags(), + () -> + SetAssetTags.ofVariantIdAndAssetKey( + variantId, oldAsset.getKey(), newAsset.getTags(), true)); + } - /** - * Compares the custom fields and custom types of an {@link Asset} and an {@link AssetDraft} and returns a list of - * {@link UpdateAction}<{@link Product}> as a result. If both the {@link Asset} and the {@link AssetDraft} - * have identical custom fields and types, then no update action is needed and hence an empty {@link List} is - * returned. + /** + * Compares the sources of an {@link Asset} and an {@link AssetDraft} and returns an {@link + * UpdateAction}<{@link Product}> as a result in an {@link Optional}. If both the {@link + * Asset} and the {@link AssetDraft} have the same sources, then no update action is needed and + * hence an empty {@link Optional} is returned. + * + * @param variantId the variantId needed for building the update action. + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new sources. + * @return A filled optional with the update action or an empty optional if the sources are + * identical. + */ + @Nonnull + public static Optional> buildSetAssetSourcesUpdateAction( + @Nonnull final Integer variantId, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAsset) { + return buildUpdateAction( + oldAsset.getSources(), + newAsset.getSources(), + () -> + SetAssetSources.ofVariantIdAndAssetKey( + variantId, oldAsset.getKey(), newAsset.getSources(), true)); + } - * @param Type of the mainresource draft - * @param oldResource mainresource, whose asset should be updated. - * @param newResource new mainresource draft, which contains the asset to update. - * @param variantId the variantId needed for building the update action. - * @param oldAsset the asset which should be updated. - * @param newAsset the asset draft where we get the new custom fields and types. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the custom field/type update actions or an empty list if the custom fields/types are - * identical. - */ - @Nonnull - public static List> buildCustomUpdateActions( - @Nonnull final Resource oldResource, - @Nonnull final D newResource, - @Nonnull final Integer variantId, - @Nonnull final Asset oldAsset, - @Nonnull final AssetDraft newAsset, - @Nonnull final ProductSyncOptions syncOptions) { + /** + * Compares the custom fields and custom types of an {@link Asset} and an {@link AssetDraft} and + * returns a list of {@link UpdateAction}<{@link Product}> as a result. If both the {@link + * Asset} and the {@link AssetDraft} have identical custom fields and types, then no update action + * is needed and hence an empty {@link List} is returned. + * + * @param Type of the mainresource draft + * @param oldResource mainresource, whose asset should be updated. + * @param newResource new mainresource draft, which contains the asset to update. + * @param variantId the variantId needed for building the update action. + * @param oldAsset the asset which should be updated. + * @param newAsset the asset draft where we get the new custom fields and types. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the custom field/type update actions or an empty list if the custom + * fields/types are identical. + */ + @Nonnull + public static List> buildCustomUpdateActions( + @Nonnull final Resource oldResource, + @Nonnull final D newResource, + @Nonnull final Integer variantId, + @Nonnull final Asset oldAsset, + @Nonnull final AssetDraft newAsset, + @Nonnull final ProductSyncOptions syncOptions) { - return CustomUpdateActionUtils.buildCustomUpdateActions( - oldResource, - newResource, - oldAsset, - newAsset, - new AssetCustomActionBuilder(), - variantId, - Asset::getId, - asset -> Asset.resourceTypeId(), - Asset::getKey, - syncOptions); - } + return CustomUpdateActionUtils.buildCustomUpdateActions( + oldResource, + newResource, + oldAsset, + newAsset, + new AssetCustomActionBuilder(), + variantId, + Asset::getId, + asset -> Asset.resourceTypeId(), + Asset::getKey, + syncOptions); + } - private ProductVariantAssetUpdateActionUtils() { - } + private ProductVariantAssetUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/products/utils/ProductVariantAttributeUpdateActionUtils.java b/src/main/java/com/commercetools/sync/products/utils/ProductVariantAttributeUpdateActionUtils.java index df7a2d22bd..f72f6aa4f7 100644 --- a/src/main/java/com/commercetools/sync/products/utils/ProductVariantAttributeUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/products/utils/ProductVariantAttributeUpdateActionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static java.lang.String.format; + import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.products.AttributeMetaData; import com.fasterxml.jackson.databind.JsonNode; @@ -9,69 +12,72 @@ import io.sphere.sdk.products.attributes.AttributeDraft; import io.sphere.sdk.products.commands.updateactions.SetAttribute; import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static java.lang.String.format; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class ProductVariantAttributeUpdateActionUtils { - public static final String ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA = "Cannot find the attribute with the name '%s'" - + " in the supplied attribute metadata."; - - /** - * Compares the attributes of a {@link AttributeDraft} and a {@link Attribute} to build either a - * {@link io.sphere.sdk.products.commands.updateactions.SetAttribute} or a - * {@link io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants}. - * - *

If the attribute is sameForAll a - * {@link io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants} is built. Otherwise, - * a {@link io.sphere.sdk.products.commands.updateactions.SetAttribute} is built. - * - *

If both the {@link AttributeDraft} and the {@link Attribute} have identical values, then - * no update action is needed and hence an empty {@link List} is returned. - * - * @param variantId the id of the variant of that the attribute belong to. It is used only in the error - * messages if any. - * @param oldProductVariantAttribute the {@link Attribute} which should be updated. - * @param newProductVariantAttribute the {@link AttributeDraft} where we get the new value. - * @param attributesMetaData a map of attribute name -> {@link AttributeMetaData}; which defines attribute - * information: its name and whether it has the constraint "SameForAll" or not. - * @return A filled optional with the update action or an empty optional if the attributes are identical. - * @throws BuildUpdateActionException thrown if attribute as not found in the {@code attributeMetaData} or - * if the attribute is required and the new value is null. - */ - @Nonnull - public static Optional> buildProductVariantAttributeUpdateAction( - final int variantId, - @Nullable final Attribute oldProductVariantAttribute, - @Nonnull final AttributeDraft newProductVariantAttribute, - @Nonnull final Map attributesMetaData) throws BuildUpdateActionException { + public static final String ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA = + "Cannot find the attribute with the name '%s'" + " in the supplied attribute metadata."; - final String newProductVariantAttributeName = newProductVariantAttribute.getName(); - final JsonNode newProductVariantAttributeValue = newProductVariantAttribute.getValue(); - final JsonNode oldProductVariantAttributeValue = oldProductVariantAttribute != null - ? oldProductVariantAttribute.getValueAsJsonNode() : null; + /** + * Compares the attributes of a {@link AttributeDraft} and a {@link Attribute} to build either a + * {@link io.sphere.sdk.products.commands.updateactions.SetAttribute} or a {@link + * io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants}. + * + *

If the attribute is sameForAll a {@link + * io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants} is built. Otherwise, a + * {@link io.sphere.sdk.products.commands.updateactions.SetAttribute} is built. + * + *

If both the {@link AttributeDraft} and the {@link Attribute} have identical values, then no + * update action is needed and hence an empty {@link List} is returned. + * + * @param variantId the id of the variant of that the attribute belong to. It is used only in the + * error messages if any. + * @param oldProductVariantAttribute the {@link Attribute} which should be updated. + * @param newProductVariantAttribute the {@link AttributeDraft} where we get the new value. + * @param attributesMetaData a map of attribute name -> {@link AttributeMetaData}; which + * defines attribute information: its name and whether it has the constraint "SameForAll" or + * not. + * @return A filled optional with the update action or an empty optional if the attributes are + * identical. + * @throws BuildUpdateActionException thrown if attribute as not found in the {@code + * attributeMetaData} or if the attribute is required and the new value is null. + */ + @Nonnull + public static Optional> buildProductVariantAttributeUpdateAction( + final int variantId, + @Nullable final Attribute oldProductVariantAttribute, + @Nonnull final AttributeDraft newProductVariantAttribute, + @Nonnull final Map attributesMetaData) + throws BuildUpdateActionException { - final AttributeMetaData attributeMetaData = attributesMetaData.get(newProductVariantAttributeName); + final String newProductVariantAttributeName = newProductVariantAttribute.getName(); + final JsonNode newProductVariantAttributeValue = newProductVariantAttribute.getValue(); + final JsonNode oldProductVariantAttributeValue = + oldProductVariantAttribute != null ? oldProductVariantAttribute.getValueAsJsonNode() : null; - if (attributeMetaData == null) { - final String errorMessage = format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, newProductVariantAttributeName); - throw new BuildUpdateActionException(errorMessage); - } - - return attributeMetaData.isSameForAll() - ? buildUpdateAction(oldProductVariantAttributeValue, newProductVariantAttributeValue, - () -> SetAttributeInAllVariants.of(newProductVariantAttribute, true)) : - buildUpdateAction(oldProductVariantAttributeValue, newProductVariantAttributeValue, - () -> SetAttribute.of(variantId, newProductVariantAttribute, true)); + final AttributeMetaData attributeMetaData = + attributesMetaData.get(newProductVariantAttributeName); + if (attributeMetaData == null) { + final String errorMessage = + format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, newProductVariantAttributeName); + throw new BuildUpdateActionException(errorMessage); } - private ProductVariantAttributeUpdateActionUtils() { - } + return attributeMetaData.isSameForAll() + ? buildUpdateAction( + oldProductVariantAttributeValue, + newProductVariantAttributeValue, + () -> SetAttributeInAllVariants.of(newProductVariantAttribute, true)) + : buildUpdateAction( + oldProductVariantAttributeValue, + newProductVariantAttributeValue, + () -> SetAttribute.of(variantId, newProductVariantAttribute, true)); + } + + private ProductVariantAttributeUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/products/utils/ProductVariantPriceUpdateActionUtils.java b/src/main/java/com/commercetools/sync/products/utils/ProductVariantPriceUpdateActionUtils.java index f2ed125987..7adb6c01f7 100644 --- a/src/main/java/com/commercetools/sync/products/utils/ProductVariantPriceUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/products/utils/ProductVariantPriceUpdateActionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static java.lang.String.format; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.CustomUpdateActionUtils; import com.commercetools.sync.products.ProductSyncOptions; @@ -10,135 +13,140 @@ import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.ChangePrice; - -import javax.annotation.Nonnull; -import javax.money.MonetaryAmount; import java.util.ArrayList; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static java.lang.String.format; +import javax.annotation.Nonnull; +import javax.money.MonetaryAmount; public final class ProductVariantPriceUpdateActionUtils { - private static final String VARIANT_CHANGE_PRICE_EMPTY_VALUE = "Cannot unset 'value' field of price with id" - + " '%s'."; - - /** - * Compares all the fields of a {@link Price} and a {@link PriceDraft} and returns a list of - * {@link UpdateAction}<{@link Product}> as a result. If both the {@link Price} and the {@link PriceDraft} - * have identical fields, then no update action is needed and hence an empty {@link List} is returned. - * - * @param oldProduct old Product, whose prices should be updated. - * @param newProduct new product draft, which provides the prices to update. - * @param variantId the variantId needed for building the update action. - * @param oldPrice the price which should be updated. - * @param newPrice the price draft where we get the new fields. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the update actions or an empty list if the price fields are identical. - */ - @Nonnull - public static List> buildActions( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final Integer variantId, - @Nonnull final Price oldPrice, - @Nonnull final PriceDraft newPrice, - @Nonnull final ProductSyncOptions syncOptions) { - - final List> updateActions = new ArrayList<>(); - - buildChangePriceUpdateAction(oldPrice, newPrice, syncOptions).ifPresent(updateActions::add); - updateActions.addAll(buildCustomUpdateActions(oldProduct, newProduct, variantId, oldPrice, newPrice, - syncOptions)); - - return updateActions; + private static final String VARIANT_CHANGE_PRICE_EMPTY_VALUE = + "Cannot unset 'value' field of price with id" + " '%s'."; + + /** + * Compares all the fields of a {@link Price} and a {@link PriceDraft} and returns a list of + * {@link UpdateAction}<{@link Product}> as a result. If both the {@link Price} and the + * {@link PriceDraft} have identical fields, then no update action is needed and hence an empty + * {@link List} is returned. + * + * @param oldProduct old Product, whose prices should be updated. + * @param newProduct new product draft, which provides the prices to update. + * @param variantId the variantId needed for building the update action. + * @param oldPrice the price which should be updated. + * @param newPrice the price draft where we get the new fields. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the update actions or an empty list if the price fields are identical. + */ + @Nonnull + public static List> buildActions( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final Integer variantId, + @Nonnull final Price oldPrice, + @Nonnull final PriceDraft newPrice, + @Nonnull final ProductSyncOptions syncOptions) { + + final List> updateActions = new ArrayList<>(); + + buildChangePriceUpdateAction(oldPrice, newPrice, syncOptions).ifPresent(updateActions::add); + updateActions.addAll( + buildCustomUpdateActions( + oldProduct, newProduct, variantId, oldPrice, newPrice, syncOptions)); + + return updateActions; + } + + /** + * Builds a {@link ChangePrice} action based on the comparison of the following fields of the + * supplied {@link Price} and {@link PriceDraft}: + * + *

    + *
  • {@link Price#getValue()} and {@link PriceDraft#getValue()} + *
  • {@link Price#getTiers()} and {@link PriceDraft#getTiers()} + *
+ * + *

If any of the aforementioned fields are different a {@link ChangePrice} update action will + * be returned in an {@link Optional}, otherwise if both are identical in the {@link Price} and + * the {@link PriceDraft}, then no update action is needed and hence an empty {@link Optional} is + * returned. + * + * @param oldPrice the price which should be updated. + * @param newPrice the price draft where we get the new name. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A filled optional with the update action or an empty optional if the names are + * identical. + */ + @Nonnull + public static Optional buildChangePriceUpdateAction( + @Nonnull final Price oldPrice, + @Nonnull final PriceDraft newPrice, + @Nonnull final ProductSyncOptions syncOptions) { + + final MonetaryAmount oldPriceValue = oldPrice.getValue(); + final MonetaryAmount newPriceValue = newPrice.getValue(); + + if (newPriceValue == null) { + syncOptions.applyWarningCallback( + new SyncException(format(VARIANT_CHANGE_PRICE_EMPTY_VALUE, oldPrice.getId())), + null, + null); + return Optional.empty(); } - - /** - * Builds a {@link ChangePrice} action based on the comparison of the following fields of the supplied {@link Price} - * and {@link PriceDraft}: - *

    - *
  • {@link Price#getValue()} and {@link PriceDraft#getValue()}
  • - *
  • {@link Price#getTiers()} and {@link PriceDraft#getTiers()}
  • - *
- * - *

If any of the aforementioned fields are different a {@link ChangePrice} update action will be returned in an - * {@link Optional}, otherwise if both are identical in the {@link Price} and the {@link PriceDraft}, then no update - * action is needed and hence an empty {@link Optional} is returned. - * - * @param oldPrice the price which should be updated. - * @param newPrice the price draft where we get the new name. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A filled optional with the update action or an empty optional if the names are identical. - */ - @Nonnull - public static Optional buildChangePriceUpdateAction( - @Nonnull final Price oldPrice, - @Nonnull final PriceDraft newPrice, - @Nonnull final ProductSyncOptions syncOptions) { - - final MonetaryAmount oldPriceValue = oldPrice.getValue(); - final MonetaryAmount newPriceValue = newPrice.getValue(); - - if (newPriceValue == null) { - syncOptions.applyWarningCallback( - new SyncException(format(VARIANT_CHANGE_PRICE_EMPTY_VALUE, oldPrice.getId())), - null, null); - return Optional.empty(); - } - - final Optional actionAfterValuesDiff = buildUpdateAction(oldPriceValue, newPriceValue, - () -> ChangePrice.of(oldPrice, newPrice, true)); - - return actionAfterValuesDiff.map(Optional::of) - .orElseGet(() -> - // If values are not different, compare tiers. - buildUpdateAction(oldPrice.getTiers(), newPrice.getTiers(), - () -> ChangePrice.of(oldPrice, newPrice, true))); - } - - /** - * Compares the custom fields and custom types of a {@link Price} and a {@link PriceDraft} and returns a list of - * {@link UpdateAction}<{@link Product}> as a result. If both the {@link Price} and the {@link PriceDraft} - * have identical custom fields and types, then no update action is needed and hence an empty {@link List} is - * returned. - * - * @param oldProduct old Product, whose prices should be updated. - * @param newProduct new product draft, which provides the prices to update. - * @param variantId the variantId needed for building the update action. - * @param oldPrice the price which should be updated. - * @param newPrice the price draft where we get the new custom fields and types. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the custom field/type update actions or an empty list if the custom fields/types are - * identical. - */ - @Nonnull - public static List> buildCustomUpdateActions( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final Integer variantId, - @Nonnull final Price oldPrice, - @Nonnull final PriceDraft newPrice, - @Nonnull final ProductSyncOptions syncOptions) { - - return CustomUpdateActionUtils.buildCustomUpdateActions( - oldProduct, - newProduct, - oldPrice, - newPrice, - new PriceCustomActionBuilder(), - variantId, - Price::getId, - price -> Price.resourceTypeId(), - Price::getId, - syncOptions); - } - - private ProductVariantPriceUpdateActionUtils() { - } + final Optional actionAfterValuesDiff = + buildUpdateAction( + oldPriceValue, newPriceValue, () -> ChangePrice.of(oldPrice, newPrice, true)); + + return actionAfterValuesDiff + .map(Optional::of) + .orElseGet( + () -> + // If values are not different, compare tiers. + buildUpdateAction( + oldPrice.getTiers(), + newPrice.getTiers(), + () -> ChangePrice.of(oldPrice, newPrice, true))); + } + + /** + * Compares the custom fields and custom types of a {@link Price} and a {@link PriceDraft} and + * returns a list of {@link UpdateAction}<{@link Product}> as a result. If both the {@link + * Price} and the {@link PriceDraft} have identical custom fields and types, then no update action + * is needed and hence an empty {@link List} is returned. + * + * @param oldProduct old Product, whose prices should be updated. + * @param newProduct new product draft, which provides the prices to update. + * @param variantId the variantId needed for building the update action. + * @param oldPrice the price which should be updated. + * @param newPrice the price draft where we get the new custom fields and types. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the custom field/type update actions or an empty list if the custom + * fields/types are identical. + */ + @Nonnull + public static List> buildCustomUpdateActions( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final Integer variantId, + @Nonnull final Price oldPrice, + @Nonnull final PriceDraft newPrice, + @Nonnull final ProductSyncOptions syncOptions) { + + return CustomUpdateActionUtils.buildCustomUpdateActions( + oldProduct, + newProduct, + oldPrice, + newPrice, + new PriceCustomActionBuilder(), + variantId, + Price::getId, + price -> Price.resourceTypeId(), + Price::getId, + syncOptions); + } + + private ProductVariantPriceUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/products/utils/ProductVariantUpdateActionUtils.java b/src/main/java/com/commercetools/sync/products/utils/ProductVariantUpdateActionUtils.java index cef920b334..2ab227f448 100644 --- a/src/main/java/com/commercetools/sync/products/utils/ProductVariantUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/products/utils/ProductVariantUpdateActionUtils.java @@ -1,5 +1,20 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.commons.utils.AssetsUpdateActionUtils.buildAssetsUpdateActions; +import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToMap; +import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; +import static com.commercetools.sync.commons.utils.CollectionUtils.filterCollection; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.internals.utils.UnorderedCollectionSyncUtils.buildRemoveUpdateActions; +import static com.commercetools.sync.internals.utils.UpdateActionsSortUtils.sortPriceActions; +import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA; +import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.buildProductVariantAttributeUpdateAction; +import static com.commercetools.sync.products.utils.ProductVariantPriceUpdateActionUtils.buildActions; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Optional.ofNullable; + import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.internals.helpers.PriceCompositeId; @@ -25,358 +40,419 @@ import io.sphere.sdk.products.commands.updateactions.SetAttribute; import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; import io.sphere.sdk.products.commands.updateactions.SetSku; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.AssetsUpdateActionUtils.buildAssetsUpdateActions; -import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToMap; -import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; -import static com.commercetools.sync.commons.utils.CollectionUtils.filterCollection; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.internals.utils.UnorderedCollectionSyncUtils.buildRemoveUpdateActions; -import static com.commercetools.sync.internals.utils.UpdateActionsSortUtils.sortPriceActions; -import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA; -import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.buildProductVariantAttributeUpdateAction; -import static com.commercetools.sync.products.utils.ProductVariantPriceUpdateActionUtils.buildActions; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static java.util.Optional.ofNullable; - +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class ProductVariantUpdateActionUtils { - public static final String FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION = "Failed to build a " - + "setAttribute/setAttributeInAllVariants update action for the attribute with the name '%s' in the " - + "ProductVariantDraft with key '%s' on the product with key '%s'. Reason: %s"; - public static final String NULL_PRODUCT_VARIANT_ATTRIBUTE = "AttributeDraft is null."; - private static final String NULL_PRODUCT_VARIANT_PRICE = "New price is null."; - - /** - * Compares the SKUs of a {@link ProductVariantDraft} and a {@link ProductVariant}. It returns a {@link SetSku} - * update action as a result in an {@link Optional}. If both the {@link ProductVariantDraft} - * and the {@link ProductVariant} have identical identical SKUs, then no update action is needed and hence an - * empty {@link Optional} is returned. - * - * @param oldProductVariant the variant which should be updated. - * @param newProductVariant the variant draft where we get the new SKU. - * @return A filled optional with the update action or an empty optional if the SKUs are identical. - */ - @Nonnull - public static Optional buildProductVariantSkuUpdateAction( - @Nonnull final ProductVariant oldProductVariant, - @Nonnull final ProductVariantDraft newProductVariant) { - final String oldProductVariantSku = oldProductVariant.getSku(); - final String newProductVariantSku = newProductVariant.getSku(); - return buildUpdateAction(oldProductVariantSku, newProductVariantSku, - () -> SetSku.of(oldProductVariant.getId(), newProductVariantSku, true)); - } - - /** - * Compares the {@link List} of {@link io.sphere.sdk.products.Price}s of a {@link ProductVariantDraft} and a - * {@link ProductVariant} and returns a {@link List} of {@link UpdateAction}<{@link Product}>. If both the - * {@link ProductVariantDraft} and the {@link ProductVariant} have identical list of prices, then no update action - * is needed and hence an empty {@link List} is returned. - * - * @param oldProduct the product which should be updated. - * @param newProduct the product draft. - * @param oldProductVariant the {@link ProductVariant} which should be updated. - * @param newProductVariant the {@link ProductVariantDraft} where we get the new list of prices. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied by - * the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link ProductSyncOptions} - * for more info). - * @return a list that contains all the update actions needed, otherwise an empty list if no update actions are - * needed. - */ - @Nonnull - public static List> buildProductVariantPricesUpdateActions( - @Nullable final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final ProductVariant oldProductVariant, - @Nonnull final ProductVariantDraft newProductVariant, - @Nonnull final ProductSyncOptions syncOptions) { - - final List oldPrices = oldProductVariant.getPrices(); - final List newPrices = newProductVariant.getPrices(); - - final List> updateActions = buildRemoveUpdateActions(oldPrices, newPrices, - PriceCompositeId::of, PriceCompositeId::of, price -> RemovePrice.of(price, true)); - - final Integer variantId = oldProductVariant.getId(); - final Map oldPricesMap = collectionToMap(oldPrices, PriceCompositeId::of); - - emptyIfNull(newPrices).forEach(newPrice -> { - if (newPrice == null) { - syncOptions.applyErrorCallback(new SyncException(format("Failed to build prices update actions " - + "for one price on the variant with id '%d' and key '%s'. Reason: %s", variantId, - oldProductVariant.getKey(), NULL_PRODUCT_VARIANT_PRICE)), oldProduct, newProduct, null); - } else { + public static final String FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION = + "Failed to build a " + + "setAttribute/setAttributeInAllVariants update action for the attribute with the name '%s' in the " + + "ProductVariantDraft with key '%s' on the product with key '%s'. Reason: %s"; + public static final String NULL_PRODUCT_VARIANT_ATTRIBUTE = "AttributeDraft is null."; + private static final String NULL_PRODUCT_VARIANT_PRICE = "New price is null."; + + /** + * Compares the SKUs of a {@link ProductVariantDraft} and a {@link ProductVariant}. It returns a + * {@link SetSku} update action as a result in an {@link Optional}. If both the {@link + * ProductVariantDraft} and the {@link ProductVariant} have identical identical SKUs, then no + * update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldProductVariant the variant which should be updated. + * @param newProductVariant the variant draft where we get the new SKU. + * @return A filled optional with the update action or an empty optional if the SKUs are + * identical. + */ + @Nonnull + public static Optional buildProductVariantSkuUpdateAction( + @Nonnull final ProductVariant oldProductVariant, + @Nonnull final ProductVariantDraft newProductVariant) { + final String oldProductVariantSku = oldProductVariant.getSku(); + final String newProductVariantSku = newProductVariant.getSku(); + return buildUpdateAction( + oldProductVariantSku, + newProductVariantSku, + () -> SetSku.of(oldProductVariant.getId(), newProductVariantSku, true)); + } + + /** + * Compares the {@link List} of {@link io.sphere.sdk.products.Price}s of a {@link + * ProductVariantDraft} and a {@link ProductVariant} and returns a {@link List} of {@link + * UpdateAction}<{@link Product}>. If both the {@link ProductVariantDraft} and the {@link + * ProductVariant} have identical list of prices, then no update action is needed and hence an + * empty {@link List} is returned. + * + * @param oldProduct the product which should be updated. + * @param newProduct the product draft. + * @param oldProductVariant the {@link ProductVariant} which should be updated. + * @param newProductVariant the {@link ProductVariantDraft} where we get the new list of prices. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * ProductSyncOptions} for more info). + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + @Nonnull + public static List> buildProductVariantPricesUpdateActions( + @Nullable final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final ProductVariant oldProductVariant, + @Nonnull final ProductVariantDraft newProductVariant, + @Nonnull final ProductSyncOptions syncOptions) { + + final List oldPrices = oldProductVariant.getPrices(); + final List newPrices = newProductVariant.getPrices(); + + final List> updateActions = + buildRemoveUpdateActions( + oldPrices, + newPrices, + PriceCompositeId::of, + PriceCompositeId::of, + price -> RemovePrice.of(price, true)); + + final Integer variantId = oldProductVariant.getId(); + final Map oldPricesMap = + collectionToMap(oldPrices, PriceCompositeId::of); + + emptyIfNull(newPrices) + .forEach( + newPrice -> { + if (newPrice == null) { + syncOptions.applyErrorCallback( + new SyncException( + format( + "Failed to build prices update actions " + + "for one price on the variant with id '%d' and key '%s'. Reason: %s", + variantId, oldProductVariant.getKey(), NULL_PRODUCT_VARIANT_PRICE)), + oldProduct, + newProduct, + null); + } else { final PriceCompositeId newPriceCompositeId = PriceCompositeId.of(newPrice); final Price matchingOldPrice = oldPricesMap.get(newPriceCompositeId); - final List> updateOrAddPrice = ofNullable(matchingOldPrice) - .map(oldPrice -> buildActions(oldProduct, newProduct, variantId, oldPrice, newPrice, syncOptions)) - .orElseGet(() -> singletonList(AddPrice.ofVariantId(variantId, newPrice, true))); + final List> updateOrAddPrice = + ofNullable(matchingOldPrice) + .map( + oldPrice -> + buildActions( + oldProduct, + newProduct, + variantId, + oldPrice, + newPrice, + syncOptions)) + .orElseGet( + () -> singletonList(AddPrice.ofVariantId(variantId, newPrice, true))); updateActions.addAll(updateOrAddPrice); - } - }); + } + }); - return sortPriceActions(updateActions); + return sortPriceActions(updateActions); + } + + /** + * Compares the {@link List} of {@link Image}s of a {@link ProductVariantDraft} and a {@link + * ProductVariant} and returns a {@link List} of {@link UpdateAction}<{@link Product}>. If + * both the {@link ProductVariantDraft} and the {@link ProductVariant} have identical list of + * images, then no update action is needed and hence an empty {@link List} is returned. + * + * @param oldProductVariant the {@link ProductVariant} which should be updated. + * @param newProductVariant the {@link ProductVariantDraft} where we get the new list of images. + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + @Nonnull + public static List> buildProductVariantImagesUpdateActions( + @Nonnull final ProductVariant oldProductVariant, + @Nonnull final ProductVariantDraft newProductVariant) { + final List> updateActions = new ArrayList<>(); + final Integer oldProductVariantId = oldProductVariant.getId(); + final List oldProductVariantImages = oldProductVariant.getImages(); + final List newProductVariantImages = newProductVariant.getImages(); + + // This implementation is quite straight forward and might be slow on large arrays, this is + // due to it's quadratic nature on images' removal/addition. + // Unfortunately, currently there is no easy solution to sync 2 ordered lists + // having only AddExternalImage/RemoveImage/MoveImageToPosition actions. + // This solution should be re-optimized in the next releases to avoid O(N^2) for large lists. + // TODO: GITHUB ISSUE#133 + + if (!Objects.equals(oldProductVariantImages, newProductVariantImages)) { + final List updatedOldImages = new ArrayList<>(oldProductVariantImages); + final List newImages = emptyIfNull(newProductVariantImages); + + filterCollection( + oldProductVariantImages, oldVariantImage -> !newImages.contains(oldVariantImage)) + .forEach( + oldImage -> { + updateActions.add(RemoveImage.ofVariantId(oldProductVariantId, oldImage, true)); + updatedOldImages.remove(oldImage); + }); + + filterCollection( + newProductVariantImages, + newVariantImage -> !oldProductVariantImages.contains(newVariantImage)) + .forEach( + newImage -> { + updateActions.add( + AddExternalImage.ofVariantId(oldProductVariantId, newImage, true)); + updatedOldImages.add(newImage); + }); + updateActions.addAll( + buildMoveImageToPositionUpdateActions(oldProductVariantId, updatedOldImages, newImages)); } - - /** - * Compares the {@link List} of {@link Image}s of a {@link ProductVariantDraft} and a {@link ProductVariant} and - * returns a {@link List} of {@link UpdateAction}<{@link Product}>. If both the {@link ProductVariantDraft} - * and the {@link ProductVariant} have identical list of images, then no update action is needed and hence an - * empty {@link List} is returned. - * - * @param oldProductVariant the {@link ProductVariant} which should be updated. - * @param newProductVariant the {@link ProductVariantDraft} where we get the new list of images. - * @return a list that contains all the update actions needed, otherwise an empty list if no update actions are - * needed. - */ - @Nonnull - public static List> buildProductVariantImagesUpdateActions( - @Nonnull final ProductVariant oldProductVariant, - @Nonnull final ProductVariantDraft newProductVariant) { - final List> updateActions = new ArrayList<>(); - final Integer oldProductVariantId = oldProductVariant.getId(); - final List oldProductVariantImages = oldProductVariant.getImages(); - final List newProductVariantImages = newProductVariant.getImages(); - - // This implementation is quite straight forward and might be slow on large arrays, this is - // due to it's quadratic nature on images' removal/addition. - // Unfortunately, currently there is no easy solution to sync 2 ordered lists - // having only AddExternalImage/RemoveImage/MoveImageToPosition actions. - // This solution should be re-optimized in the next releases to avoid O(N^2) for large lists. - // TODO: GITHUB ISSUE#133 - - if (!Objects.equals(oldProductVariantImages, newProductVariantImages)) { - final List updatedOldImages = new ArrayList<>(oldProductVariantImages); - final List newImages = emptyIfNull(newProductVariantImages); - - filterCollection(oldProductVariantImages, oldVariantImage -> - !newImages.contains(oldVariantImage)) - .forEach(oldImage -> { - updateActions.add(RemoveImage.ofVariantId(oldProductVariantId, oldImage, true)); - updatedOldImages.remove(oldImage); - }); - - filterCollection(newProductVariantImages, newVariantImage -> - !oldProductVariantImages.contains(newVariantImage)) - .forEach(newImage -> { - updateActions.add(AddExternalImage.ofVariantId(oldProductVariantId, newImage, true)); - updatedOldImages.add(newImage); - }); - updateActions.addAll(buildMoveImageToPositionUpdateActions(oldProductVariantId, - updatedOldImages, newImages)); - } - return updateActions; + return updateActions; + } + + /** + * Compares an old {@link List} of {@link Image}s and a new one and returns a {@link List} of + * {@link MoveImageToPosition} with the given {@code variantId}. If both the lists are identical, + * then no update action is needed and hence an empty {@link List} is returned. + * + *

This method expects the two lists two contain the same images only in different order. + * Otherwise, an {@link IllegalArgumentException} would be thrown. + * + *

Note: the solution is still not optimized and may contain {@link MoveImageToPosition} + * actions for items which are already on desired positions (after previous moves in the + * sequence). This will be re-optimized in the next releases. TODO: GITHUB ISSUE#133 + * + * @param variantId the variantId for the {@link MoveImageToPosition} update actions. + * @param oldImages the old list of images. + * @param newImages the new list of images. + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + public static List buildMoveImageToPositionUpdateActions( + final int variantId, + @Nonnull final List oldImages, + @Nonnull final List newImages) { + final int oldImageListSize = oldImages.size(); + final int newImageListSize = newImages.size(); + if (oldImageListSize != newImageListSize) { + throw new IllegalArgumentException( + format( + "Old and new image lists must have the same size, but they have %d and %d respectively", + oldImageListSize, newImageListSize)); } - /** - * Compares an old {@link List} of {@link Image}s and a new one and returns a {@link List} of - * {@link MoveImageToPosition} with the given {@code variantId}. If both the lists are identical, then no update - * action is needed and hence an empty {@link List} is returned. - * - *

This method expects the two lists two contain the same images only in different order. Otherwise, an - * {@link IllegalArgumentException} would be thrown. - * - *

Note: the solution is still not optimized and may contain {@link MoveImageToPosition} actions - * for items which are already on desired positions (after previous moves in the sequence). This will be - * re-optimized in the next releases. TODO: GITHUB ISSUE#133 - * - * @param variantId the variantId for the {@link MoveImageToPosition} update actions. - * @param oldImages the old list of images. - * @param newImages the new list of images. - * @return a list that contains all the update actions needed, otherwise an empty list if no update actions are - * needed. - */ - public static List buildMoveImageToPositionUpdateActions( - final int variantId, - @Nonnull final List oldImages, - @Nonnull final List newImages) { - final int oldImageListSize = oldImages.size(); - final int newImageListSize = newImages.size(); - if (oldImageListSize != newImageListSize) { - throw new IllegalArgumentException( - format("Old and new image lists must have the same size, but they have %d and %d respectively", - oldImageListSize, newImageListSize)); - } - - // optimization: to avoid multiple linear image index searching in the loop below - create an [image -> index] - // map. This avoids quadratic order of growth of the implementation for large arrays. - final Map imageIndexMap = new HashMap<>(oldImageListSize); - int index = 0; - for (Image newImage : newImages) { - imageIndexMap.put(newImage, index++); - } - - final List updateActions = new ArrayList<>(); - - for (int oldIndex = 0; oldIndex < oldImageListSize; oldIndex++) { - final Image oldImage = oldImages.get(oldIndex); - final Integer newIndex = - ofNullable(imageIndexMap.get(oldImage)) - .orElseThrow(() -> new IllegalArgumentException( - format("Old image [%s] not found in the new images list.", oldImage))); - - if (oldIndex != newIndex) { - updateActions.add( - MoveImageToPosition.ofImageUrlAndVariantId(oldImage.getUrl(), variantId, newIndex, true)); - } - } - return updateActions; + // optimization: to avoid multiple linear image index searching in the loop below - create an + // [image -> index] + // map. This avoids quadratic order of growth of the implementation for large arrays. + final Map imageIndexMap = new HashMap<>(oldImageListSize); + int index = 0; + for (Image newImage : newImages) { + imageIndexMap.put(newImage, index++); } - /** - * Compares the {@link List} of {@link AssetDraft}s of a {@link ProductVariantDraft} and a - * {@link ProductVariant} and returns a {@link List} of {@link UpdateAction}<{@link Product}>. If both the - * {@link ProductVariantDraft} and the {@link ProductVariant} have identical list of assets, then no update action - * is needed and hence an empty {@link List} is returned. In case, the new product variant draft has a list of - * assets in which a duplicate key exists, the error callback is triggered and an empty list is returned. - * - * @param oldProduct old Product, whose variant assets should be updated. - * @param newProduct new product draft, which provides the assets to update. - * @param oldProductVariant the {@link ProductVariant} which should be updated. - * @param newProductVariant the {@link ProductVariantDraft} where we get the new list of assets. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return a list that contains all the update actions needed, otherwise an empty list if no update actions are - * needed. - */ - @Nonnull - public static List> buildProductVariantAssetsUpdateActions( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final ProductVariant oldProductVariant, - @Nonnull final ProductVariantDraft newProductVariant, - @Nonnull final ProductSyncOptions syncOptions) { - - try { - return buildAssetsUpdateActions( - oldProduct, - newProduct, - oldProductVariant.getAssets(), - newProductVariant.getAssets(), - new ProductAssetActionFactory(oldProductVariant.getId(), syncOptions), syncOptions); - } catch (final BuildUpdateActionException exception) { - SyncException syncException = new SyncException(format("Failed to build update actions for the assets " - + "of the product variant with the sku '%s'. Reason: %s", oldProductVariant.getSku(), exception), - exception); - syncOptions.applyErrorCallback(syncException, oldProduct, newProduct, null); - return emptyList(); - } + final List updateActions = new ArrayList<>(); + + for (int oldIndex = 0; oldIndex < oldImageListSize; oldIndex++) { + final Image oldImage = oldImages.get(oldIndex); + final Integer newIndex = + ofNullable(imageIndexMap.get(oldImage)) + .orElseThrow( + () -> + new IllegalArgumentException( + format("Old image [%s] not found in the new images list.", oldImage))); + + if (oldIndex != newIndex) { + updateActions.add( + MoveImageToPosition.ofImageUrlAndVariantId( + oldImage.getUrl(), variantId, newIndex, true)); + } } - - - /** - * Compares the attributes of a {@link ProductVariantDraft} and a {@link ProductVariant} to build either - * {@link io.sphere.sdk.products.commands.updateactions.SetAttribute} or - * {@link io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants} update actions. - * If both the {@link ProductVariantDraft} and the {@link ProductVariant} have identical list of attributes, then - * no update action is needed and hence an empty {@link List} is returned. - * - * @param oldProduct the product that the variants belong to. It is used only in the error - * messages if any. - * @param newProduct the new product draft. - * @param oldProductVariant the {@link ProductVariant} which should be updated. - * @param newProductVariant the {@link ProductVariantDraft} where we get the new list of attributes. - * @param attributesMetaData a map of attribute name -> {@link AttributeMetaData}; which defines attribute - * information: its name, whether a value is required or not and whether it has the - * constraint "SameForAll" or not. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied by - * the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link ProductSyncOptions} - * for more info). - * @return a list that contains all the update actions needed, otherwise an empty list if no update actions are - * needed. - */ - @Nonnull - public static List> buildProductVariantAttributesUpdateActions( - @Nonnull final Product oldProduct, - @Nonnull final ProductDraft newProduct, - @Nonnull final ProductVariant oldProductVariant, - @Nonnull final ProductVariantDraft newProductVariant, - @Nonnull final Map attributesMetaData, - @Nonnull final ProductSyncOptions syncOptions) { - - final String productKey = oldProduct.getKey(); - - final Integer oldProductVariantId = oldProductVariant.getId(); - final List newProductVariantAttributes = newProductVariant.getAttributes(); - final List oldProductVariantAttributes = oldProductVariant.getAttributes(); - - final List> updateActions = buildRemoveUpdateActions(oldProductVariantAttributes, - newProductVariantAttributes, Attribute::getName, AttributeDraft::getName, + return updateActions; + } + + /** + * Compares the {@link List} of {@link AssetDraft}s of a {@link ProductVariantDraft} and a {@link + * ProductVariant} and returns a {@link List} of {@link UpdateAction}<{@link Product}>. If + * both the {@link ProductVariantDraft} and the {@link ProductVariant} have identical list of + * assets, then no update action is needed and hence an empty {@link List} is returned. In case, + * the new product variant draft has a list of assets in which a duplicate key exists, the error + * callback is triggered and an empty list is returned. + * + * @param oldProduct old Product, whose variant assets should be updated. + * @param newProduct new product draft, which provides the assets to update. + * @param oldProductVariant the {@link ProductVariant} which should be updated. + * @param newProductVariant the {@link ProductVariantDraft} where we get the new list of assets. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + @Nonnull + public static List> buildProductVariantAssetsUpdateActions( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final ProductVariant oldProductVariant, + @Nonnull final ProductVariantDraft newProductVariant, + @Nonnull final ProductSyncOptions syncOptions) { + + try { + return buildAssetsUpdateActions( + oldProduct, + newProduct, + oldProductVariant.getAssets(), + newProductVariant.getAssets(), + new ProductAssetActionFactory(oldProductVariant.getId(), syncOptions), + syncOptions); + } catch (final BuildUpdateActionException exception) { + SyncException syncException = + new SyncException( + format( + "Failed to build update actions for the assets " + + "of the product variant with the sku '%s'. Reason: %s", + oldProductVariant.getSku(), exception), + exception); + syncOptions.applyErrorCallback(syncException, oldProduct, newProduct, null); + return emptyList(); + } + } + + /** + * Compares the attributes of a {@link ProductVariantDraft} and a {@link ProductVariant} to build + * either {@link io.sphere.sdk.products.commands.updateactions.SetAttribute} or {@link + * io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants} update actions. If + * both the {@link ProductVariantDraft} and the {@link ProductVariant} have identical list of + * attributes, then no update action is needed and hence an empty {@link List} is returned. + * + * @param oldProduct the product that the variants belong to. It is used only in the error + * messages if any. + * @param newProduct the new product draft. + * @param oldProductVariant the {@link ProductVariant} which should be updated. + * @param newProductVariant the {@link ProductVariantDraft} where we get the new list of + * attributes. + * @param attributesMetaData a map of attribute name -> {@link AttributeMetaData}; which + * defines attribute information: its name, whether a value is required or not and whether it + * has the constraint "SameForAll" or not. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * ProductSyncOptions} for more info). + * @return a list that contains all the update actions needed, otherwise an empty list if no + * update actions are needed. + */ + @Nonnull + public static List> buildProductVariantAttributesUpdateActions( + @Nonnull final Product oldProduct, + @Nonnull final ProductDraft newProduct, + @Nonnull final ProductVariant oldProductVariant, + @Nonnull final ProductVariantDraft newProductVariant, + @Nonnull final Map attributesMetaData, + @Nonnull final ProductSyncOptions syncOptions) { + + final String productKey = oldProduct.getKey(); + + final Integer oldProductVariantId = oldProductVariant.getId(); + final List newProductVariantAttributes = newProductVariant.getAttributes(); + final List oldProductVariantAttributes = oldProductVariant.getAttributes(); + + final List> updateActions = + buildRemoveUpdateActions( + oldProductVariantAttributes, + newProductVariantAttributes, + Attribute::getName, + AttributeDraft::getName, attribute -> { - try { - return buildUnSetAttribute(oldProductVariantId, attribute.getName(), attributesMetaData); - } catch (final BuildUpdateActionException buildUpdateActionException) { - final String errorMessage = format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, attribute.getName(), - newProductVariant.getKey(), productKey, buildUpdateActionException.getMessage()); - syncOptions.applyErrorCallback(new SyncException(errorMessage, - new BuildUpdateActionException(errorMessage, buildUpdateActionException)), oldProduct, - newProduct, null); - return null; - } + try { + return buildUnSetAttribute( + oldProductVariantId, attribute.getName(), attributesMetaData); + } catch (final BuildUpdateActionException buildUpdateActionException) { + final String errorMessage = + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + attribute.getName(), + newProductVariant.getKey(), + productKey, + buildUpdateActionException.getMessage()); + syncOptions.applyErrorCallback( + new SyncException( + errorMessage, + new BuildUpdateActionException(errorMessage, buildUpdateActionException)), + oldProduct, + newProduct, + null); + return null; + } }); - final Map oldAttributesMap = - collectionToMap(oldProductVariantAttributes, Attribute::getName); - - emptyIfNull(newProductVariantAttributes).forEach(newAttribute -> { - if (newAttribute == null) { - final String errorMessage = format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, null, - newProductVariant.getKey(), productKey, NULL_PRODUCT_VARIANT_ATTRIBUTE); - syncOptions.applyErrorCallback(new SyncException(errorMessage, - new BuildUpdateActionException(errorMessage)), oldProduct, newProduct, updateActions); - } else { + final Map oldAttributesMap = + collectionToMap(oldProductVariantAttributes, Attribute::getName); + + emptyIfNull(newProductVariantAttributes) + .forEach( + newAttribute -> { + if (newAttribute == null) { + final String errorMessage = + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + null, + newProductVariant.getKey(), + productKey, + NULL_PRODUCT_VARIANT_ATTRIBUTE); + syncOptions.applyErrorCallback( + new SyncException(errorMessage, new BuildUpdateActionException(errorMessage)), + oldProduct, + newProduct, + updateActions); + } else { final String newAttributeName = newAttribute.getName(); final Attribute matchingOldAttribute = oldAttributesMap.get(newAttributeName); try { - buildProductVariantAttributeUpdateAction(oldProductVariantId, matchingOldAttribute, - newAttribute, attributesMetaData) - .ifPresent(updateActions::add); + buildProductVariantAttributeUpdateAction( + oldProductVariantId, + matchingOldAttribute, + newAttribute, + attributesMetaData) + .ifPresent(updateActions::add); } catch (final BuildUpdateActionException buildUpdateActionException) { - final String errorMessage = format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, newAttributeName, - newProductVariant.getKey(), productKey, buildUpdateActionException.getMessage()); - syncOptions.applyErrorCallback(new SyncException(errorMessage, - new BuildUpdateActionException(errorMessage, buildUpdateActionException))); + final String errorMessage = + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + newAttributeName, + newProductVariant.getKey(), + productKey, + buildUpdateActionException.getMessage()); + syncOptions.applyErrorCallback( + new SyncException( + errorMessage, + new BuildUpdateActionException( + errorMessage, buildUpdateActionException))); } - } - - }); - - return updateActions; - } + } + }); - private static UpdateAction buildUnSetAttribute( - @Nonnull final Integer variantId, - @Nonnull final String attributeName, - @Nonnull final Map attributesMetaData) throws BuildUpdateActionException { + return updateActions; + } - final AttributeMetaData attributeMetaData = attributesMetaData.get(attributeName); + private static UpdateAction buildUnSetAttribute( + @Nonnull final Integer variantId, + @Nonnull final String attributeName, + @Nonnull final Map attributesMetaData) + throws BuildUpdateActionException { - if (attributeMetaData == null) { - final String errorMessage = format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, attributeName); - throw new BuildUpdateActionException(errorMessage); - } + final AttributeMetaData attributeMetaData = attributesMetaData.get(attributeName); - return attributeMetaData.isSameForAll() - ? SetAttributeInAllVariants.ofUnsetAttribute(attributeName, true) : - SetAttribute.ofUnsetAttribute(variantId, attributeName, true); + if (attributeMetaData == null) { + final String errorMessage = format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, attributeName); + throw new BuildUpdateActionException(errorMessage); } - private ProductVariantUpdateActionUtils() { - } + return attributeMetaData.isSameForAll() + ? SetAttributeInAllVariants.ofUnsetAttribute(attributeName, true) + : SetAttribute.ofUnsetAttribute(variantId, attributeName, true); + } + + private ProductVariantUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/products/utils/VariantReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/products/utils/VariantReferenceResolutionUtils.java index 46e46d17af..463bd77443 100644 --- a/src/main/java/com/commercetools/sync/products/utils/VariantReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/products/utils/VariantReferenceResolutionUtils.java @@ -1,5 +1,13 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.commons.utils.AssetReferenceResolutionUtils.mapToAssetDrafts; +import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.commons.utils.SyncUtils.getReferenceWithKeyReplaced; +import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import io.sphere.sdk.channels.Channel; @@ -17,198 +25,218 @@ import io.sphere.sdk.products.attributes.AttributeDraft; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.commons.utils.AssetReferenceResolutionUtils.mapToAssetDrafts; -import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.commons.utils.SyncUtils.getReferenceWithKeyReplaced; -import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; +import javax.annotation.Nonnull; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class VariantReferenceResolutionUtils { - /** - * Returns an {@link List}<{@link ProductVariantDraft}> consisting of the results of applying the - * mapping from {@link ProductVariant} to {@link ProductVariantDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
variants.prices.channel{@link Reference}<{@link Channel}>{@link ResourceIdentifier}<{@link Channel}>
variants.prices.customerGroup *{@link Reference}<{@link CustomerGroup}>{@link Reference}<{@link CustomerGroup}> (with key replaced with id field)
variants.prices.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
variants.assets.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
variants.attributes on {@link List}<{@link Attribute} *{@link Reference}<{@link ProductType}> (example for ProductType){@link Reference}<{@link ProductType}> (with key replaced with id field)
- * - *

Note: The aforementioned references should be expanded with a key. - * Any reference that is not expanded will have its id in place and not replaced by the key will be - * considered as existing resources on the target commercetools project and - * the library will issues an update/create API request without reference resolution. - * - * @param productVariants the product variants with expanded references. - * @return a {@link List} of {@link ProductVariantDraft} built from the - * supplied {@link List} of {@link ProductVariant}. - */ - @Nonnull - public static List mapToProductVariantDrafts( - @Nonnull final List productVariants) { - - return productVariants - .stream() - .filter(Objects::nonNull) - .map(VariantReferenceResolutionUtils::mapToProductVariantDraft) - .collect(toList()); + /** + * Returns an {@link List}<{@link ProductVariantDraft}> consisting of the results of + * applying the mapping from {@link ProductVariant} to {@link ProductVariantDraft} with + * considering reference resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
variants.prices.channel{@link Reference}<{@link Channel}>{@link ResourceIdentifier}<{@link Channel}>
variants.prices.customerGroup *{@link Reference}<{@link CustomerGroup}>{@link Reference}<{@link CustomerGroup}> (with key replaced with id field)
variants.prices.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
variants.assets.custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
variants.attributes on {@link List}<{@link Attribute} *{@link Reference}<{@link ProductType}> (example for ProductType){@link Reference}<{@link ProductType}> (with key replaced with id field)
+ * + *

Note: The aforementioned references should be expanded with a key. Any reference that + * is not expanded will have its id in place and not replaced by the key will be considered as + * existing resources on the target commercetools project and the library will issues an + * update/create API request without reference resolution. + * + * @param productVariants the product variants with expanded references. + * @return a {@link List} of {@link ProductVariantDraft} built from the supplied {@link List} of + * {@link ProductVariant}. + */ + @Nonnull + public static List mapToProductVariantDrafts( + @Nonnull final List productVariants) { + + return productVariants.stream() + .filter(Objects::nonNull) + .map(VariantReferenceResolutionUtils::mapToProductVariantDraft) + .collect(toList()); + } + + @Nonnull + private static ProductVariantDraft mapToProductVariantDraft( + @Nonnull final ProductVariant productVariant) { + return ProductVariantDraftBuilder.of(productVariant) + .prices(mapToPriceDrafts(productVariant)) + .attributes(replaceAttributesReferencesIdsWithKeys(productVariant)) + .assets(mapToAssetDrafts(productVariant.getAssets())) + .build(); + } + + @Nonnull + static List mapToPriceDrafts(@Nonnull final ProductVariant productVariant) { + + return productVariant.getPrices().stream() + .map( + price -> + PriceDraftBuilder.of(price) + .custom(mapToCustomFieldsDraft(price)) + .channel(getResourceIdentifierWithKey(price.getChannel())) + .customerGroup( + getReferenceWithKeyReplaced( + price.getCustomerGroup(), + () -> + CustomerGroup.referenceOfId( + price.getCustomerGroup().getObj().getKey()))) + .build()) + .collect(toList()); + } + + /** + * Takes a product variant that is supposed to have all its attribute product references and + * product set references expanded in order to be able to fetch the keys and replace the reference + * ids with the corresponding keys for the references. This method returns as a result a {@link + * List} of {@link AttributeDraft} that has all product references with keys replacing the ids. + * + *

Any product reference that is not expanded will have it's id in place and not replaced by + * the key. + * + * @param productVariant the product variant to replace its attribute product references ids with + * keys. + * @return a {@link List} of {@link AttributeDraft} that has all product references with keys + * replacing the ids. + */ + @Nonnull + static List replaceAttributesReferencesIdsWithKeys( + @Nonnull final ProductVariant productVariant) { + return productVariant.getAttributes().stream() + .map( + attribute -> + replaceAttributeReferenceIdWithKey(attribute) + .map( + productReference -> + AttributeDraft.of(attribute.getName(), productReference)) + .orElseGet( + () -> + replaceAttributeReferenceSetIdsWithKeys(attribute) + .map( + productReferenceSet -> + AttributeDraft.of(attribute.getName(), productReferenceSet)) + .orElseGet( + () -> + AttributeDraft.of( + attribute.getName(), attribute.getValueAsJsonNode())))) + .collect(toList()); + } + + @SuppressWarnings( + "ConstantConditions") // NPE cannot occur due to being checked in replaceReferenceIdWithKey + static Optional> replaceAttributeReferenceIdWithKey( + @Nonnull final Attribute attribute) { + return getProductReference(attribute) + .map( + productReference -> + getReferenceWithKeyReplaced( + productReference, + () -> Product.referenceOfId(productReference.getObj().getKey()))); + } + + private static Optional> getProductReference( + @Nonnull final Attribute attribute) { + return Optional.of(attribute) + .filter(VariantReferenceResolutionUtils::isProductReference) + .map( + productReferenceAttribute -> + productReferenceAttribute.getValue(AttributeAccess.ofProductReference())); + } + + @SuppressWarnings( + "ConstantConditions") // NPE cannot occur due to being checked in replaceReferenceIdWithKey + static Optional>> replaceAttributeReferenceSetIdsWithKeys( + @Nonnull final Attribute attribute) { + return getProductReferenceSet(attribute) + .map( + productReferenceSet -> + productReferenceSet.stream() + .map( + productReference -> + getReferenceWithKeyReplaced( + productReference, + () -> Product.referenceOfId(productReference.getObj().getKey()))) + .collect(toSet())); + } + + private static Optional>> getProductReferenceSet( + @Nonnull final Attribute attribute) { + return Optional.of(attribute) + .filter(VariantReferenceResolutionUtils::isProductReferenceSet) + .map( + productReferenceSetAttribute -> + productReferenceSetAttribute.getValue(AttributeAccess.ofProductReferenceSet())); + } + + static boolean isProductReference(@Nonnull final Attribute attribute) { + final JsonNode valueAsJsonNode = attribute.getValueAsJsonNode(); + return !(valueAsJsonNode instanceof ArrayNode) && isValueAProductReference(valueAsJsonNode); + } + + static boolean isProductReferenceSet(@Nonnull final Attribute attribute) { + final JsonNode valueAsJsonNode = attribute.getValueAsJsonNode(); + + if (valueAsJsonNode instanceof ArrayNode) { + final Iterator setIterator = valueAsJsonNode.elements(); + + if (setIterator.hasNext()) { + return isValueAProductReference(setIterator.next()); + } } - @Nonnull - private static ProductVariantDraft mapToProductVariantDraft(@Nonnull final ProductVariant productVariant) { - return ProductVariantDraftBuilder.of(productVariant) - .prices(mapToPriceDrafts(productVariant)) - .attributes(replaceAttributesReferencesIdsWithKeys(productVariant)) - .assets(mapToAssetDrafts(productVariant.getAssets())) - .build(); - } + return false; + } - @Nonnull - static List mapToPriceDrafts(@Nonnull final ProductVariant productVariant) { - - return productVariant.getPrices().stream().map(price -> PriceDraftBuilder - .of(price) - .custom(mapToCustomFieldsDraft(price)) - .channel(getResourceIdentifierWithKey(price.getChannel())) - .customerGroup( - getReferenceWithKeyReplaced(price.getCustomerGroup(), () -> - CustomerGroup.referenceOfId(price.getCustomerGroup().getObj().getKey()))) - .build()).collect(toList()); + private static boolean isValueAProductReference(@Nonnull final JsonNode valueAsJsonNode) { + if (valueAsJsonNode.isContainerNode()) { + final JsonNode typeIdNode = valueAsJsonNode.get(REFERENCE_TYPE_ID_FIELD); + return typeIdNode != null && Product.referenceTypeId().equals(typeIdNode.asText()); } + return false; + } - /** - * Takes a product variant that is supposed to have all its attribute product references and product set references - * expanded in order to be able to fetch the keys and replace the reference ids with the corresponding keys for the - * references. This method returns as a result a {@link List} of {@link AttributeDraft} that has all product - * references with keys replacing the ids. - * - *

Any product reference that is not expanded will have it's id in place and not replaced by the key. - * - * @param productVariant the product variant to replace its attribute product references ids with keys. - * @return a {@link List} of {@link AttributeDraft} that has all product references with keys replacing the ids. - */ - @Nonnull - static List replaceAttributesReferencesIdsWithKeys(@Nonnull final ProductVariant productVariant) { - return productVariant.getAttributes().stream() - .map(attribute -> replaceAttributeReferenceIdWithKey(attribute) - .map(productReference -> AttributeDraft.of(attribute.getName(), productReference)) - .orElseGet(() -> replaceAttributeReferenceSetIdsWithKeys(attribute) - .map(productReferenceSet -> - AttributeDraft.of(attribute.getName(), productReferenceSet)) - .orElseGet(() -> - AttributeDraft.of(attribute.getName(), attribute.getValueAsJsonNode())))) - .collect(toList()); - } - - - @SuppressWarnings("ConstantConditions") // NPE cannot occur due to being checked in replaceReferenceIdWithKey - static Optional> replaceAttributeReferenceIdWithKey(@Nonnull final Attribute attribute) { - return getProductReference(attribute) - .map(productReference -> - getReferenceWithKeyReplaced(productReference, - () -> Product.referenceOfId(productReference.getObj().getKey())) - ); - } - - private static Optional> getProductReference(@Nonnull final Attribute attribute) { - return Optional.of(attribute) - .filter(VariantReferenceResolutionUtils::isProductReference) - .map(productReferenceAttribute -> productReferenceAttribute - .getValue(AttributeAccess.ofProductReference())); - } - - @SuppressWarnings("ConstantConditions") // NPE cannot occur due to being checked in replaceReferenceIdWithKey - static Optional>> replaceAttributeReferenceSetIdsWithKeys( - @Nonnull final Attribute attribute) { - return getProductReferenceSet(attribute).map(productReferenceSet -> - productReferenceSet.stream() - .map(productReference -> - getReferenceWithKeyReplaced(productReference, - () -> Product.referenceOfId(productReference.getObj().getKey())) - ) - .collect(toSet())); - } - - private static Optional>> getProductReferenceSet(@Nonnull final Attribute attribute) { - return Optional.of(attribute) - .filter(VariantReferenceResolutionUtils::isProductReferenceSet) - .map(productReferenceSetAttribute -> productReferenceSetAttribute - .getValue(AttributeAccess.ofProductReferenceSet())); - } - - static boolean isProductReference(@Nonnull final Attribute attribute) { - final JsonNode valueAsJsonNode = attribute.getValueAsJsonNode(); - return !(valueAsJsonNode instanceof ArrayNode) && isValueAProductReference(valueAsJsonNode); - } - - static boolean isProductReferenceSet(@Nonnull final Attribute attribute) { - final JsonNode valueAsJsonNode = attribute.getValueAsJsonNode(); - - if (valueAsJsonNode instanceof ArrayNode) { - final Iterator setIterator = valueAsJsonNode.elements(); - - if (setIterator.hasNext()) { - return isValueAProductReference(setIterator.next()); - } - } - - return false; - } - - private static boolean isValueAProductReference(@Nonnull final JsonNode valueAsJsonNode) { - if (valueAsJsonNode.isContainerNode()) { - final JsonNode typeIdNode = valueAsJsonNode.get(REFERENCE_TYPE_ID_FIELD); - return typeIdNode != null && Product.referenceTypeId().equals(typeIdNode.asText()); - } - return false; - } - - private VariantReferenceResolutionUtils() { - } + private VariantReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/producttypes/ProductTypeSync.java b/src/main/java/com/commercetools/sync/producttypes/ProductTypeSync.java index 3ce979c7a3..0e8fc55988 100644 --- a/src/main/java/com/commercetools/sync/producttypes/ProductTypeSync.java +++ b/src/main/java/com/commercetools/sync/producttypes/ProductTypeSync.java @@ -1,5 +1,14 @@ package com.commercetools.sync.producttypes; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static com.commercetools.sync.producttypes.helpers.ProductTypeBatchValidator.getProductTypeKey; +import static com.commercetools.sync.producttypes.utils.ProductTypeSyncUtils.buildActions; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.InvalidReferenceException; import com.commercetools.sync.commons.exceptions.SyncException; @@ -16,10 +25,6 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.ProductTypeDraftBuilder; import io.sphere.sdk.producttypes.commands.updateactions.AddAttributeDefinition; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -31,634 +36,684 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static com.commercetools.sync.producttypes.helpers.ProductTypeBatchValidator.getProductTypeKey; -import static com.commercetools.sync.producttypes.utils.ProductTypeSyncUtils.buildActions; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; - -/** - * This class syncs product type drafts with the corresponding product types in the CTP project. - */ -public class ProductTypeSync extends BaseSync { - private static final String CTP_PRODUCT_TYPE_FETCH_FAILED = "Failed to fetch existing product types with keys:" - + " '%s'."; - private static final String CTP_PRODUCT_TYPE_UPDATE_FAILED = "Failed to update product type with key: '%s'." - + " Reason: %s"; - private static final String FAILED_TO_PROCESS = "Failed to process the productTypeDraft with key:'%s'. Reason: %s"; - - private final ProductTypeService productTypeService; - private final ProductTypeReferenceResolver referenceResolver; - private final ProductTypeBatchValidator batchValidator; - - /** - * The following set ({@code readyToResolve}) is thread-safe because it is accessed/modified in a concurrent - * context, specifically when creating productTypes in parallel in - * {@link #applyCallbackAndCreate(ProductTypeDraft)}. It has a local scope within every batch execution, which - * means that it is re-initialized on every {@link #processBatch(List)} call. - */ - private ConcurrentHashMap.KeySetView readyToResolve; - - public ProductTypeSync(@Nonnull final ProductTypeSyncOptions productTypeSyncOptions) { - this(productTypeSyncOptions, new ProductTypeServiceImpl(productTypeSyncOptions)); - } - - /** - * Takes a {@link ProductTypeSyncOptions} and a {@link ProductTypeService} instances to instantiate - * a new {@link ProductTypeSync} instance that could be used to sync productType drafts in the CTP project specified - * in the injected {@link ProductTypeSyncOptions} instance. - * - *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and passed to. - * - * @param productTypeSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param productTypeService the type service which is responsible for fetching/caching the Types from the CTP - * project. - */ - ProductTypeSync(@Nonnull final ProductTypeSyncOptions productTypeSyncOptions, - @Nonnull final ProductTypeService productTypeService) { - - super(new ProductTypeSyncStatistics(), productTypeSyncOptions); - this.productTypeService = productTypeService; - this.referenceResolver = new ProductTypeReferenceResolver(getSyncOptions(), productTypeService); - this.batchValidator = new ProductTypeBatchValidator(getSyncOptions(), getStatistics()); - } - - /** - * Iterates through the whole {@code productTypeDrafts} list and accumulates its valid drafts to batches. - * Every batch is then processed by {@link ProductTypeSync#processBatch(List)}. - * - *

Inherited doc: - * {@inheritDoc} - * - * @param productTypeDrafts {@link List} of {@link ProductTypeDraft}'s that would be synced into CTP project. - * @return {@link CompletionStage} with {@link ProductTypeSyncStatistics} holding statistics of all sync - * processes performed by this sync instance. - */ - @Override - protected CompletionStage process( - @Nonnull final List productTypeDrafts) { - - final List> batches = batchElements(productTypeDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, CompletableFuture.completedFuture(statistics)); +/** This class syncs product type drafts with the corresponding product types in the CTP project. */ +public class ProductTypeSync + extends BaseSync { + private static final String CTP_PRODUCT_TYPE_FETCH_FAILED = + "Failed to fetch existing product types with keys:" + " '%s'."; + private static final String CTP_PRODUCT_TYPE_UPDATE_FAILED = + "Failed to update product type with key: '%s'." + " Reason: %s"; + private static final String FAILED_TO_PROCESS = + "Failed to process the productTypeDraft with key:'%s'. Reason: %s"; + + private final ProductTypeService productTypeService; + private final ProductTypeReferenceResolver referenceResolver; + private final ProductTypeBatchValidator batchValidator; + + /** + * The following set ({@code readyToResolve}) is thread-safe because it is accessed/modified in a + * concurrent context, specifically when creating productTypes in parallel in {@link + * #applyCallbackAndCreate(ProductTypeDraft)}. It has a local scope within every batch execution, + * which means that it is re-initialized on every {@link #processBatch(List)} call. + */ + private ConcurrentHashMap.KeySetView readyToResolve; + + public ProductTypeSync(@Nonnull final ProductTypeSyncOptions productTypeSyncOptions) { + this(productTypeSyncOptions, new ProductTypeServiceImpl(productTypeSyncOptions)); + } + + /** + * Takes a {@link ProductTypeSyncOptions} and a {@link ProductTypeService} instances to + * instantiate a new {@link ProductTypeSync} instance that could be used to sync productType + * drafts in the CTP project specified in the injected {@link ProductTypeSyncOptions} instance. + * + *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and + * passed to. + * + * @param productTypeSyncOptions the container of all the options of the sync process including + * the CTP project client and/or configuration and other sync-specific options. + * @param productTypeService the type service which is responsible for fetching/caching the Types + * from the CTP project. + */ + ProductTypeSync( + @Nonnull final ProductTypeSyncOptions productTypeSyncOptions, + @Nonnull final ProductTypeService productTypeService) { + + super(new ProductTypeSyncStatistics(), productTypeSyncOptions); + this.productTypeService = productTypeService; + this.referenceResolver = new ProductTypeReferenceResolver(getSyncOptions(), productTypeService); + this.batchValidator = new ProductTypeBatchValidator(getSyncOptions(), getStatistics()); + } + + /** + * Iterates through the whole {@code productTypeDrafts} list and accumulates its valid drafts to + * batches. Every batch is then processed by {@link ProductTypeSync#processBatch(List)}. + * + *

Inherited doc: {@inheritDoc} + * + * @param productTypeDrafts {@link List} of {@link ProductTypeDraft}'s that would be synced into + * CTP project. + * @return {@link CompletionStage} with {@link ProductTypeSyncStatistics} holding statistics of + * all sync processes performed by this sync instance. + */ + @Override + protected CompletionStage process( + @Nonnull final List productTypeDrafts) { + + final List> batches = + batchElements(productTypeDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, CompletableFuture.completedFuture(statistics)); + } + + /** + * This method first creates a new {@link Set} of valid {@link ProductTypeDraft} elements. For + * more on the rules of validation, check: {@link + * ProductTypeBatchValidator#validateAndCollectReferencedKeys}. Using the resulting set of {@code + * validProductTypeDrafts}, the matching productTypes in the target CTP project are fetched then + * the method {@link ProductTypeSync#syncBatch(Set, Set, Map)} is called to perform the sync + * (update or create requests accordingly) on the target project. + * + *

After the batch is synced, the method resolves all missing nested references that could have + * been created after execution of sync of batch. For more info check {@link + * ProductTypeSync#resolveMissingNestedReferences(Map)}. + * + *

In case of error during of fetching of existing productTypes, the error callback will be + * triggered. And the sync process would stop for the given batch. + * + * @param batch batch of drafts that need to be synced + * @return a {@link CompletionStage} containing an instance of {@link ProductTypeSyncStatistics} + * which contains information about the result of syncing the supplied batch to the target + * project. + */ + @Override + protected CompletionStage processBatch( + @Nonnull final List batch) { + + readyToResolve = ConcurrentHashMap.newKeySet(); + + final ImmutablePair, Set> result = + batchValidator.validateAndCollectReferencedKeys(batch); + + final Set validDrafts = result.getLeft(); + if (validDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return CompletableFuture.completedFuture(statistics); } - /** - * This method first creates a new {@link Set} of valid {@link ProductTypeDraft} elements. For more on the rules of - * validation, check: {@link ProductTypeBatchValidator#validateAndCollectReferencedKeys}. Using the resulting set of - * {@code validProductTypeDrafts}, the matching productTypes in the target CTP project are fetched then the method - * {@link ProductTypeSync#syncBatch(Set, Set, Map)} is called to perform the sync (update or create - * requests accordingly) on the target project. - * - *

After the batch is synced, the method resolves all missing nested references that could have been created - * after execution of sync of batch. - * For more info check {@link ProductTypeSync#resolveMissingNestedReferences(Map)}. - * - *

In case of error during of fetching of existing productTypes, the error callback will be triggered. - * And the sync process would stop for the given batch. - *

- * - * @param batch batch of drafts that need to be synced - * @return a {@link CompletionStage} containing an instance - * of {@link ProductTypeSyncStatistics} which contains information about the result of syncing the supplied - * batch to the target project. - */ - @Override - protected CompletionStage processBatch(@Nonnull final List batch) { - - readyToResolve = ConcurrentHashMap.newKeySet(); - - final ImmutablePair, Set> result = - batchValidator.validateAndCollectReferencedKeys(batch); - - final Set validDrafts = result.getLeft(); - if (validDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return CompletableFuture.completedFuture(statistics); - } - - final Set productTypeKeys = result.getRight(); - - return productTypeService - .cacheKeysToIds(productTypeKeys) - .handle(ImmutablePair::new) - .thenCompose(cachingResponse -> { - - final Map keyToIdCache = cachingResponse.getKey(); - final Throwable cachingException = cachingResponse.getValue(); - - if (cachingException != null) { - handleError(new SyncException("Failed to build a cache of keys to ids.", cachingException), - validDrafts.size()); - return CompletableFuture.completedFuture(null); - } - - - final Set batchDraftKeys = validDrafts - .stream() - .map(ProductTypeDraft::getKey) - .collect(Collectors.toSet()); - - return productTypeService - .fetchMatchingProductTypesByKeys(batchDraftKeys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { + final Set productTypeKeys = result.getRight(); + + return productTypeService + .cacheKeysToIds(productTypeKeys) + .handle(ImmutablePair::new) + .thenCompose( + cachingResponse -> { + final Map keyToIdCache = cachingResponse.getKey(); + final Throwable cachingException = cachingResponse.getValue(); + + if (cachingException != null) { + handleError( + new SyncException("Failed to build a cache of keys to ids.", cachingException), + validDrafts.size()); + return CompletableFuture.completedFuture(null); + } + + final Set batchDraftKeys = + validDrafts.stream().map(ProductTypeDraft::getKey).collect(Collectors.toSet()); + + return productTypeService + .fetchMatchingProductTypesByKeys(batchDraftKeys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { final Set matchingProductTypes = fetchResponse.getKey(); final Throwable exception = fetchResponse.getValue(); if (exception != null) { - final String errorMessage = format(CTP_PRODUCT_TYPE_FETCH_FAILED, batchDraftKeys); - handleError(new SyncException(errorMessage, exception), batchDraftKeys.size()); - return CompletableFuture.completedFuture(null); + final String errorMessage = + format(CTP_PRODUCT_TYPE_FETCH_FAILED, batchDraftKeys); + handleError( + new SyncException(errorMessage, exception), batchDraftKeys.size()); + return CompletableFuture.completedFuture(null); } else { - return syncBatch(matchingProductTypes, validDrafts, keyToIdCache) - .thenApply(ignoredResult -> buildProductTypesToUpdateMap()) - .thenCompose(this::resolveMissingNestedReferences); + return syncBatch(matchingProductTypes, validDrafts, keyToIdCache) + .thenApply(ignoredResult -> buildProductTypesToUpdateMap()) + .thenCompose(this::resolveMissingNestedReferences); } - }); + }); }) - .thenApply(ignored -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignored -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); - - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed product types to sync. - * - * - * @param syncException The exception that called caused the failure. - * @param failedTimes The number of times that the failed product types counter is incremented. - */ - private void handleError(@Nonnull final SyncException syncException, final int failedTimes) { - syncOptions.applyErrorCallback(syncException); - statistics.incrementFailed(failedTimes); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed product types to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - * @param exception The exception that called caused the failure, if any. - * @param failedTimes The number of times that the failed product types counter is incremented. - * @param oldProductType existing product type that could be updated. - * @param newProductType draft containing data that could differ from data in {@code oldProductType}. - * @param updateActions the update actions to update the {@link ProductType} with. - */ - private void handleError(@Nonnull final String errorMessage, @Nullable final Throwable exception, - final int failedTimes, - @Nullable final ProductType oldProductType, - @Nullable final ProductTypeDraft newProductType, - @Nullable final List> updateActions) { - SyncException syncException = exception != null ? new SyncException(errorMessage, exception) + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed product types to sync. + * + * @param syncException The exception that called caused the failure. + * @param failedTimes The number of times that the failed product types counter is incremented. + */ + private void handleError(@Nonnull final SyncException syncException, final int failedTimes) { + syncOptions.applyErrorCallback(syncException); + statistics.incrementFailed(failedTimes); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed product types to sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + * @param exception The exception that called caused the failure, if any. + * @param failedTimes The number of times that the failed product types counter is incremented. + * @param oldProductType existing product type that could be updated. + * @param newProductType draft containing data that could differ from data in {@code + * oldProductType}. + * @param updateActions the update actions to update the {@link ProductType} with. + */ + private void handleError( + @Nonnull final String errorMessage, + @Nullable final Throwable exception, + final int failedTimes, + @Nullable final ProductType oldProductType, + @Nullable final ProductTypeDraft newProductType, + @Nullable final List> updateActions) { + SyncException syncException = + exception != null + ? new SyncException(errorMessage, exception) : new SyncException(errorMessage); - syncOptions.applyErrorCallback(syncException, oldProductType, newProductType, updateActions); - statistics.incrementFailed(failedTimes); - } - - /** - * Given a set of product type drafts, attempts to sync the drafts with the existing products types in the target - * CTP project. The product type and the draft are considered to match if they have the same key. - * - * - *

Note: In order to support syncing product types with nested references in any order, this method will - * remove any attribute which contains a nested reference on the drafts and keep track of it to be resolved as - * soon as the referenced product type becomes available. - * - * @param oldProductTypes old product types. - * @param newProductTypes drafts that need to be synced. - * @return a {@link CompletionStage} which contains an empty result after execution of the update - */ - @Nonnull - private CompletionStage syncBatch( - @Nonnull final Set oldProductTypes, - @Nonnull final Set newProductTypes, - @Nonnull final Map keyToIdCache) { - - final Map oldProductTypeMap = - oldProductTypes.stream().collect(toMap(ProductType::getKey, identity())); - - return CompletableFuture.allOf(newProductTypes - .stream() - .map(newProductType -> removeAndKeepTrackOfMissingNestedAttributes(newProductType, keyToIdCache)) - .map(draftWithoutMissingRefAttrs -> referenceResolver.resolveReferences(draftWithoutMissingRefAttrs) - .thenCompose(resolvedDraft -> syncDraft(oldProductTypeMap, resolvedDraft)) - .exceptionally(completionException -> { - final String errorMessage = format(FAILED_TO_PROCESS, - draftWithoutMissingRefAttrs.getKey(), - completionException.getMessage()); - handleError(new SyncException(errorMessage, completionException), 1); - return null; - }) - ) + syncOptions.applyErrorCallback(syncException, oldProductType, newProductType, updateActions); + statistics.incrementFailed(failedTimes); + } + + /** + * Given a set of product type drafts, attempts to sync the drafts with the existing products + * types in the target CTP project. The product type and the draft are considered to match if they + * have the same key. + * + *

Note: In order to support syncing product types with nested references in any order, this + * method will remove any attribute which contains a nested reference on the drafts and keep track + * of it to be resolved as soon as the referenced product type becomes available. + * + * @param oldProductTypes old product types. + * @param newProductTypes drafts that need to be synced. + * @return a {@link CompletionStage} which contains an empty result after execution of the update + */ + @Nonnull + private CompletionStage syncBatch( + @Nonnull final Set oldProductTypes, + @Nonnull final Set newProductTypes, + @Nonnull final Map keyToIdCache) { + + final Map oldProductTypeMap = + oldProductTypes.stream().collect(toMap(ProductType::getKey, identity())); + + return CompletableFuture.allOf( + newProductTypes.stream() + .map( + newProductType -> + removeAndKeepTrackOfMissingNestedAttributes(newProductType, keyToIdCache)) + .map( + draftWithoutMissingRefAttrs -> + referenceResolver + .resolveReferences(draftWithoutMissingRefAttrs) + .thenCompose(resolvedDraft -> syncDraft(oldProductTypeMap, resolvedDraft)) + .exceptionally( + completionException -> { + final String errorMessage = + format( + FAILED_TO_PROCESS, + draftWithoutMissingRefAttrs.getKey(), + completionException.getMessage()); + handleError(new SyncException(errorMessage, completionException), 1); + return null; + })) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); + } + + /** + * First, cleans up all occurrences of {@code productTypeDraft}'s key waiting in + * statistics#putMissingNestedProductType. This is because it should not be existing there, and if + * it is then the values are outdated. This is to support the case, if an already visited + * attribute is supplied again in a later batch (maybe with a different reference or a reference + * that doesn't exist anymore). + * + *

Then, makes a copy of {@code productTypeDraft} and then goes through all its attribute + * definition drafts. For each attribute, attempts to find a nested product type in its attribute + * type, if it has a nested type. It checks if the key, of the productType reference, is cached in + * {@code keyToIdCache}. If it is, then it means the referenced product type exists in the target + * project, so there is no need to remove or keep track of it. If it is not, it means it doesn't + * exist yet and needs to be tracked as a missing reference and also remove this attribute + * definition from the supplied {@code draftCopy} to be able to create product type without this + * attribute containing the missing reference. + * + * @param productTypeDraft the productTypeDraft containing the attribute which should be updated + * by removing the attribute which contains the missing reference. + * @param keyToIdCache a map of productType key to id. It represents a cache of the existing + * productTypes in the target project. + */ + @SuppressWarnings( + "ConstantConditions") // since the batch is validate before, key is assured to be non-blank + // here. + @Nonnull + private ProductTypeDraft removeAndKeepTrackOfMissingNestedAttributes( + @Nonnull final ProductTypeDraft productTypeDraft, + @Nonnull final Map keyToIdCache) { + + statistics.removeReferencingProductTypeKey(productTypeDraft.getKey()); + + final List attributeDefinitionDrafts = + productTypeDraft.getAttributes(); + if (attributeDefinitionDrafts == null || attributeDefinitionDrafts.isEmpty()) { + return productTypeDraft; } - - /** - * First, cleans up all occurrences of {@code productTypeDraft}'s key waiting in - * statistics#putMissingNestedProductType. This is because it should not be existing there, and if it is then the - * values are outdated. This is to support the case, if an already visited attribute is supplied again in a later - * batch (maybe with a different reference or a reference that doesn't exist anymore). - * - * - *

Then, makes a copy of {@code productTypeDraft} and then goes through all its attribute definition drafts. - * For each attribute, attempts to find a nested product type in its attribute type, if it has a - * nested type. It checks if the key, of the productType reference, is cached in {@code keyToIdCache}. If it is, - * then it means the referenced product type exists in the target project, so there is no need to remove or keep - * track of it. If it is not, it means it doesn't exist yet and needs to be tracked as a - * missing reference and also remove this attribute definition from the supplied {@code draftCopy} to be able to - * create product type without this attribute containing the missing reference. - * - * - * @param productTypeDraft the productTypeDraft containing the attribute which should be updated by removing - * the attribute which contains the missing reference. - * @param keyToIdCache a map of productType key to id. It represents a cache of the existing - * productTypes in the target project. - */ - @SuppressWarnings("ConstantConditions") // since the batch is validate before, key is assured to be non-blank here. - @Nonnull - private ProductTypeDraft removeAndKeepTrackOfMissingNestedAttributes( - @Nonnull final ProductTypeDraft productTypeDraft, - @Nonnull final Map keyToIdCache) { - - statistics.removeReferencingProductTypeKey(productTypeDraft.getKey()); - - final List attributeDefinitionDrafts = productTypeDraft.getAttributes(); - if (attributeDefinitionDrafts == null || attributeDefinitionDrafts.isEmpty()) { - return productTypeDraft; - } - - // copies to avoid mutation of attributes array supplied by user. - final ProductTypeDraft draftCopy = ProductTypeDraftBuilder - .of(productTypeDraft) + // copies to avoid mutation of attributes array supplied by user. + final ProductTypeDraft draftCopy = + ProductTypeDraftBuilder.of(productTypeDraft) .attributes(new ArrayList<>(productTypeDraft.getAttributes())) .build(); - for (AttributeDefinitionDraft attributeDefinitionDraft : attributeDefinitionDrafts) { - if (attributeDefinitionDraft != null) { - removeAndKeepTrackOfMissingNestedAttribute(attributeDefinitionDraft, draftCopy, keyToIdCache); - } - } - - return draftCopy; + for (AttributeDefinitionDraft attributeDefinitionDraft : attributeDefinitionDrafts) { + if (attributeDefinitionDraft != null) { + removeAndKeepTrackOfMissingNestedAttribute( + attributeDefinitionDraft, draftCopy, keyToIdCache); + } } - /** - * Attempts to find a nested product type in the attribute type of {@code attributeDefinitionDraft}, if it has a - * nested type. It checks if the key, of the productType reference, is cached in {@code keyToIdCache}. If it is, - * then it means the referenced product type exists in the target project, so there is no need to remove or keep - * track of it. However, if it is not, it means it doesn't exist yet, which means we need to keep track of it as a - * missing reference and also remove this attribute definition from the supplied {@code draftCopy} to be able to - * create product type without this attribute containing the missing reference. - * - * - *

Note: This method mutates in the supplied {@code productTypeDraft} attribute definition list by removing - * the attribute containing a missing reference. - * - * @param attributeDefinitionDraft the attribute definition being checked for any product references. - * @param productTypeDraft the productTypeDraft containing the attribute which should be updated by removing - * the attribute which contains the missing reference. - * @param keyToIdCache a map of productType key to id. It represents a cache of the existing - * productTypes in the target project. - */ - @SuppressWarnings("ConstantConditions") // since the batch is validate before, key is assured to be non-blank here. - private void removeAndKeepTrackOfMissingNestedAttribute( - @Nonnull final AttributeDefinitionDraft attributeDefinitionDraft, - @Nonnull final ProductTypeDraft productTypeDraft, - @Nonnull final Map keyToIdCache) { - - final AttributeType attributeType = attributeDefinitionDraft.getAttributeType(); - - try { - getProductTypeKey(attributeType) - .ifPresent(key -> { - if (!keyToIdCache.keySet().contains(key)) { - productTypeDraft.getAttributes().remove(attributeDefinitionDraft); - statistics.putMissingNestedProductType( - key, productTypeDraft.getKey(), attributeDefinitionDraft); - } - }); - } catch (InvalidReferenceException invalidReferenceException) { - handleError(new SyncException("This exception is unexpectedly thrown since the draft batch has been" - + "already validated for blank keys at an earlier stage, which means this draft should" - + " have a valid reference. Please communicate this error with the maintainer of the library.", - invalidReferenceException), 1); - } - } - - @Nonnull - private CompletionStage syncDraft( - @Nonnull final Map oldProductTypeMap, - @Nonnull final ProductTypeDraft newProductTypeDraft) { - - final ProductType oldProductType = oldProductTypeMap.get(newProductTypeDraft.getKey()); - - return ofNullable(oldProductType) - .map(productType -> buildActionsAndUpdate(oldProductType, newProductTypeDraft)) - .orElseGet(() -> applyCallbackAndCreate(newProductTypeDraft)); - } - - - /** - * Every key in the {@code readyToResolve} set, represents a product type which is now existing on the target - * project and can now be resolved on any of the referencing product types which were kept track of in - * {@link ProductTypeSyncStatistics#missingNestedProductTypes} map. - * - *

Based on the contents of the {@link ProductTypeSyncStatistics#missingNestedProductTypes} and the - * {@code readyToResolve} set, this method builds a map of product type keys pointing to a set of attribute - * definition drafts which are now ready to be added for this product type. The purpose of this is to aggregate - * all the definitions that are needed to be added to every product type together so that we can issue them - * together in the same update request. - * - * @return a map of product type keys pointing to a set of attribute definition drafts which are now ready to be - * added for this product type. - */ - @Nonnull - private Map> buildProductTypesToUpdateMap() { - - final Map> productTypesToUpdate = new HashMap<>(); - - readyToResolve - .forEach(readyToResolveProductTypeKey -> { - final ConcurrentHashMap> - referencingProductTypes = statistics - .getProductTypeKeysWithMissingParents() - .get(readyToResolveProductTypeKey); - - if (referencingProductTypes != null) { - referencingProductTypes - .forEach((productTypeKey, attributes) -> { - - final Set attributeDefinitionsToAdd = - productTypesToUpdate.get(productTypeKey); - - if (attributeDefinitionsToAdd != null) { - attributeDefinitionsToAdd.addAll(attributes); - } else { - productTypesToUpdate.put(productTypeKey, attributes); - } - }); + return draftCopy; + } + + /** + * Attempts to find a nested product type in the attribute type of {@code + * attributeDefinitionDraft}, if it has a nested type. It checks if the key, of the productType + * reference, is cached in {@code keyToIdCache}. If it is, then it means the referenced product + * type exists in the target project, so there is no need to remove or keep track of it. However, + * if it is not, it means it doesn't exist yet, which means we need to keep track of it as a + * missing reference and also remove this attribute definition from the supplied {@code draftCopy} + * to be able to create product type without this attribute containing the missing reference. + * + *

Note: This method mutates in the supplied {@code productTypeDraft} attribute definition list + * by removing the attribute containing a missing reference. + * + * @param attributeDefinitionDraft the attribute definition being checked for any product + * references. + * @param productTypeDraft the productTypeDraft containing the attribute which should be updated + * by removing the attribute which contains the missing reference. + * @param keyToIdCache a map of productType key to id. It represents a cache of the existing + * productTypes in the target project. + */ + @SuppressWarnings( + "ConstantConditions") // since the batch is validate before, key is assured to be non-blank + // here. + private void removeAndKeepTrackOfMissingNestedAttribute( + @Nonnull final AttributeDefinitionDraft attributeDefinitionDraft, + @Nonnull final ProductTypeDraft productTypeDraft, + @Nonnull final Map keyToIdCache) { + + final AttributeType attributeType = attributeDefinitionDraft.getAttributeType(); + + try { + getProductTypeKey(attributeType) + .ifPresent( + key -> { + if (!keyToIdCache.keySet().contains(key)) { + productTypeDraft.getAttributes().remove(attributeDefinitionDraft); + statistics.putMissingNestedProductType( + key, productTypeDraft.getKey(), attributeDefinitionDraft); } - }); - - return productTypesToUpdate; + }); + } catch (InvalidReferenceException invalidReferenceException) { + handleError( + new SyncException( + "This exception is unexpectedly thrown since the draft batch has been" + + "already validated for blank keys at an earlier stage, which means this draft should" + + " have a valid reference. Please communicate this error with the maintainer of the library.", + invalidReferenceException), + 1); } - - /** - * Given a map of product type keys pointing to a set of attribute definition drafts which are now ready to be added - * for this product type. This method first converts the drafts to {@link AddAttributeDefinition} actions in which - * the reference id value (which is a key) is resolved to an actual UUID of the product type key pointed by this - * key. Then, for each product type, the method issues an update request containing all the actions. - * - * @return a {@link CompletionStage} which contains an empty result after execution of all the update requests. - */ - @Nonnull - private CompletionStage resolveMissingNestedReferences( - @Nonnull final Map> productTypesToUpdate) { - - final Set keys = productTypesToUpdate.keySet(); - return productTypeService - .fetchMatchingProductTypesByKeys(keys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - - final Set matchingProductTypes = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - if (exception != null) { - final String errorMessage = format(CTP_PRODUCT_TYPE_FETCH_FAILED, keys); - syncOptions.applyErrorCallback(new SyncException(errorMessage, exception)); - return CompletableFuture.completedFuture(null); - } else { - final Map keyToProductType = - matchingProductTypes - .stream() - .collect(Collectors.toMap(ProductType::getKey, productType -> productType)); - return CompletableFuture.allOf( - productTypesToUpdate - .entrySet() - .stream() - .map(entry -> { - final String productTypeToUpdateKey = entry.getKey(); - final Set attributeDefinitionDrafts = entry.getValue(); - - final List> actionsWithResolvedReferences = - draftsToActions(attributeDefinitionDrafts); - - final ProductType productTypeToUpdate = keyToProductType.get(productTypeToUpdateKey); - - return resolveMissingNestedReferences( - productTypeToUpdate, - actionsWithResolvedReferences); - + } + + @Nonnull + private CompletionStage syncDraft( + @Nonnull final Map oldProductTypeMap, + @Nonnull final ProductTypeDraft newProductTypeDraft) { + + final ProductType oldProductType = oldProductTypeMap.get(newProductTypeDraft.getKey()); + + return ofNullable(oldProductType) + .map(productType -> buildActionsAndUpdate(oldProductType, newProductTypeDraft)) + .orElseGet(() -> applyCallbackAndCreate(newProductTypeDraft)); + } + + /** + * Every key in the {@code readyToResolve} set, represents a product type which is now existing on + * the target project and can now be resolved on any of the referencing product types which were + * kept track of in {@link ProductTypeSyncStatistics#missingNestedProductTypes} map. + * + *

Based on the contents of the {@link ProductTypeSyncStatistics#missingNestedProductTypes} and + * the {@code readyToResolve} set, this method builds a map of product type keys pointing to a set + * of attribute definition drafts which are now ready to be added for this product type. The + * purpose of this is to aggregate all the definitions that are needed to be added to every + * product type together so that we can issue them together in the same update request. + * + * @return a map of product type keys pointing to a set of attribute definition drafts which are + * now ready to be added for this product type. + */ + @Nonnull + private Map> buildProductTypesToUpdateMap() { + + final Map> productTypesToUpdate = new HashMap<>(); + + readyToResolve.forEach( + readyToResolveProductTypeKey -> { + final ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView> + referencingProductTypes = + statistics + .getProductTypeKeysWithMissingParents() + .get(readyToResolveProductTypeKey); + + if (referencingProductTypes != null) { + referencingProductTypes.forEach( + (productTypeKey, attributes) -> { + final Set attributeDefinitionsToAdd = + productTypesToUpdate.get(productTypeKey); + + if (attributeDefinitionsToAdd != null) { + attributeDefinitionsToAdd.addAll(attributes); + } else { + productTypesToUpdate.put(productTypeKey, attributes); + } + }); + } + }); + + return productTypesToUpdate; + } + + /** + * Given a map of product type keys pointing to a set of attribute definition drafts which are now + * ready to be added for this product type. This method first converts the drafts to {@link + * AddAttributeDefinition} actions in which the reference id value (which is a key) is resolved to + * an actual UUID of the product type key pointed by this key. Then, for each product type, the + * method issues an update request containing all the actions. + * + * @return a {@link CompletionStage} which contains an empty result after execution of all the + * update requests. + */ + @Nonnull + private CompletionStage resolveMissingNestedReferences( + @Nonnull final Map> productTypesToUpdate) { + + final Set keys = productTypesToUpdate.keySet(); + return productTypeService + .fetchMatchingProductTypesByKeys(keys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Set matchingProductTypes = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + if (exception != null) { + final String errorMessage = format(CTP_PRODUCT_TYPE_FETCH_FAILED, keys); + syncOptions.applyErrorCallback(new SyncException(errorMessage, exception)); + return CompletableFuture.completedFuture(null); + } else { + final Map keyToProductType = + matchingProductTypes.stream() + .collect(Collectors.toMap(ProductType::getKey, productType -> productType)); + return CompletableFuture.allOf( + productTypesToUpdate.entrySet().stream() + .map( + entry -> { + final String productTypeToUpdateKey = entry.getKey(); + final Set attributeDefinitionDrafts = + entry.getValue(); + + final List> actionsWithResolvedReferences = + draftsToActions(attributeDefinitionDrafts); + + final ProductType productTypeToUpdate = + keyToProductType.get(productTypeToUpdateKey); + + return resolveMissingNestedReferences( + productTypeToUpdate, actionsWithResolvedReferences); }) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)); - } + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)); + } }); - } - - /** - * Given an existing {@link ProductType} and a list of {@link UpdateAction}s, required to resolve the productType - * with nestedType references. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP, the error handling method - * is called. - * - * @param oldProductType existing product type that could be updated. - * @param updateActions actions to update the product type with. - * @return a {@link CompletionStage} which contains an empty result after execution of the update. - */ - @SuppressWarnings("ConstantConditions") // since the batch is validate before, key is assured to be non-blank here. - @Nonnull - private CompletionStage resolveMissingNestedReferences( - @Nonnull final ProductType oldProductType, - @Nonnull final List> updateActions) { - - return productTypeService - .updateProductType(oldProductType, updateActions) - .handle(ImmutablePair::new) - .thenCompose(updateResponse -> { - final Throwable sphereException = updateResponse.getValue(); - if (sphereException != null) { - return executeSupplierIfConcurrentModificationException( - sphereException, - () -> fetchAndUpdate(oldProductType, - fetchedProductType -> resolveMissingNestedReferences(fetchedProductType, updateActions)), - () -> { - final String errorMessage = - format(CTP_PRODUCT_TYPE_UPDATE_FAILED, oldProductType.getKey(), - sphereException.getMessage()); - handleError(new SyncException(errorMessage, sphereException), 1); - return CompletableFuture.completedFuture(null); - }); - } else { - // Update missing parents by removing parent keys in ready to resolve. - statistics.removeReferencingProductTypeKey(oldProductType.getKey()); - return CompletableFuture.completedFuture(null); - } + } + + /** + * Given an existing {@link ProductType} and a list of {@link UpdateAction}s, required to resolve + * the productType with nestedType references. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP, + * the error handling method is called. + * + * @param oldProductType existing product type that could be updated. + * @param updateActions actions to update the product type with. + * @return a {@link CompletionStage} which contains an empty result after execution of the update. + */ + @SuppressWarnings( + "ConstantConditions") // since the batch is validate before, key is assured to be non-blank + // here. + @Nonnull + private CompletionStage resolveMissingNestedReferences( + @Nonnull final ProductType oldProductType, + @Nonnull final List> updateActions) { + + return productTypeService + .updateProductType(oldProductType, updateActions) + .handle(ImmutablePair::new) + .thenCompose( + updateResponse -> { + final Throwable sphereException = updateResponse.getValue(); + if (sphereException != null) { + return executeSupplierIfConcurrentModificationException( + sphereException, + () -> + fetchAndUpdate( + oldProductType, + fetchedProductType -> + resolveMissingNestedReferences(fetchedProductType, updateActions)), + () -> { + final String errorMessage = + format( + CTP_PRODUCT_TYPE_UPDATE_FAILED, + oldProductType.getKey(), + sphereException.getMessage()); + handleError(new SyncException(errorMessage, sphereException), 1); + return CompletableFuture.completedFuture(null); + }); + } else { + // Update missing parents by removing parent keys in ready to resolve. + statistics.removeReferencingProductTypeKey(oldProductType.getKey()); + return CompletableFuture.completedFuture(null); + } }); - } - - /** - * Given a set of {@link AttributeDefinitionDraft}, for every draft, this method resolves the nested type reference - * on the attribute definition draft and creates an {@link AddAttributeDefinition} action out of it and returns - * a list of update actions. - * - * @return a list of update actions corresponding to the supplied set of {@link AttributeDefinitionDraft}s. - */ - @Nonnull - private List> draftsToActions( - @Nonnull final Set attributeDefinitionDrafts) { - - return attributeDefinitionDrafts - .stream() - .map(attributeDefinitionDraft -> { - final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = - new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); - final AttributeDefinitionDraft resolvedDraft = attributeDefinitionReferenceResolver - .resolveReferences(attributeDefinitionDraft) - .toCompletableFuture() - .join(); - return AddAttributeDefinition.of(resolvedDraft); + } + + /** + * Given a set of {@link AttributeDefinitionDraft}, for every draft, this method resolves the + * nested type reference on the attribute definition draft and creates an {@link + * AddAttributeDefinition} action out of it and returns a list of update actions. + * + * @return a list of update actions corresponding to the supplied set of {@link + * AttributeDefinitionDraft}s. + */ + @Nonnull + private List> draftsToActions( + @Nonnull final Set attributeDefinitionDrafts) { + + return attributeDefinitionDrafts.stream() + .map( + attributeDefinitionDraft -> { + final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = + new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); + final AttributeDefinitionDraft resolvedDraft = + attributeDefinitionReferenceResolver + .resolveReferences(attributeDefinitionDraft) + .toCompletableFuture() + .join(); + return AddAttributeDefinition.of(resolvedDraft); }) - .collect(Collectors.toList()); - } - - @Nonnull - private CompletionStage buildActionsAndUpdate( - @Nonnull final ProductType oldProductType, - @Nonnull final ProductTypeDraft newProductType) { + .collect(Collectors.toList()); + } - final List> updateActions = buildActions(oldProductType, newProductType, syncOptions); + @Nonnull + private CompletionStage buildActionsAndUpdate( + @Nonnull final ProductType oldProductType, @Nonnull final ProductTypeDraft newProductType) { - final List> updateActionsAfterCallback = - syncOptions.applyBeforeUpdateCallback(updateActions, newProductType, oldProductType); + final List> updateActions = + buildActions(oldProductType, newProductType, syncOptions); - if (!updateActionsAfterCallback.isEmpty()) { - return updateProductType(oldProductType, newProductType, updateActionsAfterCallback); - } + final List> updateActionsAfterCallback = + syncOptions.applyBeforeUpdateCallback(updateActions, newProductType, oldProductType); - return completedFuture(null); + if (!updateActionsAfterCallback.isEmpty()) { + return updateProductType(oldProductType, newProductType, updateActionsAfterCallback); } - /** - * Given an existing {@link ProductType} and a new {@link ProductTypeDraft}, the method calculates all the - * update actions required to synchronize the existing product type to be the same as the new one. If there are - * update actions found, a request is made to CTP to update the existing product type, otherwise it doesn't issue a - * request. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP, the error handling method - * is called. - * - * @param oldProductType existing product type that could be updated. - * @param newProductType draft containing data that could differ from data in {@code oldProductType}. - * @param updateActions the update actions to update the {@link ProductType} with. - * @return a {@link CompletionStage} which contains an empty result after execution of the update. - */ - @Nonnull - private CompletionStage updateProductType( - @Nonnull final ProductType oldProductType, - @Nonnull final ProductTypeDraft newProductType, - @Nonnull final List> updateActions) { - - return productTypeService - .updateProductType(oldProductType, updateActions) - .handle(ImmutablePair::new) - .thenCompose(updateResponse -> { - final Throwable sphereException = updateResponse.getValue(); - if (sphereException != null) { - return executeSupplierIfConcurrentModificationException( - sphereException, - () -> fetchAndUpdate(oldProductType, - fetchedProductType -> buildActionsAndUpdate(fetchedProductType, newProductType)), - () -> { - final String errorMessage = - format(CTP_PRODUCT_TYPE_UPDATE_FAILED, newProductType.getKey(), - sphereException.getMessage()); - handleError(errorMessage, sphereException, 1, oldProductType, newProductType, - updateActions); - return CompletableFuture.completedFuture(null); - }); - } else { - statistics.incrementUpdated(); - return CompletableFuture.completedFuture(null); - } + return completedFuture(null); + } + + /** + * Given an existing {@link ProductType} and a new {@link ProductTypeDraft}, the method calculates + * all the update actions required to synchronize the existing product type to be the same as the + * new one. If there are update actions found, a request is made to CTP to update the existing + * product type, otherwise it doesn't issue a request. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP, + * the error handling method is called. + * + * @param oldProductType existing product type that could be updated. + * @param newProductType draft containing data that could differ from data in {@code + * oldProductType}. + * @param updateActions the update actions to update the {@link ProductType} with. + * @return a {@link CompletionStage} which contains an empty result after execution of the update. + */ + @Nonnull + private CompletionStage updateProductType( + @Nonnull final ProductType oldProductType, + @Nonnull final ProductTypeDraft newProductType, + @Nonnull final List> updateActions) { + + return productTypeService + .updateProductType(oldProductType, updateActions) + .handle(ImmutablePair::new) + .thenCompose( + updateResponse -> { + final Throwable sphereException = updateResponse.getValue(); + if (sphereException != null) { + return executeSupplierIfConcurrentModificationException( + sphereException, + () -> + fetchAndUpdate( + oldProductType, + fetchedProductType -> + buildActionsAndUpdate(fetchedProductType, newProductType)), + () -> { + final String errorMessage = + format( + CTP_PRODUCT_TYPE_UPDATE_FAILED, + newProductType.getKey(), + sphereException.getMessage()); + handleError( + errorMessage, + sphereException, + 1, + oldProductType, + newProductType, + updateActions); + return CompletableFuture.completedFuture(null); + }); + } else { + statistics.incrementUpdated(); + return CompletableFuture.completedFuture(null); + } }); - } - - @Nonnull - private CompletionStage fetchAndUpdate( - @Nonnull final ProductType oldProductType, - @Nonnull final Function> fetchedProductMapper) { - - final String key = oldProductType.getKey(); - return productTypeService - .fetchProductType(key) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Optional fetchedProductTypeOptional = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(CTP_PRODUCT_TYPE_UPDATE_FAILED, key, + } + + @Nonnull + private CompletionStage fetchAndUpdate( + @Nonnull final ProductType oldProductType, + @Nonnull final Function> fetchedProductMapper) { + + final String key = oldProductType.getKey(); + return productTypeService + .fetchProductType(key) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Optional fetchedProductTypeOptional = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + CTP_PRODUCT_TYPE_UPDATE_FAILED, + key, "Failed to fetch from CTP while retrying after concurrency modification."); - // todo: add newProductType or updateActions to handleError - handleError(errorMessage, exception, 1, oldProductType, null, null); - return CompletableFuture.completedFuture(null); - } - - return fetchedProductTypeOptional - .map(fetchedProductMapper) - .orElseGet(() -> { + // todo: add newProductType or updateActions to handleError + handleError(errorMessage, exception, 1, oldProductType, null, null); + return CompletableFuture.completedFuture(null); + } + + return fetchedProductTypeOptional + .map(fetchedProductMapper) + .orElseGet( + () -> { final String errorMessage = - format(CTP_PRODUCT_TYPE_UPDATE_FAILED, key, + format( + CTP_PRODUCT_TYPE_UPDATE_FAILED, + key, "Not found when attempting to fetch while retrying " + "after concurrency modification."); // todo: add newProductType or updateActions to handleError handleError(errorMessage, null, 1, oldProductType, null, null); return CompletableFuture.completedFuture(null); - }); + }); }); - - } - - /** - * Given a product type draft, this method applies the beforeCreateCallback and then issues a create request to the - * CTP project to create the corresponding Product Type. - * - * @param productTypeDraft the product type draft to create the product type from. - * @return a {@link CompletionStage} which contains an empty result after execution of the create. - */ - @Nonnull - private CompletionStage applyCallbackAndCreate( - @Nonnull final ProductTypeDraft productTypeDraft) { - - return syncOptions - .applyBeforeCreateCallback(productTypeDraft) - .map(draft -> productTypeService - .createProductType(draft) - .thenAccept(productTypeOptional -> { - if (productTypeOptional.isPresent()) { - readyToResolve.add(productTypeDraft.getKey()); - statistics.incrementCreated(); - } else { - statistics.incrementFailed(); - } - }) - ) - .orElseGet(() -> CompletableFuture.completedFuture(null)); - } + } + + /** + * Given a product type draft, this method applies the beforeCreateCallback and then issues a + * create request to the CTP project to create the corresponding Product Type. + * + * @param productTypeDraft the product type draft to create the product type from. + * @return a {@link CompletionStage} which contains an empty result after execution of the create. + */ + @Nonnull + private CompletionStage applyCallbackAndCreate( + @Nonnull final ProductTypeDraft productTypeDraft) { + + return syncOptions + .applyBeforeCreateCallback(productTypeDraft) + .map( + draft -> + productTypeService + .createProductType(draft) + .thenAccept( + productTypeOptional -> { + if (productTypeOptional.isPresent()) { + readyToResolve.add(productTypeDraft.getKey()); + statistics.incrementCreated(); + } else { + statistics.incrementFailed(); + } + })) + .orElseGet(() -> CompletableFuture.completedFuture(null)); + } } diff --git a/src/main/java/com/commercetools/sync/producttypes/ProductTypeSyncOptions.java b/src/main/java/com/commercetools/sync/producttypes/ProductTypeSyncOptions.java index 43d3102fcf..04600c4a4c 100644 --- a/src/main/java/com/commercetools/sync/producttypes/ProductTypeSyncOptions.java +++ b/src/main/java/com/commercetools/sync/producttypes/ProductTypeSyncOptions.java @@ -9,35 +9,43 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.ProductTypeDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class ProductTypeSyncOptions extends BaseSyncOptions { - ProductTypeSyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallback, - @Nullable final TriConsumer, Optional> - warningCallback, - final int batchSize, - @Nullable final TriFunction>, ProductTypeDraft, ProductType, - List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, final long cacheSize - ) { - - super( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } + ProductTypeSyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallback, + @Nullable + final TriConsumer, Optional> + warningCallback, + final int batchSize, + @Nullable + final TriFunction< + List>, + ProductTypeDraft, + ProductType, + List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { + super( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } } diff --git a/src/main/java/com/commercetools/sync/producttypes/ProductTypeSyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/producttypes/ProductTypeSyncOptionsBuilder.java index a82e7a47bb..9925d1de44 100644 --- a/src/main/java/com/commercetools/sync/producttypes/ProductTypeSyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/producttypes/ProductTypeSyncOptionsBuilder.java @@ -4,56 +4,57 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.ProductTypeDraft; - import javax.annotation.Nonnull; -public final class ProductTypeSyncOptionsBuilder extends BaseSyncOptionsBuilder { - - public static final int BATCH_SIZE_DEFAULT = 50; - - private ProductTypeSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } - - /** - * Creates a new instance of {@link ProductTypeSyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 50). - * - * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link ProductTypeSyncOptionsBuilder} - */ - public static ProductTypeSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new ProductTypeSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); - } - - /** - * Creates new instance of {@link ProductTypeSyncOptions} enriched with all attributes provided to {@code this} - * builder. - * - * @return new instance of {@link ProductTypeSyncOptions} - */ - @Override - public ProductTypeSyncOptions build() { - return new ProductTypeSyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } - - /** - * Returns an instance of this class to be used in the superclass's generic methods. Please see the JavaDoc in the - * overridden method for further details. - * - * @return an instance of this class. - */ - @Override - protected ProductTypeSyncOptionsBuilder getThis() { - return this; - } +public final class ProductTypeSyncOptionsBuilder + extends BaseSyncOptionsBuilder< + ProductTypeSyncOptionsBuilder, ProductTypeSyncOptions, ProductType, ProductTypeDraft> { + + public static final int BATCH_SIZE_DEFAULT = 50; + + private ProductTypeSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } + + /** + * Creates a new instance of {@link ProductTypeSyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 50). + * + * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the + * target CTP project. + * @return new instance of {@link ProductTypeSyncOptionsBuilder} + */ + public static ProductTypeSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new ProductTypeSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } + + /** + * Creates new instance of {@link ProductTypeSyncOptions} enriched with all attributes provided to + * {@code this} builder. + * + * @return new instance of {@link ProductTypeSyncOptions} + */ + @Override + public ProductTypeSyncOptions build() { + return new ProductTypeSyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } + + /** + * Returns an instance of this class to be used in the superclass's generic methods. Please see + * the JavaDoc in the overridden method for further details. + * + * @return an instance of this class. + */ + @Override + protected ProductTypeSyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/producttypes/helpers/AttributeDefinitionReferenceResolver.java b/src/main/java/com/commercetools/sync/producttypes/helpers/AttributeDefinitionReferenceResolver.java index 91044cc319..f80045a09e 100644 --- a/src/main/java/com/commercetools/sync/producttypes/helpers/AttributeDefinitionReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/producttypes/helpers/AttributeDefinitionReferenceResolver.java @@ -1,5 +1,9 @@ package com.commercetools.sync.producttypes.helpers; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.BaseReferenceResolver; import com.commercetools.sync.producttypes.ProductTypeSyncOptions; @@ -11,112 +15,110 @@ import io.sphere.sdk.products.attributes.NestedAttributeType; import io.sphere.sdk.products.attributes.SetAttributeType; import io.sphere.sdk.producttypes.ProductType; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; import java.util.Optional; import java.util.concurrent.CompletionStage; - -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; public class AttributeDefinitionReferenceResolver extends BaseReferenceResolver { - private ProductTypeService productTypeService; - - - public AttributeDefinitionReferenceResolver(@Nonnull final ProductTypeSyncOptions options, - @Nonnull final ProductTypeService productTypeService) { - super(options); - this.productTypeService = productTypeService; - } - - /** - * Given an {@link AttributeDefinitionDraft} this method attempts to resolve the ProductType references, which can - * exist on attributeDefinition with an AttributeType: NestedType or SetType of NestedType, to return a - * {@link CompletionStage} which contains a new instance of the draft with the resolved references. - * - * @param attributeDefinitionDraft the attributeDefinitionDraft to resolve its references. - * @return a {@link CompletionStage} that contains as a result a new attributeDefinitionDraft instance with resolved - * references or if there is no productType existing with the given key the draft will be returned as is - * without the reference resolved. In case an error occurs during reference resolution, a - * {@link ReferenceResolutionException} is thrown. - */ - @Nonnull - public CompletionStage resolveReferences( - @Nonnull final AttributeDefinitionDraft attributeDefinitionDraft) { - - final AttributeDefinitionDraftBuilder draftBuilder = - AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft); - - return resolveReferences(draftBuilder) - .handle(ImmutablePair::new) - .thenCompose(result -> { - final Throwable exception = result.getValue(); - final AttributeDefinitionDraftBuilder resolvedBuilder = result.getKey(); - if (exception == null) { - return completedFuture(resolvedBuilder.build()); - } else { - final String errorMessage = - format("Failed to resolve references on attribute definition with name '%s'.", - attributeDefinitionDraft.getName()); - return exceptionallyCompletedFuture( - new ReferenceResolutionException(errorMessage, exception.getCause())); - } + private ProductTypeService productTypeService; + + public AttributeDefinitionReferenceResolver( + @Nonnull final ProductTypeSyncOptions options, + @Nonnull final ProductTypeService productTypeService) { + super(options); + this.productTypeService = productTypeService; + } + + /** + * Given an {@link AttributeDefinitionDraft} this method attempts to resolve the ProductType + * references, which can exist on attributeDefinition with an AttributeType: NestedType or SetType + * of NestedType, to return a {@link CompletionStage} which contains a new instance of the draft + * with the resolved references. + * + * @param attributeDefinitionDraft the attributeDefinitionDraft to resolve its references. + * @return a {@link CompletionStage} that contains as a result a new attributeDefinitionDraft + * instance with resolved references or if there is no productType existing with the given key + * the draft will be returned as is without the reference resolved. In case an error occurs + * during reference resolution, a {@link ReferenceResolutionException} is thrown. + */ + @Nonnull + public CompletionStage resolveReferences( + @Nonnull final AttributeDefinitionDraft attributeDefinitionDraft) { + + final AttributeDefinitionDraftBuilder draftBuilder = + AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft); + + return resolveReferences(draftBuilder) + .handle(ImmutablePair::new) + .thenCompose( + result -> { + final Throwable exception = result.getValue(); + final AttributeDefinitionDraftBuilder resolvedBuilder = result.getKey(); + if (exception == null) { + return completedFuture(resolvedBuilder.build()); + } else { + final String errorMessage = + format( + "Failed to resolve references on attribute definition with name '%s'.", + attributeDefinitionDraft.getName()); + return exceptionallyCompletedFuture( + new ReferenceResolutionException(errorMessage, exception.getCause())); + } }); + } - } - - @Nonnull - private CompletionStage resolveReferences( - @Nonnull final AttributeDefinitionDraftBuilder attributeDefinitionDraftBuilder) { + @Nonnull + private CompletionStage resolveReferences( + @Nonnull final AttributeDefinitionDraftBuilder attributeDefinitionDraftBuilder) { - final AttributeType attributeType = attributeDefinitionDraftBuilder.getAttributeType(); + final AttributeType attributeType = attributeDefinitionDraftBuilder.getAttributeType(); - if (attributeType instanceof NestedAttributeType) { - return resolveNestedTypeReference((NestedAttributeType) attributeType) - .thenApply(attributeDefinitionDraftBuilder::attributeType); + if (attributeType instanceof NestedAttributeType) { + return resolveNestedTypeReference((NestedAttributeType) attributeType) + .thenApply(attributeDefinitionDraftBuilder::attributeType); - } else if (attributeType instanceof SetAttributeType) { - final SetAttributeType setAttributeType = (SetAttributeType) attributeType; - final AttributeType elementType = setAttributeType.getElementType(); + } else if (attributeType instanceof SetAttributeType) { + final SetAttributeType setAttributeType = (SetAttributeType) attributeType; + final AttributeType elementType = setAttributeType.getElementType(); - if (elementType instanceof NestedAttributeType) { + if (elementType instanceof NestedAttributeType) { - return resolveNestedTypeReference((NestedAttributeType) elementType) - .thenApply(SetAttributeType::of) - .thenApply(attributeDefinitionDraftBuilder::attributeType); - } - } - return completedFuture(attributeDefinitionDraftBuilder); + return resolveNestedTypeReference((NestedAttributeType) elementType) + .thenApply(SetAttributeType::of) + .thenApply(attributeDefinitionDraftBuilder::attributeType); + } } - - @Nonnull - private CompletionStage resolveNestedTypeReference( - @Nonnull final NestedAttributeType nestedAttributeType) { - - final Reference typeReference = nestedAttributeType.getTypeReference(); - - return resolveProductTypeReference(typeReference) - .thenApply(optionalResolvedReference -> - optionalResolvedReference.map(NestedAttributeType::of) - .orElse(nestedAttributeType)); + return completedFuture(attributeDefinitionDraftBuilder); + } + + @Nonnull + private CompletionStage resolveNestedTypeReference( + @Nonnull final NestedAttributeType nestedAttributeType) { + + final Reference typeReference = nestedAttributeType.getTypeReference(); + + return resolveProductTypeReference(typeReference) + .thenApply( + optionalResolvedReference -> + optionalResolvedReference.map(NestedAttributeType::of).orElse(nestedAttributeType)); + } + + @Nonnull + private CompletionStage>> resolveProductTypeReference( + @Nonnull final Reference typeReference) { + + final String resourceKey; + try { + resourceKey = getIdFromReference(typeReference); + } catch (ReferenceResolutionException exception) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + "Failed to resolve NestedType productType reference.", exception)); } - - @Nonnull - private CompletionStage>> resolveProductTypeReference( - @Nonnull final Reference typeReference) { - - final String resourceKey; - try { - resourceKey = getIdFromReference(typeReference); - } catch (ReferenceResolutionException exception) { - return exceptionallyCompletedFuture( - new ReferenceResolutionException("Failed to resolve NestedType productType reference.", exception)); - } - return productTypeService.fetchCachedProductTypeId(resourceKey) - .thenApply(optionalId -> optionalId.map(ProductType::referenceOfId)); - } - + return productTypeService + .fetchCachedProductTypeId(resourceKey) + .thenApply(optionalId -> optionalId.map(ProductType::referenceOfId)); + } } diff --git a/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeBatchValidator.java b/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeBatchValidator.java index 6fcf21f74e..0fb8d619c4 100644 --- a/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeBatchValidator.java +++ b/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeBatchValidator.java @@ -1,5 +1,10 @@ package com.commercetools.sync.producttypes.helpers; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; +import static java.lang.String.format; +import static java.util.Collections.emptySet; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.exceptions.InvalidReferenceException; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.helpers.BaseBatchValidator; @@ -9,154 +14,162 @@ import io.sphere.sdk.products.attributes.NestedAttributeType; import io.sphere.sdk.products.attributes.SetAttributeType; import io.sphere.sdk.producttypes.ProductTypeDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; -import static java.lang.String.format; -import static java.util.Collections.emptySet; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class ProductTypeBatchValidator - extends BaseBatchValidator { - - static final String PRODUCT_TYPE_DRAFT_KEY_NOT_SET = "ProductTypeDraft with name: %s doesn't have a key. " - + "Please make sure all productType drafts have keys."; - static final String PRODUCT_TYPE_DRAFT_IS_NULL = "ProductTypeDraft is null."; - static final String PRODUCT_TYPE_HAS_INVALID_REFERENCES = "ProductTypeDraft with key: '%s' has invalid productType " - + "references on the following AttributeDefinitionDrafts: %s"; - - public ProductTypeBatchValidator(@Nonnull final ProductTypeSyncOptions syncOptions, - @Nonnull final ProductTypeSyncStatistics syncStatistics) { - super(syncOptions, syncStatistics); - } - - /** - * Given the {@link List}<{@link ProductTypeDraft}> of drafts this method attempts to validate - * drafts and collect referenced keys from the draft - * and return an {@link ImmutablePair}<{@link Set}<{@link ProductTypeDraft}> - * ,{@link Set}<{@link String}>> - * which contains the {@link Set} of valid drafts and referenced product type keys. - * - *

A valid productType draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a key which is not blank (null/empty)
  4. - *
  5. It has no invalid productType reference on an attributeDefinitionDraft - * with either a NestedType or SetType AttributeType. - * A valid reference is simply one which has its id field's value not blank (null/empty)
  6. - *
- * - * @param productTypeDrafts the product type drafts to validate and collect referenced product type keys. - * @return {@link ImmutablePair}<{@link Set}<{@link ProductTypeDraft}>, - * {@link Set}<{@link String}>> which contains the {@link Set} of valid drafts and - * referenced product type keys. - */ - @Override - public ImmutablePair, Set> validateAndCollectReferencedKeys( - @Nonnull final List productTypeDrafts) { - - final Set productTypeKeys = new HashSet<>(); - - final Set validDrafts = productTypeDrafts - .stream() + extends BaseBatchValidator< + ProductTypeDraft, ProductTypeSyncOptions, ProductTypeSyncStatistics> { + + static final String PRODUCT_TYPE_DRAFT_KEY_NOT_SET = + "ProductTypeDraft with name: %s doesn't have a key. " + + "Please make sure all productType drafts have keys."; + static final String PRODUCT_TYPE_DRAFT_IS_NULL = "ProductTypeDraft is null."; + static final String PRODUCT_TYPE_HAS_INVALID_REFERENCES = + "ProductTypeDraft with key: '%s' has invalid productType " + + "references on the following AttributeDefinitionDrafts: %s"; + + public ProductTypeBatchValidator( + @Nonnull final ProductTypeSyncOptions syncOptions, + @Nonnull final ProductTypeSyncStatistics syncStatistics) { + super(syncOptions, syncStatistics); + } + + /** + * Given the {@link List}<{@link ProductTypeDraft}> of drafts this method attempts to + * validate drafts and collect referenced keys from the draft and return an {@link + * ImmutablePair}<{@link Set}<{@link ProductTypeDraft}> ,{@link Set}<{@link + * String}>> which contains the {@link Set} of valid drafts and referenced product type + * keys. + * + *

A valid productType draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a key which is not blank (null/empty) + *
  3. It has no invalid productType reference on an attributeDefinitionDraft with either a + * NestedType or SetType AttributeType. A valid reference is simply one which has its id + * field's value not blank (null/empty) + *
+ * + * @param productTypeDrafts the product type drafts to validate and collect referenced product + * type keys. + * @return {@link ImmutablePair}<{@link Set}<{@link ProductTypeDraft}>, {@link + * Set}<{@link String}>> which contains the {@link Set} of valid drafts and + * referenced product type keys. + */ + @Override + public ImmutablePair, Set> validateAndCollectReferencedKeys( + @Nonnull final List productTypeDrafts) { + + final Set productTypeKeys = new HashSet<>(); + + final Set validDrafts = + productTypeDrafts.stream() .filter(productTypeDraft -> isValidProductTypeDraft(productTypeDraft, productTypeKeys)) .collect(Collectors.toSet()); - return ImmutablePair.of(validDrafts, productTypeKeys); + return ImmutablePair.of(validDrafts, productTypeKeys); + } + + private boolean isValidProductTypeDraft( + @Nullable final ProductTypeDraft productTypeDraft, + @Nonnull final Set productTypeKeys) { + + if (productTypeDraft == null) { + handleError(PRODUCT_TYPE_DRAFT_IS_NULL); + } else if (isBlank(productTypeDraft.getKey())) { + handleError(format(PRODUCT_TYPE_DRAFT_KEY_NOT_SET, productTypeDraft.getName())); + } else { + try { + final Set referencedProductTypeKeys = + getReferencedProductTypeKeys(productTypeDraft); + productTypeKeys.addAll(referencedProductTypeKeys); + return true; + } catch (SyncException syncException) { + handleError(syncException); + } } - private boolean isValidProductTypeDraft( - @Nullable final ProductTypeDraft productTypeDraft, - @Nonnull final Set productTypeKeys) { - - if (productTypeDraft == null) { - handleError(PRODUCT_TYPE_DRAFT_IS_NULL); - } else if (isBlank(productTypeDraft.getKey())) { - handleError(format(PRODUCT_TYPE_DRAFT_KEY_NOT_SET, productTypeDraft.getName())); - } else { - try { - final Set referencedProductTypeKeys = getReferencedProductTypeKeys(productTypeDraft); - productTypeKeys.addAll(referencedProductTypeKeys); - return true; - } catch (SyncException syncException) { - handleError(syncException); - } - } + return false; + } - return false; - } + @Nonnull + private static Set getReferencedProductTypeKeys( + @Nonnull final ProductTypeDraft productTypeDraft) throws SyncException { - @Nonnull - private static Set getReferencedProductTypeKeys(@Nonnull final ProductTypeDraft productTypeDraft) - throws SyncException { - - final List attributeDefinitionDrafts = productTypeDraft.getAttributes(); - if (attributeDefinitionDrafts == null || attributeDefinitionDrafts.isEmpty()) { - return emptySet(); - } + final List attributeDefinitionDrafts = + productTypeDraft.getAttributes(); + if (attributeDefinitionDrafts == null || attributeDefinitionDrafts.isEmpty()) { + return emptySet(); + } - final Set referencedProductTypeKeys = new HashSet<>(); - final List invalidAttributeDefinitionNames = new ArrayList<>(); - - for (AttributeDefinitionDraft attributeDefinitionDraft : attributeDefinitionDrafts) { - if (attributeDefinitionDraft != null) { - final AttributeType attributeType = attributeDefinitionDraft.getAttributeType(); - try { - getProductTypeKey(attributeType).ifPresent(referencedProductTypeKeys::add); - } catch (InvalidReferenceException invalidReferenceException) { - invalidAttributeDefinitionNames.add(attributeDefinitionDraft.getName()); - } - } - } + final Set referencedProductTypeKeys = new HashSet<>(); + final List invalidAttributeDefinitionNames = new ArrayList<>(); - if (!invalidAttributeDefinitionNames.isEmpty()) { - final String errorMessage = format(PRODUCT_TYPE_HAS_INVALID_REFERENCES, productTypeDraft.getKey(), - invalidAttributeDefinitionNames); - throw new SyncException(errorMessage, - new InvalidReferenceException(BLANK_ID_VALUE_ON_REFERENCE)); + for (AttributeDefinitionDraft attributeDefinitionDraft : attributeDefinitionDrafts) { + if (attributeDefinitionDraft != null) { + final AttributeType attributeType = attributeDefinitionDraft.getAttributeType(); + try { + getProductTypeKey(attributeType).ifPresent(referencedProductTypeKeys::add); + } catch (InvalidReferenceException invalidReferenceException) { + invalidAttributeDefinitionNames.add(attributeDefinitionDraft.getName()); } + } + } - return referencedProductTypeKeys; + if (!invalidAttributeDefinitionNames.isEmpty()) { + final String errorMessage = + format( + PRODUCT_TYPE_HAS_INVALID_REFERENCES, + productTypeDraft.getKey(), + invalidAttributeDefinitionNames); + throw new SyncException( + errorMessage, new InvalidReferenceException(BLANK_ID_VALUE_ON_REFERENCE)); } - /** - * This method is meant be only used internally by the library. - * @param attributeType the attributeType to attempt to fetch the product type key of, if it contains a - * nestedType reference. - * @return an optional containing the productType key or empty if it does not contain a productType reference. - * @throws InvalidReferenceException thrown if the productType key in the nested reference is invalid. - */ - @Nonnull - public static Optional getProductTypeKey(@Nonnull final AttributeType attributeType) - throws InvalidReferenceException { - - if (attributeType instanceof NestedAttributeType) { - final NestedAttributeType nestedElementType = (NestedAttributeType) attributeType; - return Optional.of(getProductTypeKey(nestedElementType)); - } else if (attributeType instanceof SetAttributeType) { - final SetAttributeType setAttributeType = (SetAttributeType) attributeType; - return getProductTypeKey(setAttributeType.getElementType()); - } - return Optional.empty(); + return referencedProductTypeKeys; + } + + /** + * This method is meant be only used internally by the library. + * + * @param attributeType the attributeType to attempt to fetch the product type key of, if it + * contains a nestedType reference. + * @return an optional containing the productType key or empty if it does not contain a + * productType reference. + * @throws InvalidReferenceException thrown if the productType key in the nested reference is + * invalid. + */ + @Nonnull + public static Optional getProductTypeKey(@Nonnull final AttributeType attributeType) + throws InvalidReferenceException { + + if (attributeType instanceof NestedAttributeType) { + final NestedAttributeType nestedElementType = (NestedAttributeType) attributeType; + return Optional.of(getProductTypeKey(nestedElementType)); + } else if (attributeType instanceof SetAttributeType) { + final SetAttributeType setAttributeType = (SetAttributeType) attributeType; + return getProductTypeKey(setAttributeType.getElementType()); } + return Optional.empty(); + } - @Nonnull - private static String getProductTypeKey(@Nonnull final NestedAttributeType nestedAttributeType) - throws InvalidReferenceException { + @Nonnull + private static String getProductTypeKey(@Nonnull final NestedAttributeType nestedAttributeType) + throws InvalidReferenceException { - final String key = nestedAttributeType.getTypeReference().getId(); - if (isBlank(key)) { - throw new InvalidReferenceException(BLANK_ID_VALUE_ON_REFERENCE); - } - return key; + final String key = nestedAttributeType.getTypeReference().getId(); + if (isBlank(key)) { + throw new InvalidReferenceException(BLANK_ID_VALUE_ON_REFERENCE); } + return key; + } } diff --git a/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeReferenceResolver.java b/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeReferenceResolver.java index 20140c78aa..0ba1c9f67e 100644 --- a/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeReferenceResolver.java @@ -1,5 +1,9 @@ package com.commercetools.sync.producttypes.helpers; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.BaseReferenceResolver; import com.commercetools.sync.producttypes.ProductTypeSyncOptions; @@ -7,63 +11,65 @@ import io.sphere.sdk.products.attributes.AttributeDefinitionDraft; import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.ProductTypeDraftBuilder; - -import javax.annotation.Nonnull; import java.util.List; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; public final class ProductTypeReferenceResolver extends BaseReferenceResolver { - private AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver; + private AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver; - /** - * Takes a {@link ProductTypeSyncOptions} instance and a {@link ProductTypeService} to instantiate - * a {@link AttributeDefinitionReferenceResolver} instance that could be used to resolve the AttributeDefinition - * references on the productType draft supplied to the {@link #resolveReferences(ProductTypeDraft)} method. - * - * @param productTypeSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param productTypeService the service to fetch the product type for reference resolution. - */ - public ProductTypeReferenceResolver(@Nonnull final ProductTypeSyncOptions productTypeSyncOptions, - @Nonnull final ProductTypeService productTypeService) { - super(productTypeSyncOptions); - this.attributeDefinitionReferenceResolver = - new AttributeDefinitionReferenceResolver(productTypeSyncOptions, productTypeService); - } + /** + * Takes a {@link ProductTypeSyncOptions} instance and a {@link ProductTypeService} to instantiate + * a {@link AttributeDefinitionReferenceResolver} instance that could be used to resolve the + * AttributeDefinition references on the productType draft supplied to the {@link + * #resolveReferences(ProductTypeDraft)} method. + * + * @param productTypeSyncOptions the container of all the options of the sync process including + * the CTP project client and/or configuration and other sync-specific options. + * @param productTypeService the service to fetch the product type for reference resolution. + */ + public ProductTypeReferenceResolver( + @Nonnull final ProductTypeSyncOptions productTypeSyncOptions, + @Nonnull final ProductTypeService productTypeService) { + super(productTypeSyncOptions); + this.attributeDefinitionReferenceResolver = + new AttributeDefinitionReferenceResolver(productTypeSyncOptions, productTypeService); + } - /** - * Given a {@link ProductTypeDraft} this method attempts to resolve the attribute definition references to return - * a {@link CompletionStage} which contains a new instance of the draft with the resolved references. - * - * @param productTypeDraft the productTypeDraft to resolve its references. - * @return a {@link CompletionStage} that contains as a result a new productTypeDraft instance with resolved - * references or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Nonnull - public CompletionStage resolveReferences(@Nonnull final ProductTypeDraft productTypeDraft) { - return resolveAttributeDefinitionsReferences(ProductTypeDraftBuilder.of(productTypeDraft)) - .thenApply(ProductTypeDraftBuilder::build); - } - - @Nonnull - CompletionStage resolveAttributeDefinitionsReferences( - @Nonnull final ProductTypeDraftBuilder productTypeDraftBuilder) { + /** + * Given a {@link ProductTypeDraft} this method attempts to resolve the attribute definition + * references to return a {@link CompletionStage} which contains a new instance of the draft with + * the resolved references. + * + * @param productTypeDraft the productTypeDraft to resolve its references. + * @return a {@link CompletionStage} that contains as a result a new productTypeDraft instance + * with resolved references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Nonnull + public CompletionStage resolveReferences( + @Nonnull final ProductTypeDraft productTypeDraft) { + return resolveAttributeDefinitionsReferences(ProductTypeDraftBuilder.of(productTypeDraft)) + .thenApply(ProductTypeDraftBuilder::build); + } - final List attributeDefinitionDrafts = productTypeDraftBuilder.getAttributes(); + @Nonnull + CompletionStage resolveAttributeDefinitionsReferences( + @Nonnull final ProductTypeDraftBuilder productTypeDraftBuilder) { - if (attributeDefinitionDrafts == null) { - return completedFuture(productTypeDraftBuilder); - } + final List attributeDefinitionDrafts = + productTypeDraftBuilder.getAttributes(); - return mapValuesToFutureOfCompletedValues(attributeDefinitionDrafts, - attributeDefinitionReferenceResolver::resolveReferences, toList()) - .thenApply(productTypeDraftBuilder::attributes); + if (attributeDefinitionDrafts == null) { + return completedFuture(productTypeDraftBuilder); } + + return mapValuesToFutureOfCompletedValues( + attributeDefinitionDrafts, + attributeDefinitionReferenceResolver::resolveReferences, + toList()) + .thenApply(productTypeDraftBuilder::attributes); + } } diff --git a/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeSyncStatistics.java b/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeSyncStatistics.java index d7ceaf3698..939cc70197 100644 --- a/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeSyncStatistics.java +++ b/src/main/java/com/commercetools/sync/producttypes/helpers/ProductTypeSyncStatistics.java @@ -1,12 +1,12 @@ package com.commercetools.sync.producttypes.helpers; +import static java.lang.String.format; + import com.commercetools.sync.commons.helpers.BaseSyncStatistics; import com.commercetools.sync.producttypes.ProductTypeSync; import io.sphere.sdk.products.attributes.AttributeDefinitionDraft; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.ProductTypeDraft; - -import javax.annotation.Nonnull; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -15,204 +15,210 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; - -import static java.lang.String.format; +import javax.annotation.Nonnull; public class ProductTypeSyncStatistics extends BaseSyncStatistics { - /** - * The following {@link ConcurrentHashMap} ({@code missingNestedProductTypes}) keeps track of the keys of missing - * product types, the keys of the product types which are referencing those missing product types and a list - * of attribute definitions which contains those references. - * - *
    - *
  • key: key of the missing product type.
  • - *
  • value: a map of which consists of: - *
      - *
    • key: key of the product type referencing the missing product type.
    • - *
    • value: a set of the attribute definition drafts which contains the reference - * to the missing product type.
    • - *
    - *
  • - *
- * - *

The map is thread-safe (by instantiating it with {@link ConcurrentHashMap}) because it is accessed/modified in - * a concurrent context, specifically when syncing product types in parallel in - * {@link ProductTypeSync#removeMissingReferenceAttributeAndUpdateMissingParentMap(ProductTypeDraft, Map)}, - * {@link ProductTypeSync#updateProductType(ProductType, List)} and {@link ProductTypeSync#buildToBeUpdatedMap()} - */ - private - ConcurrentHashMap - > - > - missingNestedProductTypes = new ConcurrentHashMap<>(); - - /** - * Only used for testing. - */ - ProductTypeSyncStatistics(@Nonnull final ConcurrentHashMap>> missingNestedProductTypes) { - this.missingNestedProductTypes = missingNestedProductTypes; - } - - public ProductTypeSyncStatistics() { + /** + * The following {@link ConcurrentHashMap} ({@code missingNestedProductTypes}) keeps track of the + * keys of missing product types, the keys of the product types which are referencing those + * missing product types and a list of attribute definitions which contains those references. + * + *

    + *
  • key: key of the missing product type. + *
  • value: a map of which consists of: + *
      + *
    • key: key of the product type referencing the missing product type. + *
    • value: a set of the attribute definition drafts which contains the reference to the + * missing product type. + *
    + *
+ * + *

The map is thread-safe (by instantiating it with {@link ConcurrentHashMap}) because it is + * accessed/modified in a concurrent context, specifically when syncing product types in parallel + * in {@link + * ProductTypeSync#removeMissingReferenceAttributeAndUpdateMissingParentMap(ProductTypeDraft, + * Map)}, {@link ProductTypeSync#updateProductType(ProductType, List)} and {@link + * ProductTypeSync#buildToBeUpdatedMap()} + */ + private ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingNestedProductTypes = new ConcurrentHashMap<>(); + + /** Only used for testing. */ + ProductTypeSyncStatistics( + @Nonnull + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingNestedProductTypes) { + this.missingNestedProductTypes = missingNestedProductTypes; + } + + public ProductTypeSyncStatistics() {} + + /** + * Builds a summary of the product type sync statistics instance that looks like the following + * example: + * + *

"Summary: 2 product types were processed in total (0 created, 0 updated, 0 failed to sync + * and 0 product types with at least one NestedType or a Set of NestedType attribute definition(s) + * referencing a missing productType)." + * + * @return a summary message of the product types sync statistics instance. + */ + @Override + public String getReportMessage() { + return format( + "Summary: %s product types were processed in total (%s created, %s updated, %s failed to sync" + + " and %s product types with at least one NestedType or a Set of NestedType attribute definition(s)" + + " referencing a missing product type).", + getProcessed(), + getCreated(), + getUpdated(), + getFailed(), + getNumberOfProductTypesWithMissingNestedProductTypes()); + } + + /** + * Returns the total number of product types with at least one NestedType or a Set of NestedType + * attribute definition(s) referencing a missing productType. + * + * @return the total number of product types with at least one NestedType or a Set of NestedType + * attribute definition(s) referencing a missing productType. + */ + public int getNumberOfProductTypesWithMissingNestedProductTypes() { + final Set productTypesWithMissingReferences = new HashSet<>(); + + missingNestedProductTypes.values().stream() + .map(ConcurrentHashMap::keySet) + .flatMap(Collection::stream) + .forEach(productTypesWithMissingReferences::add); + + return productTypesWithMissingReferences.size(); + } + + /** + * @return an unmodifiable {@link ConcurrentHashMap} ({@code missingNestedProductTypes}) which + * keeps track of the keys of missing product types, the keys of the product types which are + * referencing those missing product types and a list of attribute definitions which contains + * those references. + *

    + *
  • key: key of the missing product type + *
  • value: a map of which consists of: + *
      + *
    • key: key of the product type referencing the missing product type. + *
    • value: a set of the attribute definition drafts which contains the reference to + * the missing product type. + *
    + *
+ */ + public Map< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + getProductTypeKeysWithMissingParents() { + return Collections.unmodifiableMap(missingNestedProductTypes); + } + + /** + * Adds a new entry for a {@code referencingAttributeDefinitionDraft} that is pointed to by a + * {@code referencingProductTypeKey} which is pointed to by a {@code missingNestedProductTypeKey}. + * + *

If any of the inner sets/maps is not existing (null), this method will create a new set/map + * with only this new entry. + * + *

Important: This method is meant to be used only for internal use of the library and should + * not be used by externally. + * + * @param missingNestedProductTypeKey the key of the missing nested product type. + * @param referencingProductTypeKey the key of the referencing product type. + * @param referencingAttributeDefinitionDraft the referencing attribute definition draft. + */ + public void putMissingNestedProductType( + @Nonnull final String missingNestedProductTypeKey, + @Nonnull final String referencingProductTypeKey, + @Nonnull final AttributeDefinitionDraft referencingAttributeDefinitionDraft) { + + final ConcurrentHashMap> + existingReferencingProductTypes = + missingNestedProductTypes.get(missingNestedProductTypeKey); + + if (existingReferencingProductTypes != null) { + final ConcurrentHashMap.KeySetView + existingAttributeDefinitionDrafts = + existingReferencingProductTypes.get(referencingProductTypeKey); + + if (existingAttributeDefinitionDrafts != null) { + existingAttributeDefinitionDrafts.add(referencingAttributeDefinitionDraft); + } else { + existingReferencingProductTypes.put( + referencingProductTypeKey, asSet(referencingAttributeDefinitionDraft)); + } + } else { + missingNestedProductTypes.put( + missingNestedProductTypeKey, + asMap(referencingProductTypeKey, referencingAttributeDefinitionDraft)); } - - /** - * Builds a summary of the product type sync statistics instance that looks like the following example: - * - *

"Summary: 2 product types were processed in total (0 created, 0 updated, 0 failed to sync - * and 0 product types with at least one NestedType or a Set of NestedType attribute definition(s) referencing a - * missing productType)." - * - * @return a summary message of the product types sync statistics instance. - */ - @Override - public String getReportMessage() { - return format( - "Summary: %s product types were processed in total (%s created, %s updated, %s failed to sync" - + " and %s product types with at least one NestedType or a Set of NestedType attribute definition(s)" - + " referencing a missing product type).", - getProcessed(), getCreated(), getUpdated(), getFailed(), - getNumberOfProductTypesWithMissingNestedProductTypes()); - } - - /** - * Returns the total number of product types with at least one NestedType or a Set of NestedType attribute - * definition(s) referencing a missing productType. - * - * @return the total number of product types with at least one NestedType or a Set of NestedType attribute - * definition(s) referencing a missing productType. - */ - public int getNumberOfProductTypesWithMissingNestedProductTypes() { - final Set productTypesWithMissingReferences = new HashSet<>(); - - missingNestedProductTypes - .values() - .stream() - .map(ConcurrentHashMap::keySet) - .flatMap(Collection::stream) - .forEach(productTypesWithMissingReferences::add); - - return productTypesWithMissingReferences.size(); - } - - /** - * @return an unmodifiable {@link ConcurrentHashMap} ({@code missingNestedProductTypes}) which keeps track of the - * keys of missing product types, the keys of the product types which are referencing those missing product - * types and a list of attribute definitions which contains those references. - *

    - *
  • key: key of the missing product type
  • - *
  • value: a map of which consists of: - *
      - *
    • key: key of the product type referencing the missing product type.
    • - *
    • value: a set of the attribute definition drafts which contains the reference - * to the missing product type.
    • - *
    - *
  • - *
- */ - public Map>> getProductTypeKeysWithMissingParents() { - return Collections.unmodifiableMap(missingNestedProductTypes); - } - - /** - * Adds a new entry for a {@code referencingAttributeDefinitionDraft} that is pointed to by - * a {@code referencingProductTypeKey} which is pointed to by a {@code missingNestedProductTypeKey}. - * - *

If any of the inner sets/maps is not existing (null), this method will create a new set/map with only this new - * entry. - * - *

Important: This method is meant to be used only for internal use of the library and should not be used by - * externally. - * - * @param missingNestedProductTypeKey the key of the missing nested product type. - * @param referencingProductTypeKey the key of the referencing product type. - * @param referencingAttributeDefinitionDraft the referencing attribute definition draft. - */ - public void putMissingNestedProductType( - @Nonnull final String missingNestedProductTypeKey, - @Nonnull final String referencingProductTypeKey, - @Nonnull final AttributeDefinitionDraft referencingAttributeDefinitionDraft) { - - final ConcurrentHashMap> - existingReferencingProductTypes = missingNestedProductTypes.get(missingNestedProductTypeKey); - - if (existingReferencingProductTypes != null) { - final ConcurrentHashMap.KeySetView existingAttributeDefinitionDrafts = - existingReferencingProductTypes.get(referencingProductTypeKey); - - if (existingAttributeDefinitionDrafts != null) { - existingAttributeDefinitionDrafts.add(referencingAttributeDefinitionDraft); - } else { - existingReferencingProductTypes - .put( - referencingProductTypeKey, - asSet(referencingAttributeDefinitionDraft)); - } - } else { - missingNestedProductTypes - .put( - missingNestedProductTypeKey, - asMap(referencingProductTypeKey, referencingAttributeDefinitionDraft)); - } - } - - @Nonnull - private ConcurrentHashMap> asMap( - @Nonnull final String referencingProductTypeKey, - @Nonnull final AttributeDefinitionDraft referencingAttributeDefinitionDraft) { - - final ConcurrentHashMap> - newReferencingProductTypes = new ConcurrentHashMap<>(); - newReferencingProductTypes - .put( - referencingProductTypeKey, - asSet(referencingAttributeDefinitionDraft)); - return newReferencingProductTypes; - } - - @Nonnull - private ConcurrentHashMap.KeySetView asSet( - @Nonnull final AttributeDefinitionDraft referencingAttributeDefinitionDraft) { - - final ConcurrentHashMap.KeySetView newAttributeDefinitions = - ConcurrentHashMap.newKeySet(); - newAttributeDefinitions.add(referencingAttributeDefinitionDraft); - return newAttributeDefinitions; - } - - /** - * Removes all occurrences of the referencing product type key from {@link #missingNestedProductTypes}. - * If there are no referencing product types for any missing nested product type, the whole entry for this - * missing nested product type will be removed from {@link #missingNestedProductTypes}. - * - *

Important: This method is meant to be used only for internal use of the library and should not be used by - * externally. - * - * @param referencingProductTypeKey the key that should be removed from {@link #missingNestedProductTypes}. - */ - public void removeReferencingProductTypeKey(@Nonnull final String referencingProductTypeKey) { - - final Iterator>> - referencingProductTypesIterator = missingNestedProductTypes.values().iterator(); - - while (referencingProductTypesIterator.hasNext()) { - final ConcurrentHashMap> - referencingProductTypes = referencingProductTypesIterator.next(); - - referencingProductTypes.remove(referencingProductTypeKey); - - // If there are no referencing product types for this missing nested product type, - // remove referencing product types map. - if (referencingProductTypes.isEmpty()) { - referencingProductTypesIterator.remove(); - } - } + } + + @Nonnull + private ConcurrentHashMap> + asMap( + @Nonnull final String referencingProductTypeKey, + @Nonnull final AttributeDefinitionDraft referencingAttributeDefinitionDraft) { + + final ConcurrentHashMap> + newReferencingProductTypes = new ConcurrentHashMap<>(); + newReferencingProductTypes.put( + referencingProductTypeKey, asSet(referencingAttributeDefinitionDraft)); + return newReferencingProductTypes; + } + + @Nonnull + private ConcurrentHashMap.KeySetView asSet( + @Nonnull final AttributeDefinitionDraft referencingAttributeDefinitionDraft) { + + final ConcurrentHashMap.KeySetView newAttributeDefinitions = + ConcurrentHashMap.newKeySet(); + newAttributeDefinitions.add(referencingAttributeDefinitionDraft); + return newAttributeDefinitions; + } + + /** + * Removes all occurrences of the referencing product type key from {@link + * #missingNestedProductTypes}. If there are no referencing product types for any missing nested + * product type, the whole entry for this missing nested product type will be removed from {@link + * #missingNestedProductTypes}. + * + *

Important: This method is meant to be used only for internal use of the library and should + * not be used by externally. + * + * @param referencingProductTypeKey the key that should be removed from {@link + * #missingNestedProductTypes}. + */ + public void removeReferencingProductTypeKey(@Nonnull final String referencingProductTypeKey) { + + final Iterator< + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + referencingProductTypesIterator = missingNestedProductTypes.values().iterator(); + + while (referencingProductTypesIterator.hasNext()) { + final ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView> + referencingProductTypes = referencingProductTypesIterator.next(); + + referencingProductTypes.remove(referencingProductTypeKey); + + // If there are no referencing product types for this missing nested product type, + // remove referencing product types map. + if (referencingProductTypes.isEmpty()) { + referencingProductTypesIterator.remove(); + } } + } } diff --git a/src/main/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionUpdateActionUtils.java b/src/main/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionUpdateActionUtils.java index 460b7c2b1c..f4b0aa4c7e 100644 --- a/src/main/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionUpdateActionUtils.java @@ -1,5 +1,11 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static com.commercetools.sync.producttypes.utils.LocalizedEnumValueUpdateActionUtils.buildLocalizedEnumValuesUpdateActions; +import static com.commercetools.sync.producttypes.utils.PlainEnumValueUpdateActionUtils.buildEnumValuesUpdateActions; +import static java.util.Optional.ofNullable; + import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.EnumValue; @@ -19,285 +25,303 @@ import io.sphere.sdk.producttypes.commands.updateactions.ChangeInputHint; import io.sphere.sdk.producttypes.commands.updateactions.ChangeIsSearchable; import io.sphere.sdk.producttypes.commands.updateactions.SetInputTip; - -import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; import java.util.Optional; +import javax.annotation.Nonnull; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static com.commercetools.sync.producttypes.utils.LocalizedEnumValueUpdateActionUtils.buildLocalizedEnumValuesUpdateActions; -import static com.commercetools.sync.producttypes.utils.PlainEnumValueUpdateActionUtils.buildEnumValuesUpdateActions; -import static java.util.Optional.ofNullable; - -/** - * This class is only meant for the internal use of the commercetools-sync-java library. - */ +/** This class is only meant for the internal use of the commercetools-sync-java library. */ final class AttributeDefinitionUpdateActionUtils { - /** - * Compares all the fields of an {@link AttributeDefinition} and an {@link AttributeDefinitionDraft} and returns - * a list of {@link UpdateAction}<{@link ProductType}> as a result. If both the {@link AttributeDefinition} - * and the {@link AttributeDefinitionDraft} have identical fields, then no update action is needed and hence an - * empty {@link List} is returned. - * - * @param oldAttributeDefinition the old attribute definition which should be updated. - * @param newAttributeDefinitionDraft the new attribute definition draft where we get the new fields. - * @return A list with the update actions or an empty list if the attribute definition fields are identical. - * - * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. - */ - @Nonnull - static List> buildActions( - @Nonnull final AttributeDefinition oldAttributeDefinition, - @Nonnull final AttributeDefinitionDraft newAttributeDefinitionDraft) { - - final List> updateActions = filterEmptyOptionals( - buildChangeLabelUpdateAction(oldAttributeDefinition, newAttributeDefinitionDraft), - buildSetInputTipUpdateAction(oldAttributeDefinition, newAttributeDefinitionDraft), - buildChangeIsSearchableUpdateAction(oldAttributeDefinition, newAttributeDefinitionDraft), - buildChangeInputHintUpdateAction(oldAttributeDefinition, newAttributeDefinitionDraft), - buildChangeAttributeConstraintUpdateAction(oldAttributeDefinition, newAttributeDefinitionDraft) - ); - - updateActions.addAll(buildEnumUpdateActions(oldAttributeDefinition, newAttributeDefinitionDraft)); - return updateActions; - } - - /** - * Checks if both the supplied {@code oldAttributeDefinition} and {@code newAttributeDefinitionDraft} have an - * {@link AttributeType} that is either an {@link EnumAttributeType} or a {@link LocalizedEnumAttributeType} or - * a {@link SetAttributeType} with a subtype that is either an {@link EnumAttributeType} or a - * {@link LocalizedEnumAttributeType}. - * - *

The method compares all the {@link EnumValue} and {@link LocalizedEnumValue} values of the - * {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} attribute types and returns a list of - * {@link UpdateAction}<{@link ProductType}> as a result. If both the {@link AttributeDefinition} and the - * {@link AttributeDefinitionDraft} have identical enum values, then no update action is needed and hence an empty - * {@link List} is returned.

- * - *

Note: This method expects the supplied {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} - * to have the same {@link AttributeType}. Otherwise, the behaviour is not guaranteed.

- * - * @param oldAttributeDefinition the attribute definition which should be updated. - * @param newAttributeDefinitionDraft the new attribute definition draft where we get the new fields. - * @return A list with the update actions or an empty list if the attribute definition enums are identical. - * - * @throws DuplicateKeyException in case there are enum values with duplicate keys. - */ - @Nonnull - static List> buildEnumUpdateActions( - @Nonnull final AttributeDefinition oldAttributeDefinition, - @Nonnull final AttributeDefinitionDraft newAttributeDefinitionDraft) { - - final AttributeType oldAttributeType = oldAttributeDefinition.getAttributeType(); - final AttributeType newAttributeType = newAttributeDefinitionDraft.getAttributeType(); - - return getEnumAttributeType(oldAttributeType) - .map(oldEnumAttributeType -> + /** + * Compares all the fields of an {@link AttributeDefinition} and an {@link + * AttributeDefinitionDraft} and returns a list of {@link UpdateAction}<{@link ProductType}> + * as a result. If both the {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} + * have identical fields, then no update action is needed and hence an empty {@link List} is + * returned. + * + * @param oldAttributeDefinition the old attribute definition which should be updated. + * @param newAttributeDefinitionDraft the new attribute definition draft where we get the new + * fields. + * @return A list with the update actions or an empty list if the attribute definition fields are + * identical. + * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. + */ + @Nonnull + static List> buildActions( + @Nonnull final AttributeDefinition oldAttributeDefinition, + @Nonnull final AttributeDefinitionDraft newAttributeDefinitionDraft) { + + final List> updateActions = + filterEmptyOptionals( + buildChangeLabelUpdateAction(oldAttributeDefinition, newAttributeDefinitionDraft), + buildSetInputTipUpdateAction(oldAttributeDefinition, newAttributeDefinitionDraft), + buildChangeIsSearchableUpdateAction( + oldAttributeDefinition, newAttributeDefinitionDraft), + buildChangeInputHintUpdateAction(oldAttributeDefinition, newAttributeDefinitionDraft), + buildChangeAttributeConstraintUpdateAction( + oldAttributeDefinition, newAttributeDefinitionDraft)); + + updateActions.addAll( + buildEnumUpdateActions(oldAttributeDefinition, newAttributeDefinitionDraft)); + return updateActions; + } + + /** + * Checks if both the supplied {@code oldAttributeDefinition} and {@code + * newAttributeDefinitionDraft} have an {@link AttributeType} that is either an {@link + * EnumAttributeType} or a {@link LocalizedEnumAttributeType} or a {@link SetAttributeType} with a + * subtype that is either an {@link EnumAttributeType} or a {@link LocalizedEnumAttributeType}. + * + *

The method compares all the {@link EnumValue} and {@link LocalizedEnumValue} values of the + * {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} attribute types and + * returns a list of {@link UpdateAction}<{@link ProductType}> as a result. If both the + * {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} have identical enum + * values, then no update action is needed and hence an empty {@link List} is returned. + * + *

Note: This method expects the supplied {@link AttributeDefinition} and the {@link + * AttributeDefinitionDraft} to have the same {@link AttributeType}. Otherwise, the behaviour is + * not guaranteed. + * + * @param oldAttributeDefinition the attribute definition which should be updated. + * @param newAttributeDefinitionDraft the new attribute definition draft where we get the new + * fields. + * @return A list with the update actions or an empty list if the attribute definition enums are + * identical. + * @throws DuplicateKeyException in case there are enum values with duplicate keys. + */ + @Nonnull + static List> buildEnumUpdateActions( + @Nonnull final AttributeDefinition oldAttributeDefinition, + @Nonnull final AttributeDefinitionDraft newAttributeDefinitionDraft) { + + final AttributeType oldAttributeType = oldAttributeDefinition.getAttributeType(); + final AttributeType newAttributeType = newAttributeDefinitionDraft.getAttributeType(); + + return getEnumAttributeType(oldAttributeType) + .map( + oldEnumAttributeType -> getEnumAttributeType(newAttributeType) - .map(newEnumAttributeType -> - buildEnumValuesUpdateActions(oldAttributeDefinition.getName(), - oldEnumAttributeType.getValues(), - newEnumAttributeType.getValues()) - ) - .orElseGet(Collections::emptyList) - ) - .orElseGet(() -> + .map( + newEnumAttributeType -> + buildEnumValuesUpdateActions( + oldAttributeDefinition.getName(), + oldEnumAttributeType.getValues(), + newEnumAttributeType.getValues())) + .orElseGet(Collections::emptyList)) + .orElseGet( + () -> getLocalizedEnumAttributeType(oldAttributeType) - .map(oldLocalizedEnumAttributeType -> - getLocalizedEnumAttributeType(newAttributeType) - .map(newLocalizedEnumAttributeType -> - - buildLocalizedEnumValuesUpdateActions(oldAttributeDefinition.getName(), - oldLocalizedEnumAttributeType.getValues(), - newLocalizedEnumAttributeType.getValues()) - - ) - .orElseGet(Collections::emptyList) - ) - .orElseGet(Collections::emptyList) - ); + .map( + oldLocalizedEnumAttributeType -> + getLocalizedEnumAttributeType(newAttributeType) + .map( + newLocalizedEnumAttributeType -> + buildLocalizedEnumValuesUpdateActions( + oldAttributeDefinition.getName(), + oldLocalizedEnumAttributeType.getValues(), + newLocalizedEnumAttributeType.getValues())) + .orElseGet(Collections::emptyList)) + .orElseGet(Collections::emptyList)); + } + + /** + * Returns an optional containing the attribute type if is an {@link EnumAttributeType} or if the + * {@link AttributeType} is a {@link SetAttributeType} with an {@link EnumAttributeType} as a + * subtype, it returns this subtype in the optional. Otherwise, an empty optional. + * + * @param attributeType the attribute type. + * @return an optional containing the attribute type if is an {@link EnumAttributeType} or if the + * {@link AttributeType} is a {@link SetAttributeType} with an {@link EnumAttributeType} as a + * subtype, it returns this subtype in the optional. Otherwise, an empty optional. + */ + private static Optional getEnumAttributeType( + @Nonnull final AttributeType attributeType) { + + if (attributeType instanceof EnumAttributeType) { + return Optional.of((EnumAttributeType) attributeType); } - /** - * Returns an optional containing the attribute type if is an {@link EnumAttributeType} or if the - * {@link AttributeType} is a {@link SetAttributeType} with an {@link EnumAttributeType} as a subtype, it returns - * this subtype in the optional. Otherwise, an empty optional. - * - * @param attributeType the attribute type. - * @return an optional containing the attribute type if is an {@link EnumAttributeType} or if the - * {@link AttributeType} is a {@link SetAttributeType} with an {@link EnumAttributeType} as a subtype, it - * returns this subtype in the optional. Otherwise, an empty optional. - */ - private static Optional getEnumAttributeType( - @Nonnull final AttributeType attributeType) { - - if (attributeType instanceof EnumAttributeType) { - return Optional.of((EnumAttributeType) attributeType); - } - - if (attributeType instanceof SetAttributeType) { - final SetAttributeType setFieldType = (SetAttributeType) attributeType; - final AttributeType subType = setFieldType.getElementType(); - - if (subType instanceof EnumAttributeType) { - return Optional.of((EnumAttributeType) subType); - } - } - - return Optional.empty(); - } + if (attributeType instanceof SetAttributeType) { + final SetAttributeType setFieldType = (SetAttributeType) attributeType; + final AttributeType subType = setFieldType.getElementType(); - /** - * Returns an optional containing the attribute type if is an {@link LocalizedEnumAttributeType} or if the - * {@link AttributeType} is a {@link SetAttributeType} with an {@link LocalizedEnumAttributeType} as a subtype, it - * returns this subtype in the optional. Otherwise, an empty optional. - * - * @param attributeType the attribute type. - * @return an optional containing the attribute type if is an {@link LocalizedEnumAttributeType} or if the - * {@link AttributeType} is a {@link SetAttributeType} with an {@link LocalizedEnumAttributeType} as a - * subtype, it returns this subtype in the optional. Otherwise, an empty optional. - */ - private static Optional getLocalizedEnumAttributeType( - @Nonnull final AttributeType attributeType) { - - if (attributeType instanceof LocalizedEnumAttributeType) { - return Optional.of((LocalizedEnumAttributeType) attributeType); - } - - if (attributeType instanceof SetAttributeType) { - final SetAttributeType setFieldType = (SetAttributeType) attributeType; - final AttributeType subType = setFieldType.getElementType(); - - if (subType instanceof LocalizedEnumAttributeType) { - return Optional.of((LocalizedEnumAttributeType) subType); - } - } - - return Optional.empty(); + if (subType instanceof EnumAttributeType) { + return Optional.of((EnumAttributeType) subType); + } } - /** - * Compares the {@link LocalizedString} labels of an {@link AttributeDefinition} and an - * {@link AttributeDefinitionDraft} and returns an {@link UpdateAction}<{@link ProductType}> as a result in - * an {@link Optional}. If both the {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} have the - * same label, then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldAttributeDefinition the attribute definition which should be updated. - * @param newAttributeDefinition the attribute definition draft where we get the new label. - * @return A filled optional with the update action or an empty optional if the labels are identical. - */ - @Nonnull - static Optional> buildChangeLabelUpdateAction( - @Nonnull final AttributeDefinition oldAttributeDefinition, - @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { - - return buildUpdateAction(oldAttributeDefinition.getLabel(), newAttributeDefinition.getLabel(), - () -> ChangeAttributeDefinitionLabel.of(oldAttributeDefinition.getName(), - newAttributeDefinition.getLabel()) - ); + return Optional.empty(); + } + + /** + * Returns an optional containing the attribute type if is an {@link LocalizedEnumAttributeType} + * or if the {@link AttributeType} is a {@link SetAttributeType} with an {@link + * LocalizedEnumAttributeType} as a subtype, it returns this subtype in the optional. Otherwise, + * an empty optional. + * + * @param attributeType the attribute type. + * @return an optional containing the attribute type if is an {@link LocalizedEnumAttributeType} + * or if the {@link AttributeType} is a {@link SetAttributeType} with an {@link + * LocalizedEnumAttributeType} as a subtype, it returns this subtype in the optional. + * Otherwise, an empty optional. + */ + private static Optional getLocalizedEnumAttributeType( + @Nonnull final AttributeType attributeType) { + + if (attributeType instanceof LocalizedEnumAttributeType) { + return Optional.of((LocalizedEnumAttributeType) attributeType); } - /** - * Compares the {@link LocalizedString} input tips of an {@link AttributeDefinition} and an - * {@link AttributeDefinitionDraft} and returns an {@link UpdateAction}<{@link ProductType}> as a result in - * an {@link Optional}. If both the {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} have the - * same input tip, then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldAttributeDefinition the attribute definition which should be updated. - * @param newAttributeDefinition the attribute definition draft where we get the new input tip. - * @return A filled optional with the update action or an empty optional if the labels are identical. - */ - @Nonnull - static Optional> buildSetInputTipUpdateAction( - @Nonnull final AttributeDefinition oldAttributeDefinition, - @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { - - return buildUpdateAction(oldAttributeDefinition.getInputTip(), newAttributeDefinition.getInputTip(), - () -> SetInputTip.of(oldAttributeDefinition.getName(), newAttributeDefinition.getInputTip()) - ); - } - - /** - * Compares the 'isSearchable' fields of an {@link AttributeDefinition} and an - * {@link AttributeDefinitionDraft} and returns an {@link UpdateAction}<{@link ProductType}> as a result in - * an {@link Optional}. If both the {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} have the - * same 'isSearchable' field, then no update action is needed and hence an empty {@link Optional} is returned. - * - *

Note: A {@code null} {@code isSearchable} value in the {@link AttributeDefinitionDraft} is treated as a - * {@code true} value which is the default value of CTP. - * - * @param oldAttributeDefinition the attribute definition which should be updated. - * @param newAttributeDefinition the attribute definition draft where we get the new 'isSearchable' field. - * @return A filled optional with the update action or an empty optional if the 'isSearchable' fields are identical. - */ - @Nonnull - static Optional> buildChangeIsSearchableUpdateAction( - @Nonnull final AttributeDefinition oldAttributeDefinition, - @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { - - final Boolean searchable = ofNullable(newAttributeDefinition.isSearchable()).orElse(true); - - return buildUpdateAction(oldAttributeDefinition.isSearchable(), searchable, - () -> ChangeIsSearchable.of(oldAttributeDefinition.getName(), searchable) - ); - } + if (attributeType instanceof SetAttributeType) { + final SetAttributeType setFieldType = (SetAttributeType) attributeType; + final AttributeType subType = setFieldType.getElementType(); - /** - * Compares the input hints of an {@link AttributeDefinition} and an {@link AttributeDefinitionDraft} and returns - * an {@link UpdateAction}<{@link ProductType}> as a result in an {@link Optional}. If both the - * {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} have the same input hints, then no update - * action is needed and hence an empty {@link Optional} is returned. - * - *

Note: A {@code null} {@code inputHint} value in the {@link AttributeDefinitionDraft} is treated as a - * {@code TextInputHint#SINGLE_LINE} value which is the default value of CTP. - * - * @param oldAttributeDefinition the attribute definition which should be updated. - * @param newAttributeDefinition the attribute definition draft where we get the new input hint. - * @return A filled optional with the update action or an empty optional if the input hints are identical. - */ - @Nonnull - static Optional> buildChangeInputHintUpdateAction( - @Nonnull final AttributeDefinition oldAttributeDefinition, - @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { - - final TextInputHint inputHint = ofNullable(newAttributeDefinition.getInputHint()) - .orElse(TextInputHint.SINGLE_LINE); - - return buildUpdateAction(oldAttributeDefinition.getInputHint(), inputHint, - () -> ChangeInputHint.of(oldAttributeDefinition.getName(), inputHint) - ); + if (subType instanceof LocalizedEnumAttributeType) { + return Optional.of((LocalizedEnumAttributeType) subType); + } } - /** - * Compares the attribute constraints of an {@link AttributeDefinition} and an {@link AttributeDefinitionDraft} - * and returns an {@link UpdateAction}<{@link ProductType}> as a result in an {@link Optional}. If both the - * {@link AttributeDefinition} and the {@link AttributeDefinitionDraft} have the same attribute constraints, then - * no update action is needed and hence an empty {@link Optional} is returned. - * - *

Note: A {@code null} {@code AttributeConstraint} value in the {@link AttributeDefinitionDraft} is treated as a - * {@code AttributeConstraint#NONE} value which is the default value of CTP. - * - * @param oldAttributeDefinition the attribute definition which should be updated. - * @param newAttributeDefinition the attribute definition draft where we get the new attribute constraint. - * @return A filled optional with the update action or an empty optional if the attribute constraints are identical. - */ - @Nonnull - static Optional> buildChangeAttributeConstraintUpdateAction( - @Nonnull final AttributeDefinition oldAttributeDefinition, - @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { - - final AttributeConstraint attributeConstraint = ofNullable(newAttributeDefinition.getAttributeConstraint()) + return Optional.empty(); + } + + /** + * Compares the {@link LocalizedString} labels of an {@link AttributeDefinition} and an {@link + * AttributeDefinitionDraft} and returns an {@link UpdateAction}<{@link ProductType}> as a + * result in an {@link Optional}. If both the {@link AttributeDefinition} and the {@link + * AttributeDefinitionDraft} have the same label, then no update action is needed and hence an + * empty {@link Optional} is returned. + * + * @param oldAttributeDefinition the attribute definition which should be updated. + * @param newAttributeDefinition the attribute definition draft where we get the new label. + * @return A filled optional with the update action or an empty optional if the labels are + * identical. + */ + @Nonnull + static Optional> buildChangeLabelUpdateAction( + @Nonnull final AttributeDefinition oldAttributeDefinition, + @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { + + return buildUpdateAction( + oldAttributeDefinition.getLabel(), + newAttributeDefinition.getLabel(), + () -> + ChangeAttributeDefinitionLabel.of( + oldAttributeDefinition.getName(), newAttributeDefinition.getLabel())); + } + + /** + * Compares the {@link LocalizedString} input tips of an {@link AttributeDefinition} and an {@link + * AttributeDefinitionDraft} and returns an {@link UpdateAction}<{@link ProductType}> as a + * result in an {@link Optional}. If both the {@link AttributeDefinition} and the {@link + * AttributeDefinitionDraft} have the same input tip, then no update action is needed and hence an + * empty {@link Optional} is returned. + * + * @param oldAttributeDefinition the attribute definition which should be updated. + * @param newAttributeDefinition the attribute definition draft where we get the new input tip. + * @return A filled optional with the update action or an empty optional if the labels are + * identical. + */ + @Nonnull + static Optional> buildSetInputTipUpdateAction( + @Nonnull final AttributeDefinition oldAttributeDefinition, + @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { + + return buildUpdateAction( + oldAttributeDefinition.getInputTip(), + newAttributeDefinition.getInputTip(), + () -> + SetInputTip.of(oldAttributeDefinition.getName(), newAttributeDefinition.getInputTip())); + } + + /** + * Compares the 'isSearchable' fields of an {@link AttributeDefinition} and an {@link + * AttributeDefinitionDraft} and returns an {@link UpdateAction}<{@link ProductType}> as a + * result in an {@link Optional}. If both the {@link AttributeDefinition} and the {@link + * AttributeDefinitionDraft} have the same 'isSearchable' field, then no update action is needed + * and hence an empty {@link Optional} is returned. + * + *

Note: A {@code null} {@code isSearchable} value in the {@link AttributeDefinitionDraft} is + * treated as a {@code true} value which is the default value of CTP. + * + * @param oldAttributeDefinition the attribute definition which should be updated. + * @param newAttributeDefinition the attribute definition draft where we get the new + * 'isSearchable' field. + * @return A filled optional with the update action or an empty optional if the 'isSearchable' + * fields are identical. + */ + @Nonnull + static Optional> buildChangeIsSearchableUpdateAction( + @Nonnull final AttributeDefinition oldAttributeDefinition, + @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { + + final Boolean searchable = ofNullable(newAttributeDefinition.isSearchable()).orElse(true); + + return buildUpdateAction( + oldAttributeDefinition.isSearchable(), + searchable, + () -> ChangeIsSearchable.of(oldAttributeDefinition.getName(), searchable)); + } + + /** + * Compares the input hints of an {@link AttributeDefinition} and an {@link + * AttributeDefinitionDraft} and returns an {@link UpdateAction}<{@link ProductType}> as a + * result in an {@link Optional}. If both the {@link AttributeDefinition} and the {@link + * AttributeDefinitionDraft} have the same input hints, then no update action is needed and hence + * an empty {@link Optional} is returned. + * + *

Note: A {@code null} {@code inputHint} value in the {@link AttributeDefinitionDraft} is + * treated as a {@code TextInputHint#SINGLE_LINE} value which is the default value of CTP. + * + * @param oldAttributeDefinition the attribute definition which should be updated. + * @param newAttributeDefinition the attribute definition draft where we get the new input hint. + * @return A filled optional with the update action or an empty optional if the input hints are + * identical. + */ + @Nonnull + static Optional> buildChangeInputHintUpdateAction( + @Nonnull final AttributeDefinition oldAttributeDefinition, + @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { + + final TextInputHint inputHint = + ofNullable(newAttributeDefinition.getInputHint()).orElse(TextInputHint.SINGLE_LINE); + + return buildUpdateAction( + oldAttributeDefinition.getInputHint(), + inputHint, + () -> ChangeInputHint.of(oldAttributeDefinition.getName(), inputHint)); + } + + /** + * Compares the attribute constraints of an {@link AttributeDefinition} and an {@link + * AttributeDefinitionDraft} and returns an {@link UpdateAction}<{@link ProductType}> as a + * result in an {@link Optional}. If both the {@link AttributeDefinition} and the {@link + * AttributeDefinitionDraft} have the same attribute constraints, then no update action is needed + * and hence an empty {@link Optional} is returned. + * + *

Note: A {@code null} {@code AttributeConstraint} value in the {@link + * AttributeDefinitionDraft} is treated as a {@code AttributeConstraint#NONE} value which is the + * default value of CTP. + * + * @param oldAttributeDefinition the attribute definition which should be updated. + * @param newAttributeDefinition the attribute definition draft where we get the new attribute + * constraint. + * @return A filled optional with the update action or an empty optional if the attribute + * constraints are identical. + */ + @Nonnull + static Optional> buildChangeAttributeConstraintUpdateAction( + @Nonnull final AttributeDefinition oldAttributeDefinition, + @Nonnull final AttributeDefinitionDraft newAttributeDefinition) { + + final AttributeConstraint attributeConstraint = + ofNullable(newAttributeDefinition.getAttributeConstraint()) .orElse(AttributeConstraint.NONE); - return buildUpdateAction(oldAttributeDefinition.getAttributeConstraint(), - attributeConstraint, - () -> ChangeAttributeConstraint.of(oldAttributeDefinition.getName(), - attributeConstraint) - ); - } + return buildUpdateAction( + oldAttributeDefinition.getAttributeConstraint(), + attributeConstraint, + () -> ChangeAttributeConstraint.of(oldAttributeDefinition.getName(), attributeConstraint)); + } - private AttributeDefinitionUpdateActionUtils() { - } + private AttributeDefinitionUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionsUpdateActionUtils.java b/src/main/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionsUpdateActionUtils.java index 2983d6033f..ac694c2234 100644 --- a/src/main/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionsUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionsUpdateActionUtils.java @@ -1,5 +1,13 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildActions; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import com.commercetools.sync.commons.exceptions.DuplicateNameException; @@ -14,9 +22,6 @@ import io.sphere.sdk.producttypes.commands.updateactions.AddAttributeDefinition; import io.sphere.sdk.producttypes.commands.updateactions.ChangeAttributeOrderByName; import io.sphere.sdk.producttypes.commands.updateactions.RemoveAttributeDefinition; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -26,298 +31,281 @@ import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildActions; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; - -/** - * This class is only meant for the internal use of the commercetools-sync-java library. - */ +/** This class is only meant for the internal use of the commercetools-sync-java library. */ final class AttributeDefinitionsUpdateActionUtils { - /** - * Compares a list of {@link AttributeDefinition}s with a list of {@link AttributeDefinitionDraft}s to - * returns a {@link List} of {@link UpdateAction}<{@link ProductType}>. If both lists have identical - * AttributeDefinitions, then no update actions are needed and hence an empty {@link List} is returned. - * - *

If the list of new {@link AttributeDefinitionDraft}s is {@code null}, then remove actions are built for - * every existing attribute definition in the {@code oldAttributeDefinitions} list. - * - *

Note: The method will ignore/filter out {@code null} attribute definitions drafts from the passed - * {@code newAttributeDefinitionDrafts}.

- * - * @param oldAttributeDefinitions the old list of attribute definitions. - * @param newAttributeDefinitionsDrafts the new list of attribute definitions drafts. - * @return a list of attribute definitions update actions if the list of attribute definitions are not identical. - * Otherwise, if the attribute definitions are identical, an empty list is returned. - * @throws BuildUpdateActionException in case there are attribute definitions drafts with duplicate names or enums - * duplicate keys. - */ - @Nonnull - static List> buildAttributeDefinitionsUpdateActions( - @Nonnull final List oldAttributeDefinitions, - @Nullable final List newAttributeDefinitionsDrafts) - throws BuildUpdateActionException { - - if (newAttributeDefinitionsDrafts != null) { - return buildUpdateActions( - oldAttributeDefinitions, - newAttributeDefinitionsDrafts.stream().filter(Objects::nonNull).collect(toList()) - ); - } else { - return oldAttributeDefinitions - .stream() - .map(AttributeDefinition::getName) - .map(RemoveAttributeDefinition::of) - .collect(Collectors.toList()); - } + /** + * Compares a list of {@link AttributeDefinition}s with a list of {@link + * AttributeDefinitionDraft}s to returns a {@link List} of {@link UpdateAction}<{@link + * ProductType}>. If both lists have identical AttributeDefinitions, then no update actions are + * needed and hence an empty {@link List} is returned. + * + *

If the list of new {@link AttributeDefinitionDraft}s is {@code null}, then remove actions + * are built for every existing attribute definition in the {@code oldAttributeDefinitions} list. + * + *

Note: The method will ignore/filter out {@code null} attribute definitions drafts from the + * passed {@code newAttributeDefinitionDrafts}. + * + * @param oldAttributeDefinitions the old list of attribute definitions. + * @param newAttributeDefinitionsDrafts the new list of attribute definitions drafts. + * @return a list of attribute definitions update actions if the list of attribute definitions are + * not identical. Otherwise, if the attribute definitions are identical, an empty list is + * returned. + * @throws BuildUpdateActionException in case there are attribute definitions drafts with + * duplicate names or enums duplicate keys. + */ + @Nonnull + static List> buildAttributeDefinitionsUpdateActions( + @Nonnull final List oldAttributeDefinitions, + @Nullable final List newAttributeDefinitionsDrafts) + throws BuildUpdateActionException { + + if (newAttributeDefinitionsDrafts != null) { + return buildUpdateActions( + oldAttributeDefinitions, + newAttributeDefinitionsDrafts.stream().filter(Objects::nonNull).collect(toList())); + } else { + return oldAttributeDefinitions.stream() + .map(AttributeDefinition::getName) + .map(RemoveAttributeDefinition::of) + .collect(Collectors.toList()); } - - /** - * Compares a list of {@link AttributeDefinition}s with a list of {@link AttributeDefinitionDraft}s. - * The method serves as an implementation for attribute definitions syncing. The method takes in functions - * for building the required update actions (AddAttribute, RemoveAttribute, ChangeAttributeOrder and 1-1 - * update actions on attribute definitions (e.g. changeAttributeName, changeAttributeLabel, etc..) for the required - * resource. - * - * @param oldAttributeDefinitions the old list of attribute definitions. - * @param newAttributeDefinitionsDrafts the new list of attribute definitions drafts. - * @return a list of attribute definitions update actions if the list of attribute definitions is not identical. - * Otherwise, if the attribute definitions are identical, an empty list is returned. - * @throws BuildUpdateActionException in case there are attribute definitions drafts with duplicate names or enums - * duplicate keys. - */ - @Nonnull - private static List> buildUpdateActions( - @Nonnull final List oldAttributeDefinitions, - @Nonnull final List newAttributeDefinitionsDrafts) - throws BuildUpdateActionException { - - try { - final List> updateActions = - buildRemoveAttributeDefinitionOrAttributeDefinitionUpdateActions( - oldAttributeDefinitions, - newAttributeDefinitionsDrafts - ); - - updateActions.addAll( - buildAddAttributeDefinitionUpdateActions( - oldAttributeDefinitions, - newAttributeDefinitionsDrafts - ) - ); - - buildChangeAttributeDefinitionOrderUpdateAction( - oldAttributeDefinitions, - newAttributeDefinitionsDrafts - ) - .ifPresent(updateActions::add); - - return updateActions; - - } catch (final DuplicateNameException | DuplicateKeyException exception) { - throw new BuildUpdateActionException(exception); - } + } + + /** + * Compares a list of {@link AttributeDefinition}s with a list of {@link + * AttributeDefinitionDraft}s. The method serves as an implementation for attribute definitions + * syncing. The method takes in functions for building the required update actions (AddAttribute, + * RemoveAttribute, ChangeAttributeOrder and 1-1 update actions on attribute definitions (e.g. + * changeAttributeName, changeAttributeLabel, etc..) for the required resource. + * + * @param oldAttributeDefinitions the old list of attribute definitions. + * @param newAttributeDefinitionsDrafts the new list of attribute definitions drafts. + * @return a list of attribute definitions update actions if the list of attribute definitions is + * not identical. Otherwise, if the attribute definitions are identical, an empty list is + * returned. + * @throws BuildUpdateActionException in case there are attribute definitions drafts with + * duplicate names or enums duplicate keys. + */ + @Nonnull + private static List> buildUpdateActions( + @Nonnull final List oldAttributeDefinitions, + @Nonnull final List newAttributeDefinitionsDrafts) + throws BuildUpdateActionException { + + try { + final List> updateActions = + buildRemoveAttributeDefinitionOrAttributeDefinitionUpdateActions( + oldAttributeDefinitions, newAttributeDefinitionsDrafts); + + updateActions.addAll( + buildAddAttributeDefinitionUpdateActions( + oldAttributeDefinitions, newAttributeDefinitionsDrafts)); + + buildChangeAttributeDefinitionOrderUpdateAction( + oldAttributeDefinitions, newAttributeDefinitionsDrafts) + .ifPresent(updateActions::add); + + return updateActions; + + } catch (final DuplicateNameException | DuplicateKeyException exception) { + throw new BuildUpdateActionException(exception); } - - /** - * Checks if there are any attribute definitions which are not existing in the - * {@code newAttributeDefinitionsDrafts}. If there are, then "remove" attribute definition update actions are - * built. - * Otherwise, if the attribute definition still exists in the new draft, then compare the attribute definition - * fields (name, label, etc..), and add the computed actions to the list of update actions. - * - *

Note: If the attribute type field changes, the old attribute definition is removed and the new attribute - * definition is added with the new attribute type. - * - * @param oldAttributeDefinitions the list of old {@link AttributeDefinition}s. - * @param newAttributeDefinitionsDrafts the list of new {@link AttributeDefinitionDraft}s. - * @return a list of attribute definition update actions if there are attribute definitions that are not existing - * in the new draft. If the attribute definition still exists in the new draft, then compare the attribute - * definition fields (name, label, etc..), and add the computed actions to the list of update actions. - * Otherwise, if the attribute definitions are identical, an empty optional is returned. - * @throws DuplicateNameException in case there are attribute definitions drafts with duplicate names. - * @throws DuplicateKeyException in case there are enum values with duplicate keys. - */ - @Nonnull - private static List> buildRemoveAttributeDefinitionOrAttributeDefinitionUpdateActions( - @Nonnull final List oldAttributeDefinitions, - @Nonnull final List newAttributeDefinitionsDrafts) { - - final Map newAttributesDefinitionsDraftsNameMap = - newAttributeDefinitionsDrafts - .stream().collect( - toMap(AttributeDefinitionDraft::getName, attributeDefinitionDraft -> attributeDefinitionDraft, + } + + /** + * Checks if there are any attribute definitions which are not existing in the {@code + * newAttributeDefinitionsDrafts}. If there are, then "remove" attribute definition update actions + * are built. Otherwise, if the attribute definition still exists in the new draft, then compare + * the attribute definition fields (name, label, etc..), and add the computed actions to the list + * of update actions. + * + *

Note: If the attribute type field changes, the old attribute definition is removed and the + * new attribute definition is added with the new attribute type. + * + * @param oldAttributeDefinitions the list of old {@link AttributeDefinition}s. + * @param newAttributeDefinitionsDrafts the list of new {@link AttributeDefinitionDraft}s. + * @return a list of attribute definition update actions if there are attribute definitions that + * are not existing in the new draft. If the attribute definition still exists in the new + * draft, then compare the attribute definition fields (name, label, etc..), and add the + * computed actions to the list of update actions. Otherwise, if the attribute definitions are + * identical, an empty optional is returned. + * @throws DuplicateNameException in case there are attribute definitions drafts with duplicate + * names. + * @throws DuplicateKeyException in case there are enum values with duplicate keys. + */ + @Nonnull + private static List> + buildRemoveAttributeDefinitionOrAttributeDefinitionUpdateActions( + @Nonnull final List oldAttributeDefinitions, + @Nonnull final List newAttributeDefinitionsDrafts) { + + final Map newAttributesDefinitionsDraftsNameMap = + newAttributeDefinitionsDrafts.stream() + .collect( + toMap( + AttributeDefinitionDraft::getName, + attributeDefinitionDraft -> attributeDefinitionDraft, (attributeDefinitionDraftA, attributeDefinitionDraftB) -> { - throw new DuplicateNameException(format("Attribute definitions drafts have duplicated names. " - + "Duplicated attribute definition name: '%s'. " - + "Attribute definitions names are expected to be unique inside their product type.", - attributeDefinitionDraftA.getName())); - } - )); - - return oldAttributeDefinitions - .stream() - .map(oldAttributeDefinition -> { - final String oldAttributeDefinitionName = oldAttributeDefinition.getName(); - final AttributeDefinitionDraft matchingNewAttributeDefinitionDraft = - newAttributesDefinitionsDraftsNameMap.get(oldAttributeDefinitionName); - return ofNullable(matchingNewAttributeDefinitionDraft) - .map(attributeDefinitionDraft -> { - // attribute type is required so if null we let commercetools to throw exception + throw new DuplicateNameException( + format( + "Attribute definitions drafts have duplicated names. " + + "Duplicated attribute definition name: '%s'. " + + "Attribute definitions names are expected to be unique inside their product type.", + attributeDefinitionDraftA.getName())); + })); + + return oldAttributeDefinitions.stream() + .map( + oldAttributeDefinition -> { + final String oldAttributeDefinitionName = oldAttributeDefinition.getName(); + final AttributeDefinitionDraft matchingNewAttributeDefinitionDraft = + newAttributesDefinitionsDraftsNameMap.get(oldAttributeDefinitionName); + return ofNullable(matchingNewAttributeDefinitionDraft) + .map( + attributeDefinitionDraft -> { + // attribute type is required so if null we let commercetools to throw + // exception if (attributeDefinitionDraft.getAttributeType() != null) { - if (haveSameAttributeType(oldAttributeDefinition.getAttributeType(), - matchingNewAttributeDefinitionDraft.getAttributeType())) { - return buildActions(oldAttributeDefinition, attributeDefinitionDraft); - } else { - // since there is no way to change an attribute type on CTP, - // we remove the attribute definition and add a new one with a new attribute type - return Arrays.asList( - RemoveAttributeDefinition.of(oldAttributeDefinitionName), - AddAttributeDefinition.of(attributeDefinitionDraft) - ); - } + if (haveSameAttributeType( + oldAttributeDefinition.getAttributeType(), + matchingNewAttributeDefinitionDraft.getAttributeType())) { + return buildActions(oldAttributeDefinition, attributeDefinitionDraft); + } else { + // since there is no way to change an attribute type on CTP, + // we remove the attribute definition and add a new one with a new + // attribute type + return Arrays.asList( + RemoveAttributeDefinition.of(oldAttributeDefinitionName), + AddAttributeDefinition.of(attributeDefinitionDraft)); + } } else { - return new ArrayList>(); + return new ArrayList>(); } - - }) - .orElseGet(() -> singletonList(RemoveAttributeDefinition.of(oldAttributeDefinitionName))); + }) + .orElseGet( + () -> + singletonList(RemoveAttributeDefinition.of(oldAttributeDefinitionName))); }) - .flatMap(Collection::stream) - .collect(Collectors.toList()); + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + /** + * Compares the attribute types of the {@code attributeDefinitionA} and the {@code + * attributeDefinitionB} and returns true if both attribute definitions have the same attribute + * type, false otherwise. + * + *

Note: + * + *

    + *
  • It returns true if both attribute types are of {@link EnumAttributeType} type, regardless + * of the enum values. + *
  • It returns true if both attribute types are of {@link LocalizedEnumAttributeType} type, + * regardless of the localized enum values. + *
+ * + * @param attributeTypeA the first attribute type to compare. + * @param attributeTypeB the second attribute type to compare. + * @return true if both attribute definitions have the same attribute type, false otherwise. + */ + private static boolean haveSameAttributeType( + @Nonnull final AttributeType attributeTypeA, @Nonnull final AttributeType attributeTypeB) { + + if (attributeTypeA instanceof SetAttributeType && attributeTypeB instanceof SetAttributeType) { + return haveSameAttributeType( + ((SetAttributeType) attributeTypeA).getElementType(), + ((SetAttributeType) attributeTypeB).getElementType()); } - /** - * Compares the attribute types of the {@code attributeDefinitionA} and the {@code attributeDefinitionB} and - * returns true if both attribute definitions have the same attribute type, false otherwise. - * - *

Note: - *

    - *
  • - * It returns true if both attribute types are of {@link EnumAttributeType} type, - * regardless of the enum values. - *
  • - *
  • - * It returns true if both attribute types are of {@link LocalizedEnumAttributeType} type, - * regardless of the localized enum values. - *
  • - *
- * - * @param attributeTypeA the first attribute type to compare. - * @param attributeTypeB the second attribute type to compare. - * @return true if both attribute definitions have the same attribute type, false otherwise. - */ - private static boolean haveSameAttributeType( - @Nonnull final AttributeType attributeTypeA, - @Nonnull final AttributeType attributeTypeB) { - - if (attributeTypeA instanceof SetAttributeType && attributeTypeB instanceof SetAttributeType) { - return haveSameAttributeType( - ((SetAttributeType) attributeTypeA).getElementType(), - ((SetAttributeType) attributeTypeB).getElementType()); - } - - if (attributeTypeA instanceof EnumAttributeType - && attributeTypeB instanceof EnumAttributeType) { - return true; - } - - if (attributeTypeA instanceof LocalizedEnumAttributeType - && attributeTypeB instanceof LocalizedEnumAttributeType) { - return true; - } - - return Objects.equals(attributeTypeA, attributeTypeB); + if (attributeTypeA instanceof EnumAttributeType + && attributeTypeB instanceof EnumAttributeType) { + return true; } + if (attributeTypeA instanceof LocalizedEnumAttributeType + && attributeTypeB instanceof LocalizedEnumAttributeType) { + return true; + } - - /** - * Compares the order of a list of old {@link AttributeDefinition}s and a list of new - * {@link AttributeDefinitionDraft}s. If there is a change in order, then a change attribute definition order - * (with the new order) is built. If there are no changes in order an empty optional is returned. - * - * @param oldAttributeDefinitions the list of old {@link AttributeDefinition}s - * @param newAttributeDefinitionDrafts the list of new {@link AttributeDefinitionDraft}s - * @return a list of attribute definition update actions if the order of attribute definitions is not - * identical. Otherwise, if the attribute definitions order is identical, an empty optional is returned. - */ - @Nonnull - private static Optional> buildChangeAttributeDefinitionOrderUpdateAction( - @Nonnull final List oldAttributeDefinitions, - @Nonnull final List newAttributeDefinitionDrafts) { - - - final List newNames = newAttributeDefinitionDrafts - .stream() + return Objects.equals(attributeTypeA, attributeTypeB); + } + + /** + * Compares the order of a list of old {@link AttributeDefinition}s and a list of new {@link + * AttributeDefinitionDraft}s. If there is a change in order, then a change attribute definition + * order (with the new order) is built. If there are no changes in order an empty optional is + * returned. + * + * @param oldAttributeDefinitions the list of old {@link AttributeDefinition}s + * @param newAttributeDefinitionDrafts the list of new {@link AttributeDefinitionDraft}s + * @return a list of attribute definition update actions if the order of attribute definitions is + * not identical. Otherwise, if the attribute definitions order is identical, an empty + * optional is returned. + */ + @Nonnull + private static Optional> + buildChangeAttributeDefinitionOrderUpdateAction( + @Nonnull final List oldAttributeDefinitions, + @Nonnull final List newAttributeDefinitionDrafts) { + + final List newNames = + newAttributeDefinitionDrafts.stream() .map(AttributeDefinitionDraft::getName) .collect(toList()); - final List existingNames = oldAttributeDefinitions - .stream() + final List existingNames = + oldAttributeDefinitions.stream() .map(AttributeDefinition::getName) .filter(newNames::contains) .collect(toList()); - final List notExistingNames = newNames - .stream() - .filter(newName -> !existingNames.contains(newName)) - .collect(toList()); + final List notExistingNames = + newNames.stream().filter(newName -> !existingNames.contains(newName)).collect(toList()); - final List newAttributeDefinitionsOrder = newAttributeDefinitionDrafts - .stream() + final List newAttributeDefinitionsOrder = + newAttributeDefinitionDrafts.stream() .map(AttributeDefinitionDraft::getName) .collect(toList()); - final List allNames = Stream.concat(existingNames.stream(), notExistingNames.stream()) - .collect(toList()); - - return buildUpdateAction( - allNames, - newNames, - () -> ChangeAttributeOrderByName.of(newAttributeDefinitionsOrder) - ); - - - } - - /** - * Checks if there are any new attribute definition drafts which are not existing in the - * {@code oldAttributeDefinitions}. If there are, then "add" attribute definition update actions are built. - * Otherwise, if there are no new attribute definitions, then an empty list - * is returned. - * - * @param oldAttributeDefinitions the list of old {@link AttributeDefinition}s. - * @param newAttributeDefinitionDrafts the list of new {@link AttributeDefinitionDraft}s. - * - * @return a list of attribute definition update actions if there are new attribute definition that should be added. - * Otherwise, if the attribute definitions are identical, an empty optional is returned. - */ - @Nonnull - private static List> buildAddAttributeDefinitionUpdateActions( - @Nonnull final List oldAttributeDefinitions, - @Nonnull final List newAttributeDefinitionDrafts) { - - final Map oldAttributeDefinitionNameMap = - oldAttributeDefinitions - .stream() - .collect(toMap(AttributeDefinition::getName, attributeDefinition -> attributeDefinition)); - - return newAttributeDefinitionDrafts - .stream() - .filter(attributeDefinitionDraft -> !oldAttributeDefinitionNameMap - .containsKey(attributeDefinitionDraft.getName()) - ) - .map(AddAttributeDefinition::of) - .collect(Collectors.toList()); - } - - private AttributeDefinitionsUpdateActionUtils() { - } + final List allNames = + Stream.concat(existingNames.stream(), notExistingNames.stream()).collect(toList()); + + return buildUpdateAction( + allNames, newNames, () -> ChangeAttributeOrderByName.of(newAttributeDefinitionsOrder)); + } + + /** + * Checks if there are any new attribute definition drafts which are not existing in the {@code + * oldAttributeDefinitions}. If there are, then "add" attribute definition update actions are + * built. Otherwise, if there are no new attribute definitions, then an empty list is returned. + * + * @param oldAttributeDefinitions the list of old {@link AttributeDefinition}s. + * @param newAttributeDefinitionDrafts the list of new {@link AttributeDefinitionDraft}s. + * @return a list of attribute definition update actions if there are new attribute definition + * that should be added. Otherwise, if the attribute definitions are identical, an empty + * optional is returned. + */ + @Nonnull + private static List> buildAddAttributeDefinitionUpdateActions( + @Nonnull final List oldAttributeDefinitions, + @Nonnull final List newAttributeDefinitionDrafts) { + + final Map oldAttributeDefinitionNameMap = + oldAttributeDefinitions.stream() + .collect( + toMap(AttributeDefinition::getName, attributeDefinition -> attributeDefinition)); + + return newAttributeDefinitionDrafts.stream() + .filter( + attributeDefinitionDraft -> + !oldAttributeDefinitionNameMap.containsKey(attributeDefinitionDraft.getName())) + .map(AddAttributeDefinition::of) + .collect(Collectors.toList()); + } + + private AttributeDefinitionsUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/producttypes/utils/LocalizedEnumValueUpdateActionUtils.java b/src/main/java/com/commercetools/sync/producttypes/utils/LocalizedEnumValueUpdateActionUtils.java index f5c80e1e1e..105ecd25f8 100644 --- a/src/main/java/com/commercetools/sync/producttypes/utils/LocalizedEnumValueUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/producttypes/utils/LocalizedEnumValueUpdateActionUtils.java @@ -1,5 +1,9 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.EnumValuesUpdateActionUtils.buildActions; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; + import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedEnumValue; @@ -8,98 +12,97 @@ import io.sphere.sdk.producttypes.commands.updateactions.ChangeLocalizedEnumValueLabel; import io.sphere.sdk.producttypes.commands.updateactions.ChangeLocalizedEnumValueOrder; import io.sphere.sdk.producttypes.commands.updateactions.RemoveEnumValues; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static com.commercetools.sync.commons.utils.EnumValuesUpdateActionUtils.buildActions; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class LocalizedEnumValueUpdateActionUtils { - /** - * Compares a list of old {@link LocalizedEnumValue}s with a list of new {@link LocalizedEnumValue}s for a given - * attribute definition and builds required update actions (e.g addLocalizedEnumValue, removeLocalizedEnumValue, - * changeLocalizedEnumValueOrder) and 1-1 update actions on localized enum values - * (e.g. changeLocalizedEnumValueLabel) for the required resource. If both the {@link LocalizedEnumValue}'s are - * identical, then no update action is needed and hence an empty {@link List} is returned. - * - *

If the list of new {@link LocalizedEnumValue}s is {@code null}, then remove actions are built for - * every existing localized enum value in the {@code oldEnumValues} list. - * - * @param attributeDefinitionName the attribute name whose localized enum values are going to be synced. - * @param oldEnumValues the old list of localized enum values. - * @param newEnumValues the new list of localized enum values. - * @return a list of localized enum values update actions if the list of localized enum values is not identical. - * Otherwise, if the localized enum values are identical, an empty list is returned. - * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. - */ - @Nonnull - public static List> buildLocalizedEnumValuesUpdateActions( - @Nonnull final String attributeDefinitionName, - @Nonnull final List oldEnumValues, - @Nullable final List newEnumValues) { - - return buildActions( - attributeDefinitionName, - oldEnumValues, - newEnumValues, - RemoveEnumValues::of, - LocalizedEnumValueUpdateActionUtils::buildLocalizedEnumValueUpdateActions, - AddLocalizedEnumValue::of, - ChangeLocalizedEnumValueOrder::of, - null - ); - } + /** + * Compares a list of old {@link LocalizedEnumValue}s with a list of new {@link + * LocalizedEnumValue}s for a given attribute definition and builds required update actions (e.g + * addLocalizedEnumValue, removeLocalizedEnumValue, changeLocalizedEnumValueOrder) and 1-1 update + * actions on localized enum values (e.g. changeLocalizedEnumValueLabel) for the required + * resource. If both the {@link LocalizedEnumValue}'s are identical, then no update action is + * needed and hence an empty {@link List} is returned. + * + *

If the list of new {@link LocalizedEnumValue}s is {@code null}, then remove actions are + * built for every existing localized enum value in the {@code oldEnumValues} list. + * + * @param attributeDefinitionName the attribute name whose localized enum values are going to be + * synced. + * @param oldEnumValues the old list of localized enum values. + * @param newEnumValues the new list of localized enum values. + * @return a list of localized enum values update actions if the list of localized enum values is + * not identical. Otherwise, if the localized enum values are identical, an empty list is + * returned. + * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. + */ + @Nonnull + public static List> buildLocalizedEnumValuesUpdateActions( + @Nonnull final String attributeDefinitionName, + @Nonnull final List oldEnumValues, + @Nullable final List newEnumValues) { + return buildActions( + attributeDefinitionName, + oldEnumValues, + newEnumValues, + RemoveEnumValues::of, + LocalizedEnumValueUpdateActionUtils::buildLocalizedEnumValueUpdateActions, + AddLocalizedEnumValue::of, + ChangeLocalizedEnumValueOrder::of, + null); + } - /** - * Compares all the fields of an old {@link LocalizedEnumValue} and a new {@link LocalizedEnumValue} and returns a - * list of {@link UpdateAction}<{@link ProductType}> as a result. If both {@link LocalizedEnumValue} have - * identical fields then no update action is needed and hence an empty {@link List} is returned. - * - * @param attributeDefinitionName the attribute definition name whose localized enum values belong to. - * @param oldEnumValue the localized enum value which should be updated. - * @param newEnumValue the localized enum value where we get the new fields. - * @return A list with the update actions or an empty list if the localized enum values are - * identical. - */ - @Nonnull - public static List> buildLocalizedEnumValueUpdateActions( - @Nonnull final String attributeDefinitionName, - @Nonnull final LocalizedEnumValue oldEnumValue, - @Nonnull final LocalizedEnumValue newEnumValue) { + /** + * Compares all the fields of an old {@link LocalizedEnumValue} and a new {@link + * LocalizedEnumValue} and returns a list of {@link UpdateAction}<{@link ProductType}> as a + * result. If both {@link LocalizedEnumValue} have identical fields then no update action is + * needed and hence an empty {@link List} is returned. + * + * @param attributeDefinitionName the attribute definition name whose localized enum values belong + * to. + * @param oldEnumValue the localized enum value which should be updated. + * @param newEnumValue the localized enum value where we get the new fields. + * @return A list with the update actions or an empty list if the localized enum values are + * identical. + */ + @Nonnull + public static List> buildLocalizedEnumValueUpdateActions( + @Nonnull final String attributeDefinitionName, + @Nonnull final LocalizedEnumValue oldEnumValue, + @Nonnull final LocalizedEnumValue newEnumValue) { - return filterEmptyOptionals( - buildChangeLabelAction(attributeDefinitionName, oldEnumValue, newEnumValue) - ); - } + return filterEmptyOptionals( + buildChangeLabelAction(attributeDefinitionName, oldEnumValue, newEnumValue)); + } - /** - * Compares the {@code label} values of an old {@link LocalizedEnumValue} and a new {@link LocalizedEnumValue} - * and returns an {@link Optional} of update action, which would contain the {@code "changeLabel"} - * {@link UpdateAction}. If both, old and new {@link LocalizedEnumValue} have the same {@code label} values, - * then no update action is needed and empty optional will be returned. - * - * @param attributeDefinitionName the attribute definition name whose localized enum values belong to. - * @param oldEnumValue the old localized enum value. - * @param newEnumValue the new localized enum value which contains the new description. - * @return optional containing update action or empty optional if labels - * are identical. - */ - @Nonnull - public static Optional> buildChangeLabelAction( - @Nonnull final String attributeDefinitionName, - @Nonnull final LocalizedEnumValue oldEnumValue, - @Nonnull final LocalizedEnumValue newEnumValue) { + /** + * Compares the {@code label} values of an old {@link LocalizedEnumValue} and a new {@link + * LocalizedEnumValue} and returns an {@link Optional} of update action, which would contain the + * {@code "changeLabel"} {@link UpdateAction}. If both, old and new {@link LocalizedEnumValue} + * have the same {@code label} values, then no update action is needed and empty optional will be + * returned. + * + * @param attributeDefinitionName the attribute definition name whose localized enum values belong + * to. + * @param oldEnumValue the old localized enum value. + * @param newEnumValue the new localized enum value which contains the new description. + * @return optional containing update action or empty optional if labels are identical. + */ + @Nonnull + public static Optional> buildChangeLabelAction( + @Nonnull final String attributeDefinitionName, + @Nonnull final LocalizedEnumValue oldEnumValue, + @Nonnull final LocalizedEnumValue newEnumValue) { - return buildUpdateAction(oldEnumValue.getLabel(), newEnumValue.getLabel(), - () -> ChangeLocalizedEnumValueLabel.of(attributeDefinitionName, newEnumValue)); - } + return buildUpdateAction( + oldEnumValue.getLabel(), + newEnumValue.getLabel(), + () -> ChangeLocalizedEnumValueLabel.of(attributeDefinitionName, newEnumValue)); + } - private LocalizedEnumValueUpdateActionUtils() { - } + private LocalizedEnumValueUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/producttypes/utils/PlainEnumValueUpdateActionUtils.java b/src/main/java/com/commercetools/sync/producttypes/utils/PlainEnumValueUpdateActionUtils.java index 6fd0ce0838..de7311d8a7 100644 --- a/src/main/java/com/commercetools/sync/producttypes/utils/PlainEnumValueUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/producttypes/utils/PlainEnumValueUpdateActionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; + import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import com.commercetools.sync.commons.utils.EnumValuesUpdateActionUtils; import io.sphere.sdk.commands.UpdateAction; @@ -9,95 +12,91 @@ import io.sphere.sdk.producttypes.commands.updateactions.ChangeEnumValueOrder; import io.sphere.sdk.producttypes.commands.updateactions.ChangePlainEnumValueLabel; import io.sphere.sdk.producttypes.commands.updateactions.RemoveEnumValues; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class PlainEnumValueUpdateActionUtils { - /** - * Compares a list of old {@link EnumValue}s with a list of new {@link EnumValue}s for a given - * attribute definition and builds required update actions (e.g addEnumValue, removeEnumValue, - * changeEnumValueOrder) and 1-1 update actions on enum values (e.g. changeEnumValueLabel) for the required - * resource. If both the {@link EnumValue}'s are identical, then no update action is needed and hence - * an empty {@link List} is returned. - * - *

If the list of new {@link EnumValue}s is {@code null}, then remove actions are built for - * every existing plain enum value in the {@code oldEnumValues} list. - * - * @param attributeDefinitionName the attribute name whose plain enum values are going to be synced. - * @param oldEnumValues the old list of plain enum values. - * @param newEnumValues the new list of plain enum values. - * @return a list of plain enum values update actions if the list of plain enum values is not identical. - * Otherwise, if the plain enum values are identical, an empty list is returned. - * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. - */ - @Nonnull - public static List> buildEnumValuesUpdateActions( - @Nonnull final String attributeDefinitionName, - @Nonnull final List oldEnumValues, - @Nullable final List newEnumValues) { - - return EnumValuesUpdateActionUtils.buildActions(attributeDefinitionName, - oldEnumValues, - newEnumValues, - RemoveEnumValues::of, - PlainEnumValueUpdateActionUtils::buildEnumValueUpdateActions, - AddEnumValue::of, - ChangeEnumValueOrder::of, - null); - } + /** + * Compares a list of old {@link EnumValue}s with a list of new {@link EnumValue}s for a given + * attribute definition and builds required update actions (e.g addEnumValue, removeEnumValue, + * changeEnumValueOrder) and 1-1 update actions on enum values (e.g. changeEnumValueLabel) for the + * required resource. If both the {@link EnumValue}'s are identical, then no update action is + * needed and hence an empty {@link List} is returned. + * + *

If the list of new {@link EnumValue}s is {@code null}, then remove actions are built for + * every existing plain enum value in the {@code oldEnumValues} list. + * + * @param attributeDefinitionName the attribute name whose plain enum values are going to be + * synced. + * @param oldEnumValues the old list of plain enum values. + * @param newEnumValues the new list of plain enum values. + * @return a list of plain enum values update actions if the list of plain enum values is not + * identical. Otherwise, if the plain enum values are identical, an empty list is returned. + * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. + */ + @Nonnull + public static List> buildEnumValuesUpdateActions( + @Nonnull final String attributeDefinitionName, + @Nonnull final List oldEnumValues, + @Nullable final List newEnumValues) { - /** - * Compares all the fields of an old {@link EnumValue} and a new {@link EnumValue} and returns a list of - * {@link UpdateAction}<{@link ProductType}> as a result. If both {@link EnumValue} have identical fields, - * then no update action is needed and hence an empty {@link List} is returned. - * - * @param attributeDefinitionName the attribute definition name whose plain enum values belong to. - * @param oldEnumValue the enum value which should be updated. - * @param newEnumValue the enum value where we get the new fields. - * @return A list with the update actions or an empty list if the enum values are - * identical. - */ - @Nonnull - public static List> buildEnumValueUpdateActions( - @Nonnull final String attributeDefinitionName, - @Nonnull final EnumValue oldEnumValue, - @Nonnull final EnumValue newEnumValue) { + return EnumValuesUpdateActionUtils.buildActions( + attributeDefinitionName, + oldEnumValues, + newEnumValues, + RemoveEnumValues::of, + PlainEnumValueUpdateActionUtils::buildEnumValueUpdateActions, + AddEnumValue::of, + ChangeEnumValueOrder::of, + null); + } - return filterEmptyOptionals( - buildChangeLabelAction(attributeDefinitionName, oldEnumValue, newEnumValue) - ); - } + /** + * Compares all the fields of an old {@link EnumValue} and a new {@link EnumValue} and returns a + * list of {@link UpdateAction}<{@link ProductType}> as a result. If both {@link EnumValue} + * have identical fields, then no update action is needed and hence an empty {@link List} is + * returned. + * + * @param attributeDefinitionName the attribute definition name whose plain enum values belong to. + * @param oldEnumValue the enum value which should be updated. + * @param newEnumValue the enum value where we get the new fields. + * @return A list with the update actions or an empty list if the enum values are identical. + */ + @Nonnull + public static List> buildEnumValueUpdateActions( + @Nonnull final String attributeDefinitionName, + @Nonnull final EnumValue oldEnumValue, + @Nonnull final EnumValue newEnumValue) { - /** - * Compares the {@code label} values of an old {@link EnumValue} and a new {@link EnumValue} - * and returns an {@link Optional} of update action, which would contain the {@code "changeLabel"} - * {@link UpdateAction}. If both, old and new {@link EnumValue} have the same {@code label} values, - * then no update action is needed and empty optional will be returned. - * - * @param attributeDefinitionName the attribute definition name whose plain enum values belong to. - * @param oldEnumValue the old plain enum value. - * @param newEnumValue the new plain enum value which contains the new description. - * @return optional containing update action or empty optional if labels - * are identical. - */ - @Nonnull - public static Optional> buildChangeLabelAction( - @Nonnull final String attributeDefinitionName, - @Nonnull final EnumValue oldEnumValue, - @Nonnull final EnumValue newEnumValue) { + return filterEmptyOptionals( + buildChangeLabelAction(attributeDefinitionName, oldEnumValue, newEnumValue)); + } - return buildUpdateAction(oldEnumValue.getLabel(), newEnumValue.getLabel(), - () -> ChangePlainEnumValueLabel.of(attributeDefinitionName, newEnumValue)); - } + /** + * Compares the {@code label} values of an old {@link EnumValue} and a new {@link EnumValue} and + * returns an {@link Optional} of update action, which would contain the {@code "changeLabel"} + * {@link UpdateAction}. If both, old and new {@link EnumValue} have the same {@code label} + * values, then no update action is needed and empty optional will be returned. + * + * @param attributeDefinitionName the attribute definition name whose plain enum values belong to. + * @param oldEnumValue the old plain enum value. + * @param newEnumValue the new plain enum value which contains the new description. + * @return optional containing update action or empty optional if labels are identical. + */ + @Nonnull + public static Optional> buildChangeLabelAction( + @Nonnull final String attributeDefinitionName, + @Nonnull final EnumValue oldEnumValue, + @Nonnull final EnumValue newEnumValue) { + return buildUpdateAction( + oldEnumValue.getLabel(), + newEnumValue.getLabel(), + () -> ChangePlainEnumValueLabel.of(attributeDefinitionName, newEnumValue)); + } - private PlainEnumValueUpdateActionUtils() { - } + private PlainEnumValueUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtils.java index 90352b8b3e..d5ae57c166 100644 --- a/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.producttypes.utils; +import static java.lang.String.format; +import static java.util.stream.Collectors.toList; + import com.commercetools.sync.commons.exceptions.InvalidAttributeDefinitionException; import com.commercetools.sync.commons.exceptions.InvalidProductTypeException; import com.commercetools.sync.commons.exceptions.InvalidReferenceException; @@ -15,243 +18,251 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.ProductTypeDraftBuilder; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; - -import javax.annotation.Nonnull; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static java.lang.String.format; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class ProductTypeReferenceResolutionUtils { - /** - * Returns an {@link List}<{@link ProductTypeDraft}> consisting of the results of applying the - * mapping from {@link ProductType} to {@link ProductTypeDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
productType references (in case it has NestedType or set of NestedType){@link Set}<{@link Reference}<{@link ProductType}>>{@link Set}<{@link Reference}<{@link ProductType}>> (with key replaced with id field)
- * - *

Note:If some references are not expanded for an attributeDefinition of a productType, the method will - * throw a {@link ReferenceReplacementException} containing the root causes of the exceptions that occurred in any - * of the supplied {@code productTypes}. - * - * @param productTypes the product types with expanded references. - * @return a {@link List} of {@link ProductTypeDraft} built from the - * supplied {@link List} of {@link ProductType}. - */ - @Nonnull - public static List mapToProductTypeDrafts( - @Nonnull final List productTypes) { - - final Set errors = new HashSet<>(); - - final List referenceReplacedDrafts = productTypes - .stream() + /** + * Returns an {@link List}<{@link ProductTypeDraft}> consisting of the results of applying + * the mapping from {@link ProductType} to {@link ProductTypeDraft} with considering reference + * resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
productType references (in case it has NestedType or set of NestedType){@link Set}<{@link Reference}<{@link ProductType}>>{@link Set}<{@link Reference}<{@link ProductType}>> (with key replaced with id field)
+ * + *

Note:If some references are not expanded for an attributeDefinition of a productType, + * the method will throw a {@link ReferenceReplacementException} containing the root causes of the + * exceptions that occurred in any of the supplied {@code productTypes}. + * + * @param productTypes the product types with expanded references. + * @return a {@link List} of {@link ProductTypeDraft} built from the supplied {@link List} of + * {@link ProductType}. + */ + @Nonnull + public static List mapToProductTypeDrafts( + @Nonnull final List productTypes) { + + final Set errors = new HashSet<>(); + + final List referenceReplacedDrafts = + productTypes.stream() .filter(Objects::nonNull) - .map(productType -> { - final List referenceReplacedAttributeDefinitions; - try { + .map( + productType -> { + final List referenceReplacedAttributeDefinitions; + try { referenceReplacedAttributeDefinitions = replaceAttributeDefinitionsReferenceIdsWithKeys(productType); - } catch (InvalidProductTypeException invalidProductTypeException) { + } catch (InvalidProductTypeException invalidProductTypeException) { errors.add(invalidProductTypeException); return null; - } + } - return ProductTypeDraftBuilder.of(productType) - .attributes(referenceReplacedAttributeDefinitions) - .build(); - }) + return ProductTypeDraftBuilder.of(productType) + .attributes(referenceReplacedAttributeDefinitions) + .build(); + }) .filter(Objects::nonNull) .collect(toList()); - if (!errors.isEmpty()) { - throw new ReferenceReplacementException("Some errors occurred during reference replacement.", errors); - } - - return referenceReplacedDrafts; + if (!errors.isEmpty()) { + throw new ReferenceReplacementException( + "Some errors occurred during reference replacement.", errors); } - @Nonnull - private static List replaceAttributeDefinitionsReferenceIdsWithKeys( - @Nonnull final ProductType productType) throws InvalidProductTypeException { + return referenceReplacedDrafts; + } + + @Nonnull + private static List replaceAttributeDefinitionsReferenceIdsWithKeys( + @Nonnull final ProductType productType) throws InvalidProductTypeException { - final Set errors = new HashSet<>(); + final Set errors = new HashSet<>(); - final List referenceReplacedAttributeDefinitions = productType - .getAttributes() - .stream() - .map(attributeDefinition -> { - final AttributeType attributeType = attributeDefinition.getAttributeType(); - try { - final AttributeType referenceReplacedType = replaceProductTypeReferenceIdWithKey(attributeType); - return AttributeDefinitionDraftBuilder - .of(attributeDefinition) + final List referenceReplacedAttributeDefinitions = + productType.getAttributes().stream() + .map( + attributeDefinition -> { + final AttributeType attributeType = attributeDefinition.getAttributeType(); + try { + final AttributeType referenceReplacedType = + replaceProductTypeReferenceIdWithKey(attributeType); + return AttributeDefinitionDraftBuilder.of(attributeDefinition) .attributeType(referenceReplacedType) .build(); - } catch (InvalidReferenceException exception) { + } catch (InvalidReferenceException exception) { final InvalidAttributeDefinitionException attributeDefinitionException = - new InvalidAttributeDefinitionException(format( - "Failed to replace some references on the attributeDefinition with name '%s'. Cause: %s", - attributeDefinition.getName(), - exception.getMessage()), + new InvalidAttributeDefinitionException( + format( + "Failed to replace some references on the attributeDefinition with name '%s'. Cause: %s", + attributeDefinition.getName(), exception.getMessage()), exception); errors.add(attributeDefinitionException); return null; - } - }) + } + }) .filter(Objects::nonNull) .collect(toList()); - if (!errors.isEmpty()) { - throw new InvalidProductTypeException( - format("Failed to replace some references on the productType with key '%s'.", productType.getKey()), - errors); - } - return referenceReplacedAttributeDefinitions; + if (!errors.isEmpty()) { + throw new InvalidProductTypeException( + format( + "Failed to replace some references on the productType with key '%s'.", + productType.getKey()), + errors); } + return referenceReplacedAttributeDefinitions; + } - @Nonnull - private static AttributeType replaceProductTypeReferenceIdWithKey( - @Nonnull final AttributeType attributeType) throws InvalidReferenceException { - - if (attributeType instanceof NestedAttributeType) { + @Nonnull + private static AttributeType replaceProductTypeReferenceIdWithKey( + @Nonnull final AttributeType attributeType) throws InvalidReferenceException { - final Reference referenceReplacedNestedType = - replaceProductTypeReferenceIdWithKey((NestedAttributeType) attributeType); - return NestedAttributeType.of(referenceReplacedNestedType); + if (attributeType instanceof NestedAttributeType) { - } else if (attributeType instanceof SetAttributeType) { + final Reference referenceReplacedNestedType = + replaceProductTypeReferenceIdWithKey((NestedAttributeType) attributeType); + return NestedAttributeType.of(referenceReplacedNestedType); - final SetAttributeType setAttributeType = (SetAttributeType) attributeType; - final AttributeType elementType = setAttributeType.getElementType(); - final AttributeType referenceReplacedElementType = replaceProductTypeReferenceIdWithKey(elementType); - return SetAttributeType.of(referenceReplacedElementType); - } + } else if (attributeType instanceof SetAttributeType) { - return attributeType; + final SetAttributeType setAttributeType = (SetAttributeType) attributeType; + final AttributeType elementType = setAttributeType.getElementType(); + final AttributeType referenceReplacedElementType = + replaceProductTypeReferenceIdWithKey(elementType); + return SetAttributeType.of(referenceReplacedElementType); } - @Nonnull - private static Reference replaceProductTypeReferenceIdWithKey( - @Nonnull final NestedAttributeType nestedAttributeType) throws InvalidReferenceException { + return attributeType; + } - final Reference productTypeReference = nestedAttributeType.getTypeReference(); + @Nonnull + private static Reference replaceProductTypeReferenceIdWithKey( + @Nonnull final NestedAttributeType nestedAttributeType) throws InvalidReferenceException { - final ProductType productTypeReferenceObj = productTypeReference.getObj(); - if (productTypeReferenceObj != null) { - return ProductType.referenceOfId(productTypeReferenceObj.getKey()); - } else { - throw new InvalidReferenceException("ProductType reference is not expanded."); - } - } + final Reference productTypeReference = nestedAttributeType.getTypeReference(); - /** - * Builds a {@link ProductTypeQuery} for fetching products from a source CTP project with an expansion to the - * {@link ProductType} reference of any - * NestedType Attribute - * - *

Note: Please only use this util if you have nestedType attributes in the productTypes that you will fetch from - * a source commercetools project. Otherwise, it is more efficient to build the query without expansions, if they - * are not needed, to avoid unnecessarily bigger payloads fetched from the source project. - * - * @return the query for fetching products from the source CTP project with an expansion to the {@link ProductType} - * reference of any NestedType Attribute. - */ - @Nonnull - public static ProductTypeQuery buildProductTypeQuery() { - return buildProductTypeQuery(0); + final ProductType productTypeReferenceObj = productTypeReference.getObj(); + if (productTypeReferenceObj != null) { + return ProductType.referenceOfId(productTypeReferenceObj.getKey()); + } else { + throw new InvalidReferenceException("ProductType reference is not expanded."); } - - /** - * Builds a {@link ProductTypeQuery} for fetching products from a source CTP project with an expansion to the - * {@link ProductType} reference of any - * NestedType Attribute and - * also any SetType Attribute - * that has an {@code elementType} of {@code NestedType} depending on the given {@code maximumSetDepth} will provide - * an expansion for the nestedType of this set depth and all nested types of smaller depths. For example: - *

- * if {@code maximumSetDepth} is 0, it means there is no atttribute that is a type of set of nestedType. - * Therefore, the built query will have the following expansion paths:
- * attributes[*].type.typeReference - *

- * if {@code maximumSetDepth} is 1, it means the maximum nesting of sets is set of a NestedType. - * Therefore, the built query will have the following expansion paths:
- * attributes[*].type.typeReference, attributes[*].type.elementType.typeReference - *

- * if {@code maximumSetDepth} is 2, it means the maximum nesting of sets is set of set of a NestedType. - * Therefore, the built query will have the following expansion paths:
- * attributes[*].type.typeReference, attributes[*].type.elementType.typeReference, - * attributes[*].type.elementType.elementType.typeReference - *

- * and so on.. - * - *

Note: Please only use this util if you have nestedType attributes in the productTypes that you will fetch from - * a source commercetools project. Otherwise, it is more efficient to build the query without expansions, if they - * are not needed, to avoid unnecessarily bigger payloads fetched from the source project. - * - * @param maximumSetDepth defines the maximum nesting of SetType attributes. If there are no setType of - * NestedType attributes in source productTypes then this number should be 0. If there is an - * attribute of type setType of a NestedType then this number should be 1. If the maximum - * nesting is a setType of a setType attribute, then this number should be 2, and so on. - * @return the query for fetching productTypes from the source CTP project with all the aforementioned references - * expanded. - */ - @Nonnull - public static ProductTypeQuery buildProductTypeQuery(final int maximumSetDepth) { - final String nestedTypeReferenceExpansionPath = "attributes[*].type.typeReference"; - final ProductTypeQuery productTypeQuery = ProductTypeQuery - .of() + } + + /** + * Builds a {@link ProductTypeQuery} for fetching products from a source CTP project with an + * expansion to the {@link ProductType} reference of any NestedType + * Attribute + * + *

Note: Please only use this util if you have nestedType attributes in the productTypes that + * you will fetch from a source commercetools project. Otherwise, it is more efficient to build + * the query without expansions, if they are not needed, to avoid unnecessarily bigger payloads + * fetched from the source project. + * + * @return the query for fetching products from the source CTP project with an expansion to the + * {@link ProductType} reference of any NestedType Attribute. + */ + @Nonnull + public static ProductTypeQuery buildProductTypeQuery() { + return buildProductTypeQuery(0); + } + + /** + * Builds a {@link ProductTypeQuery} for fetching products from a source CTP project with an + * expansion to the {@link ProductType} reference of any NestedType + * Attribute and also any SetType + * Attribute that has an {@code elementType} of {@code NestedType} depending on the given + * {@code maximumSetDepth} will provide an expansion for the nestedType of this set depth and all + * nested types of smaller depths. For example:
+ *
+ * if {@code maximumSetDepth} is 0, it means there is no atttribute that is a type of set of + * nestedType. Therefore, the built query will have the following expansion paths:
+ * attributes[*].type.typeReference
+ *
+ * if {@code maximumSetDepth} is 1, it means the maximum nesting of sets is set of a NestedType. + * Therefore, the built query will have the following expansion paths:
+ * attributes[*].type.typeReference, attributes[*].type.elementType.typeReference
+ *
+ * if {@code maximumSetDepth} is 2, it means the maximum nesting of sets is set of set of a + * NestedType. Therefore, the built query will have the following expansion paths:
+ * attributes[*].type.typeReference, attributes[*].type.elementType.typeReference, + * attributes[*].type.elementType.elementType.typeReference
+ *
+ * and so on.. + * + *

Note: Please only use this util if you have nestedType attributes in the productTypes that + * you will fetch from a source commercetools project. Otherwise, it is more efficient to build + * the query without expansions, if they are not needed, to avoid unnecessarily bigger payloads + * fetched from the source project. + * + * @param maximumSetDepth defines the maximum nesting of SetType attributes. If there are no + * setType of NestedType attributes in source productTypes then this number should be 0. If + * there is an attribute of type setType of a NestedType then this number should be 1. If the + * maximum nesting is a setType of a setType attribute, then this number should be 2, and so + * on. + * @return the query for fetching productTypes from the source CTP project with all the + * aforementioned references expanded. + */ + @Nonnull + public static ProductTypeQuery buildProductTypeQuery(final int maximumSetDepth) { + final String nestedTypeReferenceExpansionPath = "attributes[*].type.typeReference"; + final ProductTypeQuery productTypeQuery = + ProductTypeQuery.of() .plusExpansionPaths(ExpansionPath.of(nestedTypeReferenceExpansionPath)); + final List> setsExpansionPath = + buildSetOfNestedTypeReferenceExpansionPath(maximumSetDepth); - final List> setsExpansionPath = - buildSetOfNestedTypeReferenceExpansionPath(maximumSetDepth); + return setsExpansionPath.isEmpty() + ? productTypeQuery + : productTypeQuery.plusExpansionPaths(setsExpansionPath); + } - return setsExpansionPath.isEmpty() - ? productTypeQuery : productTypeQuery.plusExpansionPaths(setsExpansionPath); - } + @Nonnull + private static List> buildSetOfNestedTypeReferenceExpansionPath( + final int maximumSetDepth) { - @Nonnull - private static List> - buildSetOfNestedTypeReferenceExpansionPath(final int maximumSetDepth) { + return IntStream.rangeClosed(1, maximumSetDepth) + .mapToObj(ProductTypeReferenceResolutionUtils::getExpansionPathForSetDepth) + .collect(toList()); + } - return IntStream.rangeClosed(1, maximumSetDepth) - .mapToObj(ProductTypeReferenceResolutionUtils::getExpansionPathForSetDepth) - .collect(toList()); - } + private static ExpansionPath getExpansionPathForSetDepth(final int maximumSetDepth) { - private static ExpansionPath getExpansionPathForSetDepth(final int maximumSetDepth) { + final String elementTypePath = + IntStream.range(0, maximumSetDepth) + .mapToObj(index -> "elementType") + .collect(Collectors.joining(".")); + return ExpansionPath.of(format("attributes[*].type.%s.typeReference", elementTypePath)); + } - final String elementTypePath = IntStream.range(0, maximumSetDepth) - .mapToObj(index -> "elementType") - .collect(Collectors.joining(".")); - return ExpansionPath.of(format("attributes[*].type.%s.typeReference", elementTypePath)); - } - - private ProductTypeReferenceResolutionUtils() { - } + private ProductTypeReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeSyncUtils.java b/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeSyncUtils.java index b06dc95fd5..62ce2d17cd 100644 --- a/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeSyncUtils.java +++ b/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeSyncUtils.java @@ -1,52 +1,50 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildAttributesUpdateActions; +import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildChangeDescriptionAction; +import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildChangeNameAction; + import com.commercetools.sync.producttypes.ProductTypeSyncOptions; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.ProductTypeDraft; - -import javax.annotation.Nonnull; import java.util.List; - -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildAttributesUpdateActions; -import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildChangeDescriptionAction; -import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildChangeNameAction; +import javax.annotation.Nonnull; public final class ProductTypeSyncUtils { - /** - * Compares all the fields (including the attributes see - * {@link ProductTypeUpdateActionUtils#buildAttributesUpdateActions}) of a {@link ProductType} and a - * {@link ProductTypeDraft}. It returns a {@link List} of {@link UpdateAction}<{@link ProductType}> as a - * result. If no update action is needed, for example in case where both the {@link ProductType} and the - * {@link ProductTypeDraft} have the same fields, an empty {@link List} is returned. - * - * @param oldProductType the {@link ProductType} which should be updated. - * @param newProductType the {@link ProductTypeDraft} where we get the new data. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied by - * the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link ProductTypeSyncOptions} - * for more info. - * @return A list of productType-specific update actions. - */ - @Nonnull - public static List> buildActions( - @Nonnull final ProductType oldProductType, - @Nonnull final ProductTypeDraft newProductType, - @Nonnull final ProductTypeSyncOptions syncOptions) { - - final List> updateActions = - filterEmptyOptionals( - buildChangeNameAction(oldProductType, newProductType), - buildChangeDescriptionAction(oldProductType, newProductType) - ); - - updateActions.addAll(buildAttributesUpdateActions(oldProductType, newProductType, syncOptions)); - - return updateActions; - } - - private ProductTypeSyncUtils() { - } + /** + * Compares all the fields (including the attributes see {@link + * ProductTypeUpdateActionUtils#buildAttributesUpdateActions}) of a {@link ProductType} and a + * {@link ProductTypeDraft}. It returns a {@link List} of {@link UpdateAction}<{@link + * ProductType}> as a result. If no update action is needed, for example in case where both the + * {@link ProductType} and the {@link ProductTypeDraft} have the same fields, an empty {@link + * List} is returned. + * + * @param oldProductType the {@link ProductType} which should be updated. + * @param newProductType the {@link ProductTypeDraft} where we get the new data. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * ProductTypeSyncOptions} for more info. + * @return A list of productType-specific update actions. + */ + @Nonnull + public static List> buildActions( + @Nonnull final ProductType oldProductType, + @Nonnull final ProductTypeDraft newProductType, + @Nonnull final ProductTypeSyncOptions syncOptions) { + + final List> updateActions = + filterEmptyOptionals( + buildChangeNameAction(oldProductType, newProductType), + buildChangeDescriptionAction(oldProductType, newProductType)); + + updateActions.addAll(buildAttributesUpdateActions(oldProductType, newProductType, syncOptions)); + + return updateActions; + } + + private ProductTypeSyncUtils() {} } diff --git a/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeUpdateActionUtils.java b/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeUpdateActionUtils.java index 9f2a5a3dc9..0469d18c1b 100644 --- a/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeUpdateActionUtils.java @@ -1,5 +1,9 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.producttypes.utils.AttributeDefinitionsUpdateActionUtils.buildAttributeDefinitionsUpdateActions; +import static java.lang.String.format; +import static java.util.Collections.emptyList; import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.SyncException; @@ -9,89 +13,89 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.commands.updateactions.ChangeDescription; import io.sphere.sdk.producttypes.commands.updateactions.ChangeName; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.producttypes.utils.AttributeDefinitionsUpdateActionUtils.buildAttributeDefinitionsUpdateActions; -import static java.lang.String.format; -import static java.util.Collections.emptyList; +import javax.annotation.Nonnull; public final class ProductTypeUpdateActionUtils { - /** - * Compares the {@code name} values of a {@link ProductType} and a {@link ProductTypeDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeName"} - * {@link UpdateAction}. If both {@link ProductType} and {@link ProductTypeDraft} have the same - * {@code name} values, then no update action is needed and empty optional will be returned. - * - * @param oldProductType the product type that should be updated. - * @param newProductType the product type draft which contains the new name. - * @return optional containing update action or empty optional if names are identical. - */ - @Nonnull - public static Optional> buildChangeNameAction( - @Nonnull final ProductType oldProductType, - @Nonnull final ProductTypeDraft newProductType) { + /** + * Compares the {@code name} values of a {@link ProductType} and a {@link ProductTypeDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "changeName"} + * {@link UpdateAction}. If both {@link ProductType} and {@link ProductTypeDraft} have the same + * {@code name} values, then no update action is needed and empty optional will be returned. + * + * @param oldProductType the product type that should be updated. + * @param newProductType the product type draft which contains the new name. + * @return optional containing update action or empty optional if names are identical. + */ + @Nonnull + public static Optional> buildChangeNameAction( + @Nonnull final ProductType oldProductType, @Nonnull final ProductTypeDraft newProductType) { - return buildUpdateAction(oldProductType.getName(), newProductType.getName(), - () -> ChangeName.of(newProductType.getName())); - } + return buildUpdateAction( + oldProductType.getName(), + newProductType.getName(), + () -> ChangeName.of(newProductType.getName())); + } - /** - * Compares the {@code description} values of a {@link ProductType} and a {@link ProductTypeDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeDescription"} - * {@link UpdateAction}. If both {@link ProductType} and {@link ProductTypeDraft} have the same - * {@code description} values, then no update action is needed and empty optional will be returned. - * - * @param oldProductType the product type that should be updated. - * @param newProductType the product type draft which contains the new description. - * @return optional containing update action or empty optional if descriptions are identical. - */ - @Nonnull - public static Optional> buildChangeDescriptionAction( - @Nonnull final ProductType oldProductType, - @Nonnull final ProductTypeDraft newProductType) { + /** + * Compares the {@code description} values of a {@link ProductType} and a {@link ProductTypeDraft} + * and returns an {@link Optional} of update action, which would contain the {@code + * "changeDescription"} {@link UpdateAction}. If both {@link ProductType} and {@link + * ProductTypeDraft} have the same {@code description} values, then no update action is needed and + * empty optional will be returned. + * + * @param oldProductType the product type that should be updated. + * @param newProductType the product type draft which contains the new description. + * @return optional containing update action or empty optional if descriptions are identical. + */ + @Nonnull + public static Optional> buildChangeDescriptionAction( + @Nonnull final ProductType oldProductType, @Nonnull final ProductTypeDraft newProductType) { - return buildUpdateAction(oldProductType.getDescription(), newProductType.getDescription(), - () -> ChangeDescription.of(newProductType.getDescription())); - } + return buildUpdateAction( + oldProductType.getDescription(), + newProductType.getDescription(), + () -> ChangeDescription.of(newProductType.getDescription())); + } - /** - * Compares the attributes of a {@link ProductType} and a {@link ProductTypeDraft} and returns a list of - * {@link UpdateAction}<{@link ProductType}> as a result. If both the {@link ProductType} and - * the {@link ProductTypeDraft} have identical attributes, then no update action is needed and hence an empty - * {@link List} is returned. In case, the new product type draft has a list of attributes in which a duplicate name - * exists, the error callback is triggered and an empty list is returned. - * - * @param oldProductType the product type which should be updated. - * @param newProductType the product type draft where we get the key. - * @param syncOptions responsible for supplying the sync options to the sync utility method. - * It is used for triggering the error callback within the utility, in case of - * errors. - * @return A list with the update actions or an empty list if the attributes are identical. - */ - @Nonnull - public static List> buildAttributesUpdateActions( - @Nonnull final ProductType oldProductType, - @Nonnull final ProductTypeDraft newProductType, - @Nonnull final ProductTypeSyncOptions syncOptions) { + /** + * Compares the attributes of a {@link ProductType} and a {@link ProductTypeDraft} and returns a + * list of {@link UpdateAction}<{@link ProductType}> as a result. If both the {@link + * ProductType} and the {@link ProductTypeDraft} have identical attributes, then no update action + * is needed and hence an empty {@link List} is returned. In case, the new product type draft has + * a list of attributes in which a duplicate name exists, the error callback is triggered and an + * empty list is returned. + * + * @param oldProductType the product type which should be updated. + * @param newProductType the product type draft where we get the key. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the update actions or an empty list if the attributes are identical. + */ + @Nonnull + public static List> buildAttributesUpdateActions( + @Nonnull final ProductType oldProductType, + @Nonnull final ProductTypeDraft newProductType, + @Nonnull final ProductTypeSyncOptions syncOptions) { - try { - return buildAttributeDefinitionsUpdateActions( - oldProductType.getAttributes(), - newProductType.getAttributes() - ); - } catch (final BuildUpdateActionException exception) { - syncOptions.applyErrorCallback( - new SyncException(format("Failed to build update actions for the attributes definitions " - + "of the product type with the key '%s'. Reason: %s", oldProductType.getKey(), exception), - exception), oldProductType, newProductType, null); - return emptyList(); - } + try { + return buildAttributeDefinitionsUpdateActions( + oldProductType.getAttributes(), newProductType.getAttributes()); + } catch (final BuildUpdateActionException exception) { + syncOptions.applyErrorCallback( + new SyncException( + format( + "Failed to build update actions for the attributes definitions " + + "of the product type with the key '%s'. Reason: %s", + oldProductType.getKey(), exception), + exception), + oldProductType, + newProductType, + null); + return emptyList(); } + } - private ProductTypeUpdateActionUtils() { - } + private ProductTypeUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/services/CartDiscountService.java b/src/main/java/com/commercetools/sync/services/CartDiscountService.java index ec73a260f3..deba3f201b 100644 --- a/src/main/java/com/commercetools/sync/services/CartDiscountService.java +++ b/src/main/java/com/commercetools/sync/services/CartDiscountService.java @@ -5,77 +5,81 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.SphereException; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface CartDiscountService { - /** - * Given a {@link Set} of CartDiscount keys, this method fetches a set of all the CartDiscounts, matching this given - * set of keys in the CTP project, defined in an injected {@link SphereClient}. A - * mapping of the key to the id of the fetched CartDiscount is persisted in an in-memory map. - * - * @param keys set of CartDiscounts keys to fetch matching CartDiscount by. - * @return {@link CompletionStage}<{@link Set}> in which the result of its completion contains a {@link Set} - * of all matching CartDiscounts. - */ - @Nonnull - CompletionStage> fetchMatchingCartDiscountsByKeys(@Nonnull Set keys); - + /** + * Given a {@link Set} of CartDiscount keys, this method fetches a set of all the CartDiscounts, + * matching this given set of keys in the CTP project, defined in an injected {@link + * SphereClient}. A mapping of the key to the id of the fetched CartDiscount is persisted in an + * in-memory map. + * + * @param keys set of CartDiscounts keys to fetch matching CartDiscount by. + * @return {@link CompletionStage}<{@link Set}> in which the result of its completion + * contains a {@link Set} of all matching CartDiscounts. + */ + @Nonnull + CompletionStage> fetchMatchingCartDiscountsByKeys(@Nonnull Set keys); - /** - * Given a cart discount key, this method fetches a cart discount that matches this given key in the CTP project - * defined in a potentially injected {@link SphereClient}. If there is no matching cart discount an empty - * {@link Optional} will be returned in the returned future. A mapping of the key to the id of the fetched cart - * discount is persisted in an in-memory map. - * - * @param key the key of the product to fetch. - * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion contains an - * {@link Optional} that contains the matching {@link CartDiscount} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchCartDiscount(@Nullable String key); + /** + * Given a cart discount key, this method fetches a cart discount that matches this given key in + * the CTP project defined in a potentially injected {@link SphereClient}. If there is no matching + * cart discount an empty {@link Optional} will be returned in the returned future. A mapping of + * the key to the id of the fetched cart discount is persisted in an in-memory map. + * + * @param key the key of the product to fetch. + * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion + * contains an {@link Optional} that contains the matching {@link CartDiscount} if exists, + * otherwise empty. + */ + @Nonnull + CompletionStage> fetchCartDiscount(@Nullable String key); - /** - * Given a resource draft of type {@link CartDiscountDraft}, this method attempts to create a resource - * {@link CartDiscount} based on it in the CTP project defined by the sync options. - * - *

A completion stage containing an empty {@link Optional} - * and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the CartDiscount gets created successfully on CTP, then the created CartDiscount's id - * and key are cached. The method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the created `CartDiscount`. - * - * @param cartDiscountDraft the resource draft to create a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created {@link CartDiscount} if successful - * otherwise an empty optional. - */ - @Nonnull - CompletionStage> createCartDiscount(@Nonnull CartDiscountDraft cartDiscountDraft); + /** + * Given a resource draft of type {@link CartDiscountDraft}, this method attempts to create a + * resource {@link CartDiscount} based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty {@link Optional} and the error callback will be + * triggered in those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the CartDiscount gets created successfully on CTP, then the created + * CartDiscount's id and key are cached. The method returns a {@link CompletionStage} in which the + * result of it's completion contains an instance {@link Optional} of the created `CartDiscount`. + * + * @param cartDiscountDraft the resource draft to create a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created {@link CartDiscount} + * if successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createCartDiscount( + @Nonnull CartDiscountDraft cartDiscountDraft); - /** - * Given a {@link CartDiscount} and a {@link List}<{@link UpdateAction}<{@link CartDiscount}>>, this - * method issues an update request with these update actions on this {@link CartDiscount} in the CTP project - * defined in a potentially injected {@link SphereClient}. This method returns - * {@link CompletionStage}<{@link CartDiscount}> in which the result of its completion contains an instance - * of the {@link CartDiscount} which was updated in the CTP project. - * - * @param cartDiscount the {@link CartDiscount} to update. - * @param updateActions the update actions to update the {@link CartDiscount} with. - * @return {@link CompletionStage}<{@link CartDiscount}> containing as a result of its completion an - * instance of the {@link CartDiscount} which was updated in the CTP project or a {@link SphereException}. - */ - @Nonnull - CompletionStage updateCartDiscount(@Nonnull CartDiscount cartDiscount, - @Nonnull List> updateActions); + /** + * Given a {@link CartDiscount} and a {@link List}<{@link UpdateAction}<{@link + * CartDiscount}>>, this method issues an update request with these update actions on this + * {@link CartDiscount} in the CTP project defined in a potentially injected {@link SphereClient}. + * This method returns {@link CompletionStage}<{@link CartDiscount}> in which the result of + * its completion contains an instance of the {@link CartDiscount} which was updated in the CTP + * project. + * + * @param cartDiscount the {@link CartDiscount} to update. + * @param updateActions the update actions to update the {@link CartDiscount} with. + * @return {@link CompletionStage}<{@link CartDiscount}> containing as a result of its + * completion an instance of the {@link CartDiscount} which was updated in the CTP project or + * a {@link SphereException}. + */ + @Nonnull + CompletionStage updateCartDiscount( + @Nonnull CartDiscount cartDiscount, @Nonnull List> updateActions); } diff --git a/src/main/java/com/commercetools/sync/services/CategoryService.java b/src/main/java/com/commercetools/sync/services/CategoryService.java index 6b70b615d7..a17e195d93 100644 --- a/src/main/java/com/commercetools/sync/services/CategoryService.java +++ b/src/main/java/com/commercetools/sync/services/CategoryService.java @@ -4,106 +4,111 @@ import io.sphere.sdk.categories.CategoryDraft; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface CategoryService { - /** - * Filters out the keys which are already cached and fetches only the not-cached category keys from the CTP project - * defined in an injected {@link SphereClient} and stores a mapping for every category to id in the cached map of - * keys -> ids and returns this cached map. - * - * @param categoryKeys - a set category keys to fetch and cache the ids for - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of - * requested category keys -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull final Set categoryKeys); + /** + * Filters out the keys which are already cached and fetches only the not-cached category keys + * from the CTP project defined in an injected {@link SphereClient} and stores a mapping for every + * category to id in the cached map of keys -> ids and returns this cached map. + * + * @param categoryKeys - a set category keys to fetch and cache the ids for + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of requested category keys -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull final Set categoryKeys); - /** - * Given a {@link Set} of category keys, this method fetches a set of all the categories, matching this given set of - * keys in the CTP project, defined in an injected {@link io.sphere.sdk.client.SphereClient}. A mapping - * of the key to the id of the fetched categories is persisted in an in-memory map. - * - * @param categoryKeys set of category keys to fetch matching categories by. - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a {@link Set} - * of all matching categories. - */ - @Nonnull - CompletionStage> fetchMatchingCategoriesByKeys(@Nonnull Set categoryKeys); + /** + * Given a {@link Set} of category keys, this method fetches a set of all the categories, matching + * this given set of keys in the CTP project, defined in an injected {@link + * io.sphere.sdk.client.SphereClient}. A mapping of the key to the id of the fetched categories is + * persisted in an in-memory map. + * + * @param categoryKeys set of category keys to fetch matching categories by. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a {@link Set} of all matching categories. + */ + @Nonnull + CompletionStage> fetchMatchingCategoriesByKeys(@Nonnull Set categoryKeys); - /** - * Given a category key, this method fetches a category that matches this given key in the CTP project defined in a - * potentially injected {@link SphereClient}. If there is no matching category an empty {@link Optional} will be - * returned in the returned future. A mapping of the key to the id of the fetched category is persisted in an in - * -memory map. - * - * @param key the key of the category to fetch. - * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion contains an - * {@link Optional} that contains the matching {@link Category} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchCategory(@Nullable String key); + /** + * Given a category key, this method fetches a category that matches this given key in the CTP + * project defined in a potentially injected {@link SphereClient}. If there is no matching + * category an empty {@link Optional} will be returned in the returned future. A mapping of the + * key to the id of the fetched category is persisted in an in -memory map. + * + * @param key the key of the category to fetch. + * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion + * contains an {@link Optional} that contains the matching {@link Category} if exists, + * otherwise empty. + */ + @Nonnull + CompletionStage> fetchCategory(@Nullable String key); - /** - * Given a {@code key}, this method first checks if cached map of category keys -> ids contains the key. - * If it does, then an optional containing the mapped id is returned. If the cache doesn't contain the key; this - * method attempts to fetch the id of the key from the CTP project, caches it and returns a - * {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's completion - * could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no {@link Category} - * was found in the CTP project with this key. - * - * @param key the key by which a {@link Category} id should be fetched from the CTP project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link Category} was found in the CTP project with this key. - */ - @Nonnull - CompletionStage> fetchCachedCategoryId(@Nonnull String key); + /** + * Given a {@code key}, this method first checks if cached map of category keys -> ids contains + * the key. If it does, then an optional containing the mapped id is returned. If the cache + * doesn't contain the key; this method attempts to fetch the id of the key from the CTP project, + * caches it and returns a {@link CompletionStage}<{@link Optional}<{@link String}>> + * in which the result of it's completion could contain an {@link Optional} with the id inside of + * it or an empty {@link Optional} if no {@link Category} was found in the CTP project with this + * key. + * + * @param key the key by which a {@link Category} id should be fetched from the CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of it's completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link Category} was found in the CTP project with this key. + */ + @Nonnull + CompletionStage> fetchCachedCategoryId(@Nonnull String key); - /** - * Given a resource draft of type {@link CategoryDraft}, this method attempts to create a resource - * {@link Category} based on it in the CTP project defined by the sync options. - * - *

A completion stage containing an empty option and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, then the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param categoryDraft the resource draft to create a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> createCategory(@Nonnull CategoryDraft categoryDraft); + /** + * Given a resource draft of type {@link CategoryDraft}, this method attempts to create a resource + * {@link Category} based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty option and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, then the created + * resource's id and key are cached and the method returns a {@link CompletionStage} in which the + * result of it's completion contains an instance {@link Optional} of the resource which was + * created. + * + * @param categoryDraft the resource draft to create a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createCategory(@Nonnull CategoryDraft categoryDraft); - /** - * Given a {@link Category} and a {@link List}<{@link UpdateAction}<{@link Category}>>, this method - * issues an update request with these update actions on this {@link Category} in the CTP project defined in a - * potentially injected {@link io.sphere.sdk.client.SphereClient}. This method returns - * {@link CompletionStage}<{@link Category}> in which the result of it's completion contains an instance of - * the {@link Category} which was updated in the CTP project. - * - * @param category the {@link Category} to update. - * @param updateActions the update actions to update the {@link Category} with. - * @return {@link CompletionStage}<{@link Category}> containing as a result of it's completion an instance of - * the {@link Category} which was updated in the CTP project or a - * {@link io.sphere.sdk.models.SphereException}. - */ - @Nonnull - CompletionStage updateCategory(@Nonnull Category category, - @Nonnull List> updateActions); + /** + * Given a {@link Category} and a {@link List}<{@link UpdateAction}<{@link + * Category}>>, this method issues an update request with these update actions on this + * {@link Category} in the CTP project defined in a potentially injected {@link + * io.sphere.sdk.client.SphereClient}. This method returns {@link CompletionStage}<{@link + * Category}> in which the result of it's completion contains an instance of the {@link + * Category} which was updated in the CTP project. + * + * @param category the {@link Category} to update. + * @param updateActions the update actions to update the {@link Category} with. + * @return {@link CompletionStage}<{@link Category}> containing as a result of it's + * completion an instance of the {@link Category} which was updated in the CTP project or a + * {@link io.sphere.sdk.models.SphereException}. + */ + @Nonnull + CompletionStage updateCategory( + @Nonnull Category category, @Nonnull List> updateActions); } - diff --git a/src/main/java/com/commercetools/sync/services/ChannelService.java b/src/main/java/com/commercetools/sync/services/ChannelService.java index ab2b275149..1b29f19e8e 100644 --- a/src/main/java/com/commercetools/sync/services/ChannelService.java +++ b/src/main/java/com/commercetools/sync/services/ChannelService.java @@ -2,62 +2,62 @@ import io.sphere.sdk.channels.Channel; import io.sphere.sdk.client.SphereClient; - -import javax.annotation.Nonnull; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; public interface ChannelService { - /** - * Filters out the keys which are already cached and fetches only the not-cached channel keys from the - * CTP project defined in an injected {@link SphereClient} and stores a mapping for every channel to id - * in the cached map of keys -> ids and returns this cached map. - * - * @param channelKeys - a set of channel keys to fetch and cache the ids for - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of - * requested tax channel -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull final Set channelKeys); + /** + * Filters out the keys which are already cached and fetches only the not-cached channel keys from + * the CTP project defined in an injected {@link SphereClient} and stores a mapping for every + * channel to id in the cached map of keys -> ids and returns this cached map. + * + * @param channelKeys - a set of channel keys to fetch and cache the ids for + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of requested tax channel -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull final Set channelKeys); - /** - * Given a {@code key}, this method first checks if a cached map of channel keys -> ids is not empty. - * If not, it returns a completed future that contains an optional that contains what this key maps to in - * the cache. If the cache is empty, the method populates the cache with the mapping of all channels' keys to ids in - * the CTP project, by querying the CTP project channels. - * - *

After that, the method returns a {@link CompletionStage}<{@link Optional}<{@link String}>> - * in which the result of it's completion could contain an - * {@link Optional} with the id inside of it or an empty {@link Optional} if no {@link Channel} was - * found in the CTP project with this key. - * - * @param key the key by which a {@link Channel} id should be fetched from the CTP project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link Channel} was found in the CTP project with this external id. - */ - @Nonnull - CompletionStage> fetchCachedChannelId(@Nonnull String key); + /** + * Given a {@code key}, this method first checks if a cached map of channel keys -> ids is not + * empty. If not, it returns a completed future that contains an optional that contains what this + * key maps to in the cache. If the cache is empty, the method populates the cache with the + * mapping of all channels' keys to ids in the CTP project, by querying the CTP project channels. + * + *

After that, the method returns a {@link CompletionStage}<{@link Optional}<{@link + * String}>> in which the result of it's completion could contain an {@link Optional} with + * the id inside of it or an empty {@link Optional} if no {@link Channel} was found in the CTP + * project with this key. + * + * @param key the key by which a {@link Channel} id should be fetched from the CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of it's completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link Channel} was found in the CTP project with this + * external id. + */ + @Nonnull + CompletionStage> fetchCachedChannelId(@Nonnull String key); - /** - * Creates a new channel with the supplied {@code key}. - * - * @param key key of supply channel. - * @return a future containing as a result the created {@link Channel} or a sphere exception. - */ - @Nonnull - CompletionStage> createChannel(@Nonnull final String key); + /** + * Creates a new channel with the supplied {@code key}. + * + * @param key key of supply channel. + * @return a future containing as a result the created {@link Channel} or a sphere exception. + */ + @Nonnull + CompletionStage> createChannel(@Nonnull final String key); - /** - * Creates a new channel with the supplied {@code key} and puts a new mapping of it's key - * to id in a cache map. - * - * @param key key of supply channel. - * @return a future containing as a result the created {@link Channel} or a sphere exception. - */ - @Nonnull - CompletionStage> createAndCacheChannel(@Nonnull final String key); + /** + * Creates a new channel with the supplied {@code key} and puts a new mapping of it's key to id in + * a cache map. + * + * @param key key of supply channel. + * @return a future containing as a result the created {@link Channel} or a sphere exception. + */ + @Nonnull + CompletionStage> createAndCacheChannel(@Nonnull final String key); } diff --git a/src/main/java/com/commercetools/sync/services/CustomObjectService.java b/src/main/java/com/commercetools/sync/services/CustomObjectService.java index ee50175714..561751e739 100644 --- a/src/main/java/com/commercetools/sync/services/CustomObjectService.java +++ b/src/main/java/com/commercetools/sync/services/CustomObjectService.java @@ -5,99 +5,105 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.CustomObjectDraft; - -import javax.annotation.Nonnull; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; public interface CustomObjectService { + /** + * Filters out the custom object identifiers which are already cached and fetches only the + * not-cached custom object identifiers from the CTP project defined in an injected {@link + * SphereClient} and stores a mapping for every custom object to id in the cached map of keys + * -> ids and returns this cached map. + * + * @param identifiers - a set custom object identifiers to fetch and cache the ids for + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of requested custom object identifiers -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds( + @Nonnull final Set identifiers); - /** - * Filters out the custom object identifiers which are already cached and fetches only the not-cached custom object - * identifiers from the CTP project defined in an injected {@link SphereClient} and stores a mapping for every - * custom object to id in the cached map of keys -> ids and returns this cached map. - * - * @param identifiers - a set custom object identifiers to fetch and cache the ids for - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of - * requested custom object identifiers -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds( - @Nonnull final Set identifiers); - - /** - * Given an {@code identifier}, this method first checks if {@code identifier#toString()} - * is contained in a cached map of {@link CustomObjectCompositeIdentifier#toString()} -> ids . - * If it contains, it returns a {@link CompletionStage}<{@link Optional}<{@link String}>> - * in which String is what this identifier maps to in the cache. If the cache doesn't contain the identifier, - * this method attempts to fetch the id of the identifier from the CTP project, caches it and returns - * a {@link CompletionStage}<{@link Optional}<{@link String}>> - * in which the {@link Optional} could contain the id inside of it. - * - * @param identifier the identifier object containing CustomObject key and container, by which a - * {@link io.sphere.sdk.customobjects.CustomObject} id should be fetched from the CTP project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of its - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link CustomObject} was found in the CTP project with this identifier. - */ - @Nonnull - CompletionStage> fetchCachedCustomObjectId(@Nonnull CustomObjectCompositeIdentifier identifier); + /** + * Given an {@code identifier}, this method first checks if {@code identifier#toString()} is + * contained in a cached map of {@link CustomObjectCompositeIdentifier#toString()} -> ids . If + * it contains, it returns a {@link CompletionStage}<{@link Optional}<{@link String}>> + * in which String is what this identifier maps to in the cache. If the cache doesn't contain the + * identifier, this method attempts to fetch the id of the identifier from the CTP project, caches + * it and returns a {@link CompletionStage}<{@link Optional}<{@link String}>> in which + * the {@link Optional} could contain the id inside of it. + * + * @param identifier the identifier object containing CustomObject key and container, by which a + * {@link io.sphere.sdk.customobjects.CustomObject} id should be fetched from the CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of its completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link CustomObject} was found in the CTP project with this + * identifier. + */ + @Nonnull + CompletionStage> fetchCachedCustomObjectId( + @Nonnull CustomObjectCompositeIdentifier identifier); - /** - * Given a {@link Set} of CustomObjectCompositeIdentifier, this method fetches a set of all the CustomObjects, - * matching this given set of CustomObjectCompositeIdentifiers in the CTP project defined in an injected - * {@link io.sphere.sdk.client.SphereClient}. A mapping of the CustomObjectCompositeIdentifier to the id of the - * fetched CustomObject is persisted in an in-memory map. - * - * @param identifiers set of CustomObjectCompositeIdentifiers. Each identifier includes key and container to fetch - * matching CustomObject. - * @return {@link CompletionStage}<{@link Map}> in which the result of its completion contains a {@link Set} - * of all matching CustomObjects. - */ - @Nonnull - CompletionStage>> fetchMatchingCustomObjects( - @Nonnull Set identifiers); + /** + * Given a {@link Set} of CustomObjectCompositeIdentifier, this method fetches a set of all the + * CustomObjects, matching this given set of CustomObjectCompositeIdentifiers in the CTP project + * defined in an injected {@link io.sphere.sdk.client.SphereClient}. A mapping of the + * CustomObjectCompositeIdentifier to the id of the fetched CustomObject is persisted in an + * in-memory map. + * + * @param identifiers set of CustomObjectCompositeIdentifiers. Each identifier includes key and + * container to fetch matching CustomObject. + * @return {@link CompletionStage}<{@link Map}> in which the result of its completion + * contains a {@link Set} of all matching CustomObjects. + */ + @Nonnull + CompletionStage>> fetchMatchingCustomObjects( + @Nonnull Set identifiers); - /** - * Given a CustomObjectCompositeIdentifier identify which includes key and container of CustomObject, this method - * fetches a CustomObject that matches this given identifier in the CTP project defined in an - * injected {@link SphereClient}. If there is no matching CustomObject an empty {@link Optional} will be - * returned in the returned future. - * - * @param identifier the identifier of the CustomObject to fetch. - * @return {@link CompletionStage}<{@link Optional}> in which the result of its completion contains an - * {@link Optional} that contains the matching {@link CustomObject} if exists, otherwise empty. - */ - @Nonnull - CompletionStage>> fetchCustomObject( - @Nonnull CustomObjectCompositeIdentifier identifier); + /** + * Given a CustomObjectCompositeIdentifier identify which includes key and container of + * CustomObject, this method fetches a CustomObject that matches this given identifier in the CTP + * project defined in an injected {@link SphereClient}. If there is no matching CustomObject an + * empty {@link Optional} will be returned in the returned future. + * + * @param identifier the identifier of the CustomObject to fetch. + * @return {@link CompletionStage}<{@link Optional}> in which the result of its completion + * contains an {@link Optional} that contains the matching {@link CustomObject} if exists, + * otherwise empty. + */ + @Nonnull + CompletionStage>> fetchCustomObject( + @Nonnull CustomObjectCompositeIdentifier identifier); - /** - * Given a resource draft of CustomObject {@link CustomObjectDraft}, this method attempts to create or update - * a resource {@link CustomObject} based on it in the CTP project defined by the sync options. - * - *

A completion stage containing an empty optional and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created or updated successfully on CTP, then the created resource's - * id and key/container wrapped by {@link CustomObjectCompositeIdentifier} are cached and the method returns a - * {@link CompletionStage} in which the result of its completion contains an instance {@link Optional} of the - * resource which was created or updated. - * - *

If an object with the given container/key exists on CTP, the object will be replaced with the new value and - * the version is incremente. - * - * @param customObjectDraft the resource draft to create or update a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created/updated resource if successful - * otherwise an empty optional. - */ - @Nonnull - CompletionStage>> upsertCustomObject( - @Nonnull CustomObjectDraft customObjectDraft); + /** + * Given a resource draft of CustomObject {@link CustomObjectDraft}, this method attempts to + * create or update a resource {@link CustomObject} based on it in the CTP project defined by the + * sync options. + * + *

A completion stage containing an empty optional and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created or updated successfully on CTP, then the + * created resource's id and key/container wrapped by {@link CustomObjectCompositeIdentifier} are + * cached and the method returns a {@link CompletionStage} in which the result of its completion + * contains an instance {@link Optional} of the resource which was created or updated. + * + *

If an object with the given container/key exists on CTP, the object will be replaced with + * the new value and the version is incremente. + * + * @param customObjectDraft the resource draft to create or update a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created/updated resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage>> upsertCustomObject( + @Nonnull CustomObjectDraft customObjectDraft); } diff --git a/src/main/java/com/commercetools/sync/services/CustomerGroupService.java b/src/main/java/com/commercetools/sync/services/CustomerGroupService.java index c29b9c9d5e..5f88b063cf 100644 --- a/src/main/java/com/commercetools/sync/services/CustomerGroupService.java +++ b/src/main/java/com/commercetools/sync/services/CustomerGroupService.java @@ -2,43 +2,42 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customergroups.CustomerGroup; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface CustomerGroupService { - /** - * Filters out the keys which are already cached and fetches only the not-cached customer group keys from the CTP - * project defined in an injected {@link SphereClient} and stores a mapping for every customer group to id in - * the cached map of keys -> ids and returns this cached map. - * - * @param customerGroupKeys - a set customer group keys to fetch and cache the ids for - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of - * requested customer group keys -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull final Set customerGroupKeys); + /** + * Filters out the keys which are already cached and fetches only the not-cached customer group + * keys from the CTP project defined in an injected {@link SphereClient} and stores a mapping for + * every customer group to id in the cached map of keys -> ids and returns this cached map. + * + * @param customerGroupKeys - a set customer group keys to fetch and cache the ids for + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of requested customer group keys -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull final Set customerGroupKeys); - /** - * Given a {@code key}, this method first checks if a cached map of customer group keys -> ids contains the key. - * If not, it returns a completed future that contains an {@link Optional} that contains what this key maps to in - * the cache. If the cache doesn't contain the key; this method attempts to fetch the id of the key from the CTP - * project, caches it and returns a {@link CompletionStage}<{@link Optional}<{@link String}>> - * in which the result of its completion could contain an {@link Optional} with the id inside of it or an empty - * {@link Optional} if no {@link CustomerGroup} was found in the CTP project with this - * key. - * - * @param key the key by which a {@link CustomerGroup} id should be fetched from the CTP project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of its - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link CustomerGroup} was found in the CTP project with this key or a blank (null/empty string) key was - * supplied. - */ - @Nonnull - CompletionStage> fetchCachedCustomerGroupId(@Nullable String key); + /** + * Given a {@code key}, this method first checks if a cached map of customer group keys -> ids + * contains the key. If not, it returns a completed future that contains an {@link Optional} that + * contains what this key maps to in the cache. If the cache doesn't contain the key; this method + * attempts to fetch the id of the key from the CTP project, caches it and returns a {@link + * CompletionStage}<{@link Optional}<{@link String}>> in which the result of its + * completion could contain an {@link Optional} with the id inside of it or an empty {@link + * Optional} if no {@link CustomerGroup} was found in the CTP project with this key. + * + * @param key the key by which a {@link CustomerGroup} id should be fetched from the CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of its completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link CustomerGroup} was found in the CTP project with this + * key or a blank (null/empty string) key was supplied. + */ + @Nonnull + CompletionStage> fetchCachedCustomerGroupId(@Nullable String key); } diff --git a/src/main/java/com/commercetools/sync/services/CustomerService.java b/src/main/java/com/commercetools/sync/services/CustomerService.java index cfbc6af398..af793e5271 100644 --- a/src/main/java/com/commercetools/sync/services/CustomerService.java +++ b/src/main/java/com/commercetools/sync/services/CustomerService.java @@ -4,103 +4,106 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.customers.Customer; import io.sphere.sdk.customers.CustomerDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface CustomerService { - /** - * Given a set of keys this method caches in-memory a mapping of key -> id only for those keys which are not - * already cached. - * - * @param keysToCache a set of keys to cache. - * @return a map of key to ids of the requested keys. - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull Set keysToCache); - - /** - * Given a {@link Set} of customer keys, this method fetches a set of all the customers, matching the given set of - * keys in the CTP project, defined in an injected {@link SphereClient}. A mapping of the key to the id of the - * fetched customers is persisted in an in-memory map. - * - * @param customerKeys set of customer keys to fetch matching resources by. - * @return {@link CompletionStage}<{@link Set}<{@link Customer}>> in which the result of it's completion - * contains a {@link Set} of all matching customers. - */ - @Nonnull - CompletionStage> fetchMatchingCustomersByKeys(@Nonnull Set customerKeys); + /** + * Given a set of keys this method caches in-memory a mapping of key -> id only for those keys + * which are not already cached. + * + * @param keysToCache a set of keys to cache. + * @return a map of key to ids of the requested keys. + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull Set keysToCache); - /** - * Given a customer key, this method fetches a customer that matches this given key in the CTP project defined in a - * potentially injected {@link SphereClient}. If there is no matching resource an empty {@link Optional} will be - * returned in the returned future. A mapping of the key to the id of the fetched customer is persisted in an in - * -memory map. - * - * @param key the key of the resource to fetch - * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion contains an {@link - * Optional} that contains the matching {@link Customer} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchCustomerByKey(@Nullable String key); + /** + * Given a {@link Set} of customer keys, this method fetches a set of all the customers, matching + * the given set of keys in the CTP project, defined in an injected {@link SphereClient}. A + * mapping of the key to the id of the fetched customers is persisted in an in-memory map. + * + * @param customerKeys set of customer keys to fetch matching resources by. + * @return {@link CompletionStage}<{@link Set}<{@link Customer}>> in which the result + * of it's completion contains a {@link Set} of all matching customers. + */ + @Nonnull + CompletionStage> fetchMatchingCustomersByKeys(@Nonnull Set customerKeys); - /** - * Given a {@code key}, if it is blank (null/empty), a completed future with an empty optional is returned. - * Otherwise this method checks if the cached map of resource keys -> ids contains the given key. If it does, an - * optional containing the matching id is returned. If the cache doesn't contain the key; this method attempts to - * fetch the id of the key from the CTP project, caches it and returns a {@link CompletionStage}< {@link - * Optional}<{@link String}>> in which the result of it's completion could contain an {@link Optional} - * holding the id or an empty {@link Optional} if no customer was found in the CTP project with this key. - * - * @param key the key by which a customer id should be fetched from the CTP project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's - * completion could contain an {@link Optional} holding the id or an empty {@link Optional} if no customer - * was found in the CTP project with this key. - */ - @Nonnull - CompletionStage> fetchCachedCustomerId(@Nonnull String key); + /** + * Given a customer key, this method fetches a customer that matches this given key in the CTP + * project defined in a potentially injected {@link SphereClient}. If there is no matching + * resource an empty {@link Optional} will be returned in the returned future. A mapping of the + * key to the id of the fetched customer is persisted in an in -memory map. + * + * @param key the key of the resource to fetch + * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion + * contains an {@link Optional} that contains the matching {@link Customer} if exists, + * otherwise empty. + */ + @Nonnull + CompletionStage> fetchCustomerByKey(@Nullable String key); - /** - * Given a resource draft of type {@link CustomerDraft}, this method attempts to create a resource {@link Customer} - * based on the draft, in the CTP project defined by the sync options. - * - *

A completion stage containing an empty option and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param customerDraft the resource draft to create a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> createCustomer(@Nonnull CustomerDraft customerDraft); + /** + * Given a {@code key}, if it is blank (null/empty), a completed future with an empty optional is + * returned. Otherwise this method checks if the cached map of resource keys -> ids contains + * the given key. If it does, an optional containing the matching id is returned. If the cache + * doesn't contain the key; this method attempts to fetch the id of the key from the CTP project, + * caches it and returns a {@link CompletionStage}< {@link Optional}<{@link String}>> + * in which the result of it's completion could contain an {@link Optional} holding the id or an + * empty {@link Optional} if no customer was found in the CTP project with this key. + * + * @param key the key by which a customer id should be fetched from the CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of it's completion could contain an {@link Optional} holding the id or an empty + * {@link Optional} if no customer was found in the CTP project with this key. + */ + @Nonnull + CompletionStage> fetchCachedCustomerId(@Nonnull String key); - /** - * Given a {@link Customer} and a {@link List}<{@link UpdateAction}<{@link Customer}>>, this method - * issues an update request with these update actions on this {@link Customer} in the CTP project defined in a - * potentially injected {@link SphereClient}. This method returns {@link CompletionStage}<{@link Customer}> in - * which the result of it's completion contains an instance of the {@link Customer} which was updated in the CTP - * project. - * - * @param customer the {@link Customer} to update. - * @param updateActions the update actions to update the {@link Customer} with. - * @return {@link CompletionStage}<{@link Customer}> containing as a result of it's completion an instance of - * the {@link Customer} which was updated in the CTP project or a {@link io.sphere.sdk.models.SphereException}. - */ - @Nonnull - CompletionStage updateCustomer(@Nonnull Customer customer, - @Nonnull List> updateActions); + /** + * Given a resource draft of type {@link CustomerDraft}, this method attempts to create a resource + * {@link Customer} based on the draft, in the CTP project defined by the sync options. + * + *

A completion stage containing an empty option and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, the created resource's + * id and key are cached and the method returns a {@link CompletionStage} in which the result of + * it's completion contains an instance {@link Optional} of the resource which was created. + * + * @param customerDraft the resource draft to create a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createCustomer(@Nonnull CustomerDraft customerDraft); -} \ No newline at end of file + /** + * Given a {@link Customer} and a {@link List}<{@link UpdateAction}<{@link + * Customer}>>, this method issues an update request with these update actions on this + * {@link Customer} in the CTP project defined in a potentially injected {@link SphereClient}. + * This method returns {@link CompletionStage}<{@link Customer}> in which the result of it's + * completion contains an instance of the {@link Customer} which was updated in the CTP project. + * + * @param customer the {@link Customer} to update. + * @param updateActions the update actions to update the {@link Customer} with. + * @return {@link CompletionStage}<{@link Customer}> containing as a result of it's + * completion an instance of the {@link Customer} which was updated in the CTP project or a + * {@link io.sphere.sdk.models.SphereException}. + */ + @Nonnull + CompletionStage updateCustomer( + @Nonnull Customer customer, @Nonnull List> updateActions); +} diff --git a/src/main/java/com/commercetools/sync/services/InventoryService.java b/src/main/java/com/commercetools/sync/services/InventoryService.java index c4e8fb3190..39a6083614 100644 --- a/src/main/java/com/commercetools/sync/services/InventoryService.java +++ b/src/main/java/com/commercetools/sync/services/InventoryService.java @@ -3,44 +3,43 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.inventory.InventoryEntry; import io.sphere.sdk.inventory.InventoryEntryDraft; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; public interface InventoryService { - /** - * Queries existing {@link InventoryEntry}'s against set of skus. - * - * @param skus {@link Set} of sku values, used in search predicate - * @return {@link List} of matching entries or empty list when there was no entry of sku matching to {@code skus}. - */ - @Nonnull - CompletionStage> fetchInventoryEntriesBySkus( - @Nonnull final Set skus); + /** + * Queries existing {@link InventoryEntry}'s against set of skus. + * + * @param skus {@link Set} of sku values, used in search predicate + * @return {@link List} of matching entries or empty list when there was no entry of sku matching + * to {@code skus}. + */ + @Nonnull + CompletionStage> fetchInventoryEntriesBySkus(@Nonnull final Set skus); - /** - * Creates new inventory entry from {@code inventoryEntryDraft}. - * - * @param inventoryEntryDraft draft with data for new inventory entry - * @return {@link CompletionStage} with created {@link InventoryEntry} or an exception - */ - @Nonnull - CompletionStage> createInventoryEntry( - @Nonnull final InventoryEntryDraft inventoryEntryDraft); + /** + * Creates new inventory entry from {@code inventoryEntryDraft}. + * + * @param inventoryEntryDraft draft with data for new inventory entry + * @return {@link CompletionStage} with created {@link InventoryEntry} or an exception + */ + @Nonnull + CompletionStage> createInventoryEntry( + @Nonnull final InventoryEntryDraft inventoryEntryDraft); - /** - * Updates existing inventory entry with {@code updateActions}. - * - * @param inventoryEntry entry that should be updated - * @param updateActions {@link List} of actions that should be applied to {@code inventoryEntry} - * @return {@link CompletionStage} with updated {@link InventoryEntry} or an exception - */ - @Nonnull - CompletionStage updateInventoryEntry( - @Nonnull final InventoryEntry inventoryEntry, - @Nonnull final List> updateActions); + /** + * Updates existing inventory entry with {@code updateActions}. + * + * @param inventoryEntry entry that should be updated + * @param updateActions {@link List} of actions that should be applied to {@code inventoryEntry} + * @return {@link CompletionStage} with updated {@link InventoryEntry} or an exception + */ + @Nonnull + CompletionStage updateInventoryEntry( + @Nonnull final InventoryEntry inventoryEntry, + @Nonnull final List> updateActions); } diff --git a/src/main/java/com/commercetools/sync/services/ProductService.java b/src/main/java/com/commercetools/sync/services/ProductService.java index 2d41f0d775..bd2e3f9ff5 100644 --- a/src/main/java/com/commercetools/sync/services/ProductService.java +++ b/src/main/java/com/commercetools/sync/services/ProductService.java @@ -4,111 +4,112 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface ProductService { - /** - * Given a {@code key}, if it is blank (null/empty), a completed future with an empty optional is returned. - * This method then checks if the cached map of product keys -> ids contains the key. If it does, then an - * optional containing the mapped id is returned. If the cache doesn't contain the key; this method attempts to - * fetch the id of the key from the CTP project, caches it and returns a - * {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's completion - * could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no {@link Product} - * was found in the CTP project with this key. - * - * @param key the key by which a {@link Product} id should be fetched from the CTP project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link Product} was found in the CTP project with this key. - */ - @Nonnull - CompletionStage> getIdFromCacheOrFetch(@Nullable String key); - - /** - * Filters out the keys which are already cached and fetches only the not-cached product keys from the CTP project - * defined in an injected {@link SphereClient} and stores a mapping for every product to id in - * the cached map of keys -> ids and returns this cached map. - * - *

Note: If all the supplied keys are already cached, the cached map is returned right away with no request to - * CTP. - * - * @param productKeys the product keys to fetch and cache the ids for. - * - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of all - * product keys -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull Set productKeys); + /** + * Given a {@code key}, if it is blank (null/empty), a completed future with an empty optional is + * returned. This method then checks if the cached map of product keys -> ids contains the key. + * If it does, then an optional containing the mapped id is returned. If the cache doesn't contain + * the key; this method attempts to fetch the id of the key from the CTP project, caches it and + * returns a {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of it's completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link Product} was found in the CTP project with this key. + * + * @param key the key by which a {@link Product} id should be fetched from the CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of it's completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link Product} was found in the CTP project with this key. + */ + @Nonnull + CompletionStage> getIdFromCacheOrFetch(@Nullable String key); - /** - * Given a {@link Set} of product keys, this method fetches a set of all the products, matching this given set of - * keys in the CTP project, defined in an injected {@link SphereClient}. A mapping of the key to the id - * of the fetched products is persisted in an in-memory map. - * - * @param productKeys set of product keys to fetch matching products by. - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a {@link Set} - * of all matching products. - */ - @Nonnull - CompletionStage> fetchMatchingProductsByKeys(@Nonnull Set productKeys); + /** + * Filters out the keys which are already cached and fetches only the not-cached product keys from + * the CTP project defined in an injected {@link SphereClient} and stores a mapping for every + * product to id in the cached map of keys -> ids and returns this cached map. + * + *

Note: If all the supplied keys are already cached, the cached map is returned right away + * with no request to CTP. + * + * @param productKeys the product keys to fetch and cache the ids for. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of all product keys -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull Set productKeys); + /** + * Given a {@link Set} of product keys, this method fetches a set of all the products, matching + * this given set of keys in the CTP project, defined in an injected {@link SphereClient}. A + * mapping of the key to the id of the fetched products is persisted in an in-memory map. + * + * @param productKeys set of product keys to fetch matching products by. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a {@link Set} of all matching products. + */ + @Nonnull + CompletionStage> fetchMatchingProductsByKeys(@Nonnull Set productKeys); - /** - * Given a product key, this method fetches a product that matches this given key in the CTP project defined in a - * potentially injected {@link SphereClient}. If there is no matching product an empty {@link Optional} will be - * returned in the returned future. A mapping of the key to the id of the fetched category is persisted in an in - * -memory map. - * - * @param key the key of the product to fetch. - * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion contains an - * {@link Optional} that contains the matching {@link Product} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchProduct(@Nullable String key); + /** + * Given a product key, this method fetches a product that matches this given key in the CTP + * project defined in a potentially injected {@link SphereClient}. If there is no matching product + * an empty {@link Optional} will be returned in the returned future. A mapping of the key to the + * id of the fetched category is persisted in an in -memory map. + * + * @param key the key of the product to fetch. + * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion + * contains an {@link Optional} that contains the matching {@link Product} if exists, + * otherwise empty. + */ + @Nonnull + CompletionStage> fetchProduct(@Nullable String key); - /** - * Given a resource draft of type {@link ProductDraft}, this method attempts to create a resource - * {@link Product} based on it in the CTP project defined by the sync options. - * - *

A completion stage containing an empty option and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, then the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param productDraft the resource draft to create a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> createProduct(@Nonnull ProductDraft productDraft); + /** + * Given a resource draft of type {@link ProductDraft}, this method attempts to create a resource + * {@link Product} based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty option and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, then the created + * resource's id and key are cached and the method returns a {@link CompletionStage} in which the + * result of it's completion contains an instance {@link Optional} of the resource which was + * created. + * + * @param productDraft the resource draft to create a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createProduct(@Nonnull ProductDraft productDraft); - /** - * Given a {@link Product} and a {@link List}<{@link UpdateAction}<{@link Product}>>, this method - * issues an update request with these update actions on this {@link Product} in the CTP project defined in a - * potentially injected {@link io.sphere.sdk.client.SphereClient}. This method returns - * {@link CompletionStage}<{@link Product}> in which the result of it's completion contains an instance of - * the {@link Product} which was updated in the CTP project. - * - * @param product the {@link Product} to update. - * @param updateActions the update actions to update the {@link Product} with. - * @return {@link CompletionStage}<{@link Product}> containing as a result of it's completion an instance of - * the {@link Product} which was updated in the CTP project or a - * {@link io.sphere.sdk.models.SphereException}. - */ - @Nonnull - CompletionStage updateProduct(@Nonnull Product product, - @Nonnull List> updateActions); -} \ No newline at end of file + /** + * Given a {@link Product} and a {@link List}<{@link UpdateAction}<{@link Product}>>, + * this method issues an update request with these update actions on this {@link Product} in the + * CTP project defined in a potentially injected {@link io.sphere.sdk.client.SphereClient}. This + * method returns {@link CompletionStage}<{@link Product}> in which the result of it's + * completion contains an instance of the {@link Product} which was updated in the CTP project. + * + * @param product the {@link Product} to update. + * @param updateActions the update actions to update the {@link Product} with. + * @return {@link CompletionStage}<{@link Product}> containing as a result of it's + * completion an instance of the {@link Product} which was updated in the CTP project or a + * {@link io.sphere.sdk.models.SphereException}. + */ + @Nonnull + CompletionStage updateProduct( + @Nonnull Product product, @Nonnull List> updateActions); +} diff --git a/src/main/java/com/commercetools/sync/services/ProductTypeService.java b/src/main/java/com/commercetools/sync/services/ProductTypeService.java index 68f822424d..a5aa7103f6 100644 --- a/src/main/java/com/commercetools/sync/services/ProductTypeService.java +++ b/src/main/java/com/commercetools/sync/services/ProductTypeService.java @@ -5,126 +5,133 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.ProductTypeDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface ProductTypeService { - /** - * Filters out the keys which are already cached and fetches only the not-cached product type keys from the CTP - * project defined in an injected {@link SphereClient} and stores a mapping for every productType to id in - * the cached map of keys -> ids and returns this cached map. - * - *

Note: If all the supplied keys are already cached, the cached map is returned right away with no request to - * CTP. - * - * @param keys the productType keys to fetch and cache the ids for. - * - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of all - * productType keys -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull Set keys); + /** + * Filters out the keys which are already cached and fetches only the not-cached product type keys + * from the CTP project defined in an injected {@link SphereClient} and stores a mapping for every + * productType to id in the cached map of keys -> ids and returns this cached map. + * + *

Note: If all the supplied keys are already cached, the cached map is returned right away + * with no request to CTP. + * + * @param keys the productType keys to fetch and cache the ids for. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of all productType keys -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull Set keys); - /** - * Given a {@code key}, this method first checks if a cached map of ProductType keys -> ids contains the key. - * If not, it returns a completed future that contains an {@link Optional} that contains what this key maps to in - * the cache. If the cache doesn't contain the key; this method attempts to fetch the id of the key from the CTP - * project, caches it and returns a {@link CompletionStage}<{@link Optional}<{@link String}>> - * in which the result of it's completion could contain an - * {@link Optional} with the id inside of it or an empty {@link Optional} if no {@link ProductType} was - * found in the CTP project with this key. - * - * @param key the key by which a {@link ProductType} id should be fetched from the CTP project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of its - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link ProductType} was found in the CTP project with this key. - */ - @Nonnull - CompletionStage> fetchCachedProductTypeId(@Nonnull String key); + /** + * Given a {@code key}, this method first checks if a cached map of ProductType keys -> ids + * contains the key. If not, it returns a completed future that contains an {@link Optional} that + * contains what this key maps to in the cache. If the cache doesn't contain the key; this method + * attempts to fetch the id of the key from the CTP project, caches it and returns a {@link + * CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's + * completion could contain an {@link Optional} with the id inside of it or an empty {@link + * Optional} if no {@link ProductType} was found in the CTP project with this key. + * + * @param key the key by which a {@link ProductType} id should be fetched from the CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of its completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link ProductType} was found in the CTP project with this + * key. + */ + @Nonnull + CompletionStage> fetchCachedProductTypeId(@Nonnull String key); - /** - * TODO FIX JAVADOC AND TEST METHOD - * Given a {@code productType}, this method first checks if a cached map of ProductType ids -> map of - * {@link AttributeMetaData} is not empty. If not, it returns a completed future that contains an optional that - * contains what this productType id maps to in the cache. If the cache is empty, - * the method populates the cache with the mapping of all ProductType ids to maps of each product type's attributes' - * {@link AttributeMetaData} in the CTP project, by querying the CTP project for all ProductTypes. - * - *

After that, the method returns a {@link CompletionStage}<{@link Optional}<{@link String}>> - * in which the result of it's completion could contain an - * {@link Optional} with a map of the attributes names -> {@link AttributeMetaData} inside of it or an empty - * {@link Optional} if no {@link ProductType} was found in the CTP project with this id. - * - * @param productTypeId the id by which a a map of the attributes names -> {@link AttributeMetaData} - * corresponding to the product type should be fetched from the CTP project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of its - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link ProductType} was found in the CTP project with this key. - */ - @Nonnull - CompletionStage>> fetchCachedProductAttributeMetaDataMap( - @Nonnull String productTypeId); + /** + * TODO FIX JAVADOC AND TEST METHOD Given a {@code productType}, this method first checks if a + * cached map of ProductType ids -> map of {@link AttributeMetaData} is not empty. If not, it + * returns a completed future that contains an optional that contains what this productType id + * maps to in the cache. If the cache is empty, the method populates the cache with the mapping of + * all ProductType ids to maps of each product type's attributes' {@link AttributeMetaData} in the + * CTP project, by querying the CTP project for all ProductTypes. + * + *

After that, the method returns a {@link CompletionStage}<{@link Optional}<{@link + * String}>> in which the result of it's completion could contain an {@link Optional} with a + * map of the attributes names -> {@link AttributeMetaData} inside of it or an empty {@link + * Optional} if no {@link ProductType} was found in the CTP project with this id. + * + * @param productTypeId the id by which a a map of the attributes names -> {@link + * AttributeMetaData} corresponding to the product type should be fetched from the CTP + * project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of its completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link ProductType} was found in the CTP project with this + * key. + */ + @Nonnull + CompletionStage>> fetchCachedProductAttributeMetaDataMap( + @Nonnull String productTypeId); - /** - * Given a {@link Set} of ProductType keys, this method fetches a set of all the ProductTypes, matching this given - * set of keys in the CTP project, defined in an injected {@link io.sphere.sdk.client.SphereClient}. A - * mapping of the key to the id of the fetched ProductType is persisted in an in-memory map. - * - * @param keys set of ProductType keys to fetch matching ProductTypes by. - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a {@link Set} - * of all matching ProductType. - */ - @Nonnull - CompletionStage> fetchMatchingProductTypesByKeys(@Nonnull Set keys); + /** + * Given a {@link Set} of ProductType keys, this method fetches a set of all the ProductTypes, + * matching this given set of keys in the CTP project, defined in an injected {@link + * io.sphere.sdk.client.SphereClient}. A mapping of the key to the id of the fetched ProductType + * is persisted in an in-memory map. + * + * @param keys set of ProductType keys to fetch matching ProductTypes by. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a {@link Set} of all matching ProductType. + */ + @Nonnull + CompletionStage> fetchMatchingProductTypesByKeys(@Nonnull Set keys); - /** - * Given a resource draft of type {@link ProductTypeDraft}, this method attempts to create a resource - * {@link ProductType} based on it in the CTP project defined by the sync options. - * - *

A completion stage containing an empty optional and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, then the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param productTypeDraft the resource draft to create a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> createProductType(@Nonnull ProductTypeDraft productTypeDraft); + /** + * Given a resource draft of type {@link ProductTypeDraft}, this method attempts to create a + * resource {@link ProductType} based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty optional and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, then the created + * resource's id and key are cached and the method returns a {@link CompletionStage} in which the + * result of it's completion contains an instance {@link Optional} of the resource which was + * created. + * + * @param productTypeDraft the resource draft to create a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createProductType( + @Nonnull ProductTypeDraft productTypeDraft); - /** - * Updates existing product type with {@code updateActions}. - * - * @param productType product type that should be updated - * @param updateActions {@link List} of actions that should be applied to {@code productType} - * @return {@link CompletionStage} with updated {@link ProductType}. - */ - @Nonnull - CompletionStage updateProductType(@Nonnull ProductType productType, - @Nonnull List> updateActions); + /** + * Updates existing product type with {@code updateActions}. + * + * @param productType product type that should be updated + * @param updateActions {@link List} of actions that should be applied to {@code productType} + * @return {@link CompletionStage} with updated {@link ProductType}. + */ + @Nonnull + CompletionStage updateProductType( + @Nonnull ProductType productType, @Nonnull List> updateActions); - /** - * Given a productType key, this method fetches a productType that matches this given key in the CTP project defined - * in an injected {@link SphereClient}. If there is no matching productType an empty {@link Optional} - * will be returned in the returned future. - * - * @param key the key of the product type to fetch. - * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion contains an - * {@link Optional} that contains the matching {@link ProductType} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchProductType(@Nullable String key); + /** + * Given a productType key, this method fetches a productType that matches this given key in the + * CTP project defined in an injected {@link SphereClient}. If there is no matching productType an + * empty {@link Optional} will be returned in the returned future. + * + * @param key the key of the product type to fetch. + * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion + * contains an {@link Optional} that contains the matching {@link ProductType} if exists, + * otherwise empty. + */ + @Nonnull + CompletionStage> fetchProductType(@Nullable String key); } diff --git a/src/main/java/com/commercetools/sync/services/ShoppingListService.java b/src/main/java/com/commercetools/sync/services/ShoppingListService.java index a5824f7e4f..eb78941783 100644 --- a/src/main/java/com/commercetools/sync/services/ShoppingListService.java +++ b/src/main/java/com/commercetools/sync/services/ShoppingListService.java @@ -4,94 +4,99 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.shoppinglists.ShoppingList; import io.sphere.sdk.shoppinglists.ShoppingListDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface ShoppingListService { - /** - * Filters out the keys which are already cached and fetches only the not-cached shopping list keys from the CTP - * project defined in an injected {@link SphereClient} and stores a mapping for every shopping list to id in - * the cached map of keys -> ids and returns this cached map. - * - *

Note: If all the supplied keys are already cached, the cached map is returned right away with no request to - * CTP. - * - * @param shoppingListKeys the shopping list keys to fetch and cache the ids for. - * - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of all - * shopping list keys -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull Set shoppingListKeys); - - /** - * Given a {@link Set} of shopping list keys, this method fetches a set of all the shopping lists, matching given - * set of keys in the CTP project, defined in an injected {@link SphereClient}. A mapping of the key to the id - * of the fetched shopping lists is persisted in an in-memory map. - * - * @param keys set of shopping list keys to fetch matching shopping lists by. - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a {@link Set} - * of all matching shopping lists. - */ - @Nonnull - CompletionStage> fetchMatchingShoppingListsByKeys(@Nonnull final Set keys); + /** + * Filters out the keys which are already cached and fetches only the not-cached shopping list + * keys from the CTP project defined in an injected {@link SphereClient} and stores a mapping for + * every shopping list to id in the cached map of keys -> ids and returns this cached map. + * + *

Note: If all the supplied keys are already cached, the cached map is returned right away + * with no request to CTP. + * + * @param shoppingListKeys the shopping list keys to fetch and cache the ids for. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of all shopping list keys -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull Set shoppingListKeys); - /** - * Given a shopping list key, this method fetches a shopping list that matches given key in the CTP project defined - * in a potentially injected {@link SphereClient}. If there is no matching shopping list, an empty {@link Optional} - * will be returned in the returned future. A mapping of the key to the id of the fetched shopping list is persisted - * in an in-memory map. - * - * @param key the key of the shopping list to fetch. - * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion contains an - * {@link Optional} that contains the matching {@link ShoppingList} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchShoppingList(@Nullable final String key); + /** + * Given a {@link Set} of shopping list keys, this method fetches a set of all the shopping lists, + * matching given set of keys in the CTP project, defined in an injected {@link SphereClient}. A + * mapping of the key to the id of the fetched shopping lists is persisted in an in-memory map. + * + * @param keys set of shopping list keys to fetch matching shopping lists by. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a {@link Set} of all matching shopping lists. + */ + @Nonnull + CompletionStage> fetchMatchingShoppingListsByKeys( + @Nonnull final Set keys); - /** - * Given a resource draft of type {@link ShoppingListDraft}, this method attempts to create a resource - * {@link ShoppingList} based on it in the CTP project defined by the sync options. - * - *

A completion stage containing an empty option and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, then the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param shoppingListDraft the resource draft to create a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> createShoppingList(@Nonnull final ShoppingListDraft shoppingListDraft); + /** + * Given a shopping list key, this method fetches a shopping list that matches given key in the + * CTP project defined in a potentially injected {@link SphereClient}. If there is no matching + * shopping list, an empty {@link Optional} will be returned in the returned future. A mapping of + * the key to the id of the fetched shopping list is persisted in an in-memory map. + * + * @param key the key of the shopping list to fetch. + * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion + * contains an {@link Optional} that contains the matching {@link ShoppingList} if exists, + * otherwise empty. + */ + @Nonnull + CompletionStage> fetchShoppingList(@Nullable final String key); - /** - * Given a {@link ShoppingList} and a {@link List}<{@link UpdateAction}<{@link ShoppingList}>>, this - * method issues an update request with these update actions on this {@link ShoppingList} in the CTP project defined - * in a potentially injected {@link SphereClient}. This method returns {@link CompletionStage}< - * {@link ShoppingList}> in which the result of it's completion contains an instance of - * the {@link ShoppingList} which was updated in the CTP project. - * - * @param shoppingList the {@link ShoppingList} to update. - * @param updateActions the update actions to update the {@link ShoppingList} with. - * @return {@link CompletionStage}<{@link ShoppingList}> containing as a result of it's completion an instance - * of the {@link ShoppingList} which was updated in the CTP project or a - * {@link io.sphere.sdk.models.SphereException}. - */ - @Nonnull - CompletionStage updateShoppingList(@Nonnull final ShoppingList shoppingList, - @Nonnull final List> updateActions); + /** + * Given a resource draft of type {@link ShoppingListDraft}, this method attempts to create a + * resource {@link ShoppingList} based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty option and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, then the created + * resource's id and key are cached and the method returns a {@link CompletionStage} in which the + * result of it's completion contains an instance {@link Optional} of the resource which was + * created. + * + * @param shoppingListDraft the resource draft to create a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createShoppingList( + @Nonnull final ShoppingListDraft shoppingListDraft); + /** + * Given a {@link ShoppingList} and a {@link List}<{@link UpdateAction}<{@link + * ShoppingList}>>, this method issues an update request with these update actions on this + * {@link ShoppingList} in the CTP project defined in a potentially injected {@link SphereClient}. + * This method returns {@link CompletionStage}< {@link ShoppingList}> in which the result of + * it's completion contains an instance of the {@link ShoppingList} which was updated in the CTP + * project. + * + * @param shoppingList the {@link ShoppingList} to update. + * @param updateActions the update actions to update the {@link ShoppingList} with. + * @return {@link CompletionStage}<{@link ShoppingList}> containing as a result of it's + * completion an instance of the {@link ShoppingList} which was updated in the CTP project or + * a {@link io.sphere.sdk.models.SphereException}. + */ + @Nonnull + CompletionStage updateShoppingList( + @Nonnull final ShoppingList shoppingList, + @Nonnull final List> updateActions); } diff --git a/src/main/java/com/commercetools/sync/services/StateService.java b/src/main/java/com/commercetools/sync/services/StateService.java index 942cc69a89..52e99c0c99 100644 --- a/src/main/java/com/commercetools/sync/services/StateService.java +++ b/src/main/java/com/commercetools/sync/services/StateService.java @@ -4,126 +4,129 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface StateService { - /** - * Filters out the keys which are already cached and fetches only the not-cached state keys from the CTP - * project defined in an injected {@link SphereClient} and stores a mapping for every state to id in - * the cached map of keys -> ids and returns this cached map. - * - *

Note: If all the supplied keys are already cached, the cached map is returned right away with no request to - * CTP. - * - * @param keys the state keys to fetch and cache the ids for. - * - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of all - * state keys -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull Set keys); - - /** - * Given a {@code key}, this method first checks if a cached map of state keys -> ids is not empty. - * If not, it returns a completed future that contains an optional that contains what this key maps to in - * the cache. If the cache is empty, the method populates the cache with the mapping of all state keys to ids - * in the CTP project, by querying the CTP project for all states. - * - *

After that, the method returns a {@link CompletionStage}<{@link Optional}<{@link String}>> - * in which the result of it's completion could contain an - * {@link Optional} with the id inside of it or an empty {@link Optional} if no {@link State} was - * found in the CTP project with this key. - * - * @param key the key by which a {@link State} id should be fetched from the CTP - * project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of its - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link State} was found in the CTP project with this key. - */ - @Nonnull - CompletionStage> fetchCachedStateId(@Nullable final String key); + /** + * Filters out the keys which are already cached and fetches only the not-cached state keys from + * the CTP project defined in an injected {@link SphereClient} and stores a mapping for every + * state to id in the cached map of keys -> ids and returns this cached map. + * + *

Note: If all the supplied keys are already cached, the cached map is returned right away + * with no request to CTP. + * + * @param keys the state keys to fetch and cache the ids for. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of all state keys -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull Set keys); - /** - * Given a {@link Set} of state keys, this method fetches a set of all the states, matching given set of - * keys in the CTP project, defined in an injected {@link SphereClient}. A mapping of the key to the id - * of the fetched states is persisted in an in-memory map. - * - * @param stateKeys set of state keys to fetch matching states by. - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a {@link Set} - * of all matching states. - */ - @Nonnull - CompletionStage> fetchMatchingStatesByKeys(@Nonnull final Set stateKeys); + /** + * Given a {@code key}, this method first checks if a cached map of state keys -> ids is not + * empty. If not, it returns a completed future that contains an optional that contains what this + * key maps to in the cache. If the cache is empty, the method populates the cache with the + * mapping of all state keys to ids in the CTP project, by querying the CTP project for all + * states. + * + *

After that, the method returns a {@link CompletionStage}<{@link Optional}<{@link + * String}>> in which the result of it's completion could contain an {@link Optional} with + * the id inside of it or an empty {@link Optional} if no {@link State} was found in the CTP + * project with this key. + * + * @param key the key by which a {@link State} id should be fetched from the CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of its completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link State} was found in the CTP project with this key. + */ + @Nonnull + CompletionStage> fetchCachedStateId(@Nullable final String key); - /** - * Given a {@link Set} of state keys, this method fetches a set of all the states with expanded transitions, - * matching given set of keys in the CTP project, defined in an injected {@link SphereClient}. A mapping - * of the key to the id of the fetched states is persisted in an in-memory map. - * - * @param stateKeys set of state keys to fetch matching states by. - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a {@link Set} - * of all matching states with expanded transitions. - */ - @Nonnull - CompletionStage> fetchMatchingStatesByKeysWithTransitions(@Nonnull final Set stateKeys); + /** + * Given a {@link Set} of state keys, this method fetches a set of all the states, matching given + * set of keys in the CTP project, defined in an injected {@link SphereClient}. A mapping of the + * key to the id of the fetched states is persisted in an in-memory map. + * + * @param stateKeys set of state keys to fetch matching states by. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a {@link Set} of all matching states. + */ + @Nonnull + CompletionStage> fetchMatchingStatesByKeys(@Nonnull final Set stateKeys); - /** - * Given a state key, this method fetches a state that matches given key in the CTP project defined in a - * potentially injected {@link SphereClient}. If there is no matching state an empty {@link Optional} will be - * returned in the returned future. A mapping of the key to the id of the fetched state is persisted in an in - * -memory map. - * - * @param key the key of the state to fetch. - * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion contains an - * {@link Optional} that contains the matching {@link State} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchState(@Nullable final String key); + /** + * Given a {@link Set} of state keys, this method fetches a set of all the states with expanded + * transitions, matching given set of keys in the CTP project, defined in an injected {@link + * SphereClient}. A mapping of the key to the id of the fetched states is persisted in an + * in-memory map. + * + * @param stateKeys set of state keys to fetch matching states by. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a {@link Set} of all matching states with expanded transitions. + */ + @Nonnull + CompletionStage> fetchMatchingStatesByKeysWithTransitions( + @Nonnull final Set stateKeys); - /** - * Given a resource draft of type {@link StateDraft}, this method attempts to create a resource - * {@link State} based on it in the CTP project defined by the sync options. - * - *

A completion stage containing an empty option and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, then the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param stateDraft the resource draft to create a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> createState(@Nonnull final StateDraft stateDraft); + /** + * Given a state key, this method fetches a state that matches given key in the CTP project + * defined in a potentially injected {@link SphereClient}. If there is no matching state an empty + * {@link Optional} will be returned in the returned future. A mapping of the key to the id of the + * fetched state is persisted in an in -memory map. + * + * @param key the key of the state to fetch. + * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion + * contains an {@link Optional} that contains the matching {@link State} if exists, otherwise + * empty. + */ + @Nonnull + CompletionStage> fetchState(@Nullable final String key); - /** - * Given a {@link State} and a {@link List}<{@link UpdateAction}<{@link State}>>, this method - * issues an update request with these update actions on this {@link State} in the CTP project defined in a - * potentially injected {@link io.sphere.sdk.client.SphereClient}. This method returns - * {@link CompletionStage}<{@link State}> in which the result of it's completion contains an instance of - * the {@link State} which was updated in the CTP project. - * - * @param state the {@link State} to update. - * @param updateActions the update actions to update the {@link State} with. - * @return {@link CompletionStage}<{@link State}> containing as a result of it's completion an instance of - * the {@link State} which was updated in the CTP project or a - * {@link io.sphere.sdk.models.SphereException}. - */ - @Nonnull - CompletionStage updateState(@Nonnull final State state, - @Nonnull final List> updateActions); + /** + * Given a resource draft of type {@link StateDraft}, this method attempts to create a resource + * {@link State} based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty option and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, then the created + * resource's id and key are cached and the method returns a {@link CompletionStage} in which the + * result of it's completion contains an instance {@link Optional} of the resource which was + * created. + * + * @param stateDraft the resource draft to create a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createState(@Nonnull final StateDraft stateDraft); + /** + * Given a {@link State} and a {@link List}<{@link UpdateAction}<{@link State}>>, this + * method issues an update request with these update actions on this {@link State} in the CTP + * project defined in a potentially injected {@link io.sphere.sdk.client.SphereClient}. This + * method returns {@link CompletionStage}<{@link State}> in which the result of it's + * completion contains an instance of the {@link State} which was updated in the CTP project. + * + * @param state the {@link State} to update. + * @param updateActions the update actions to update the {@link State} with. + * @return {@link CompletionStage}<{@link State}> containing as a result of it's completion + * an instance of the {@link State} which was updated in the CTP project or a {@link + * io.sphere.sdk.models.SphereException}. + */ + @Nonnull + CompletionStage updateState( + @Nonnull final State state, @Nonnull final List> updateActions); } diff --git a/src/main/java/com/commercetools/sync/services/TaxCategoryService.java b/src/main/java/com/commercetools/sync/services/TaxCategoryService.java index a5faa4aa55..968b855d4c 100644 --- a/src/main/java/com/commercetools/sync/services/TaxCategoryService.java +++ b/src/main/java/com/commercetools/sync/services/TaxCategoryService.java @@ -4,112 +4,120 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.taxcategories.TaxCategoryDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface TaxCategoryService { - /** - * Filters out the keys which are already cached and fetches only the not-cached tax category keys from the - * CTP project defined in an injected {@link SphereClient} and stores a mapping for every tax category to id - * in the cached map of keys -> ids and returns this cached map. - * - * @param taxCategoryKeys - a set of tax category keys to fetch and cache the ids for - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of - * requested tax category keys -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull final Set taxCategoryKeys); - - /** - * Given a {@code key}, this method first checks if a cached map of TaxCategory keys -> ids is not empty. - * If not, it returns a completed future that contains an optional that contains what this key maps to in - * the cache. If the cache is empty, the method populates the cache with the mapping of all TaxCategory keys to ids - * in the CTP project, by querying the CTP project for all Tax categories. - * - *

After that, the method returns a {@link CompletionStage}<{@link Optional}<{@link String}>> - * in which the result of it's completion could contain an - * {@link Optional} with the id inside of it or an empty {@link Optional} if no {@link TaxCategory} was - * found in the CTP project with this key. - * - * @param key the key by which a {@link TaxCategory} id should be fetched from the CTP - * project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of its - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link TaxCategory} was found in the CTP project with this key. - */ - @Nonnull - CompletionStage> fetchCachedTaxCategoryId(@Nullable final String key); + /** + * Filters out the keys which are already cached and fetches only the not-cached tax category keys + * from the CTP project defined in an injected {@link SphereClient} and stores a mapping for every + * tax category to id in the cached map of keys -> ids and returns this cached map. + * + * @param taxCategoryKeys - a set of tax category keys to fetch and cache the ids for + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of requested tax category keys -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull final Set taxCategoryKeys); - /** - * Given a {@link Set} of tax category keys, this method fetches a set of all the taxCategories, matching - * given set of keys in the CTP project, defined in an injected {@link SphereClient}. A mapping of the key to - * the id of the fetched taxCategories is persisted in an in-memory map. - *
- * One must remember key is not required to create TaxCategory but is required to synchronize tax categories. - * - * @param keys set of tax category keys to fetch matching taxCategories by. - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a {@link Set} - * of all matching taxCategories. - */ - @Nonnull - CompletionStage> fetchMatchingTaxCategoriesByKeys(@Nonnull final Set keys); + /** + * Given a {@code key}, this method first checks if a cached map of TaxCategory keys -> ids is + * not empty. If not, it returns a completed future that contains an optional that contains what + * this key maps to in the cache. If the cache is empty, the method populates the cache with the + * mapping of all TaxCategory keys to ids in the CTP project, by querying the CTP project for all + * Tax categories. + * + *

After that, the method returns a {@link CompletionStage}<{@link Optional}<{@link + * String}>> in which the result of it's completion could contain an {@link Optional} with + * the id inside of it or an empty {@link Optional} if no {@link TaxCategory} was found in the CTP + * project with this key. + * + * @param key the key by which a {@link TaxCategory} id should be fetched from the CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of its completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link TaxCategory} was found in the CTP project with this + * key. + */ + @Nonnull + CompletionStage> fetchCachedTaxCategoryId(@Nullable final String key); - /** - * Given a tax category key, this method fetches a tax category that matches given key in the CTP project - * defined in a potentially injected {@link SphereClient}. If there is no matching tax category an empty - * {@link Optional} will be returned in the returned future. A mapping of the key to the id of the fetched category - * is persisted in an in-memory map. - * - * @param key the key of the tax category to fetch. - * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion contains an - * {@link Optional} that contains the matching {@link TaxCategory} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchTaxCategory(@Nullable final String key); + /** + * Given a {@link Set} of tax category keys, this method fetches a set of all the taxCategories, + * matching given set of keys in the CTP project, defined in an injected {@link SphereClient}. A + * mapping of the key to the id of the fetched taxCategories is persisted in an in-memory map. + *
+ * One must remember key is not required to create TaxCategory but is required to synchronize tax + * categories. + * + * @param keys set of tax category keys to fetch matching taxCategories by. + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a {@link Set} of all matching taxCategories. + */ + @Nonnull + CompletionStage> fetchMatchingTaxCategoriesByKeys( + @Nonnull final Set keys); - /** - * Given a resource draft of {@link TaxCategoryDraft}, this method attempts to create a resource - * {@link TaxCategory} based on it in the CTP project defined by the sync options. - * - *

A completion stage containing an empty option and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, then the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param taxCategoryDraft the resource draft to create a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> createTaxCategory(@Nonnull final TaxCategoryDraft taxCategoryDraft); + /** + * Given a tax category key, this method fetches a tax category that matches given key in the CTP + * project defined in a potentially injected {@link SphereClient}. If there is no matching tax + * category an empty {@link Optional} will be returned in the returned future. A mapping of the + * key to the id of the fetched category is persisted in an in-memory map. + * + * @param key the key of the tax category to fetch. + * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion + * contains an {@link Optional} that contains the matching {@link TaxCategory} if exists, + * otherwise empty. + */ + @Nonnull + CompletionStage> fetchTaxCategory(@Nullable final String key); - /** - * Given a {@link TaxCategory} and a {@link List}<{@link UpdateAction}<{@link TaxCategory}>>, this - * method issues an update request with these update actions on this {@link TaxCategory} in the CTP project defined - * in a potentially injected {@link io.sphere.sdk.client.SphereClient}. This method returns - * {@link CompletionStage}<{@link TaxCategory}> in which the result of it's completion contains an instance - * of the {@link TaxCategory} which was updated in the CTP project. - * - * @param taxCategory the {@link TaxCategory} to update. - * @param updateActions the update actions to update the {@link TaxCategory} with. - * @return {@link CompletionStage}<{@link TaxCategory}> containing as a result of it's completion an instance - * of the {@link TaxCategory} which was updated in the CTP project or a - * {@link io.sphere.sdk.models.SphereException}. - */ - @Nonnull - CompletionStage updateTaxCategory(@Nonnull final TaxCategory taxCategory, - @Nonnull final List> updateActions); + /** + * Given a resource draft of {@link TaxCategoryDraft}, this method attempts to create a resource + * {@link TaxCategory} based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty option and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, then the created + * resource's id and key are cached and the method returns a {@link CompletionStage} in which the + * result of it's completion contains an instance {@link Optional} of the resource which was + * created. + * + * @param taxCategoryDraft the resource draft to create a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createTaxCategory( + @Nonnull final TaxCategoryDraft taxCategoryDraft); + /** + * Given a {@link TaxCategory} and a {@link List}<{@link UpdateAction}<{@link + * TaxCategory}>>, this method issues an update request with these update actions on this + * {@link TaxCategory} in the CTP project defined in a potentially injected {@link + * io.sphere.sdk.client.SphereClient}. This method returns {@link CompletionStage}<{@link + * TaxCategory}> in which the result of it's completion contains an instance of the {@link + * TaxCategory} which was updated in the CTP project. + * + * @param taxCategory the {@link TaxCategory} to update. + * @param updateActions the update actions to update the {@link TaxCategory} with. + * @return {@link CompletionStage}<{@link TaxCategory}> containing as a result of it's + * completion an instance of the {@link TaxCategory} which was updated in the CTP project or a + * {@link io.sphere.sdk.models.SphereException}. + */ + @Nonnull + CompletionStage updateTaxCategory( + @Nonnull final TaxCategory taxCategory, + @Nonnull final List> updateActions); } diff --git a/src/main/java/com/commercetools/sync/services/TypeService.java b/src/main/java/com/commercetools/sync/services/TypeService.java index 796bd52e57..c0f3fe7371 100644 --- a/src/main/java/com/commercetools/sync/services/TypeService.java +++ b/src/main/java/com/commercetools/sync/services/TypeService.java @@ -4,104 +4,110 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.TypeDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public interface TypeService { - /** - * Filters out the keys which are already cached and fetches only the not-cached type keys from the CTP project - * defined in an injected {@link SphereClient} and stores a mapping for every type to id in the cached map of - * keys -> ids and returns this cached map. - * - * @param typeKeys - a set type keys to fetch and cache the ids for - * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion contains a map of - * requested type keys -> ids - */ - @Nonnull - CompletionStage> cacheKeysToIds(@Nonnull final Set typeKeys); + /** + * Filters out the keys which are already cached and fetches only the not-cached type keys from + * the CTP project defined in an injected {@link SphereClient} and stores a mapping for every type + * to id in the cached map of keys -> ids and returns this cached map. + * + * @param typeKeys - a set type keys to fetch and cache the ids for + * @return {@link CompletionStage}<{@link Map}> in which the result of it's completion + * contains a map of requested type keys -> ids + */ + @Nonnull + CompletionStage> cacheKeysToIds(@Nonnull final Set typeKeys); - /** - * Given a {@code key}, this method first checks if a cached map of Type keys -> ids contains the key. - * If not, it returns a completed future that contains an {@link Optional} that contains what this key maps to in - * the cache. If the cache doesn't contain the key; this method attempts to fetch the id of the key from the CTP - * project, caches it and returns a {@link CompletionStage}<{@link Optional}<{@link String}>> - * in which the result of it's completion could contain an - * {@link Optional} with the id inside of it or an empty {@link Optional} if no {@link Type} was - * found in the CTP project with this key. - * - * @param key the key by which a {@link io.sphere.sdk.types.Type} id should be fetched from the CTP project. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of its - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * {@link Type} was found in the CTP project with this key. - */ - @Nonnull - CompletionStage> fetchCachedTypeId(@Nonnull String key); + /** + * Given a {@code key}, this method first checks if a cached map of Type keys -> ids contains + * the key. If not, it returns a completed future that contains an {@link Optional} that contains + * what this key maps to in the cache. If the cache doesn't contain the key; this method attempts + * to fetch the id of the key from the CTP project, caches it and returns a {@link + * CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's + * completion could contain an {@link Optional} with the id inside of it or an empty {@link + * Optional} if no {@link Type} was found in the CTP project with this key. + * + * @param key the key by which a {@link io.sphere.sdk.types.Type} id should be fetched from the + * CTP project. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of its completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no {@link Type} was found in the CTP project with this key. + */ + @Nonnull + CompletionStage> fetchCachedTypeId(@Nonnull String key); - /** - * Given a {@link Set} of Type keys, this method fetches a set of all the Types, matching this given - * set of keys in the CTP project, defined in an injected {@link io.sphere.sdk.client.SphereClient}. A - * mapping of the key to the id of the fetched Type is persisted in an in-memory map. - * - * @param keys set of Type keys to fetch matching Type by. - * @return {@link CompletionStage}<{@link Map}> in which the result of its completion contains a {@link Set} - * of all matching Types. - */ - @Nonnull - CompletionStage> fetchMatchingTypesByKeys(@Nonnull Set keys); + /** + * Given a {@link Set} of Type keys, this method fetches a set of all the Types, matching this + * given set of keys in the CTP project, defined in an injected {@link + * io.sphere.sdk.client.SphereClient}. A mapping of the key to the id of the fetched Type is + * persisted in an in-memory map. + * + * @param keys set of Type keys to fetch matching Type by. + * @return {@link CompletionStage}<{@link Map}> in which the result of its completion + * contains a {@link Set} of all matching Types. + */ + @Nonnull + CompletionStage> fetchMatchingTypesByKeys(@Nonnull Set keys); - /** - * Given a type key, this method fetches a type that matches this given key in the CTP project defined in an - * injected {@link SphereClient}. If there is no matching type an empty {@link Optional} will be - * returned in the returned future. - * - * @param key the key of the type to fetch. - * @return {@link CompletionStage}<{@link Optional}> in which the result of its completion contains an - * {@link Optional} that contains the matching {@link Type} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchType(@Nullable String key); + /** + * Given a type key, this method fetches a type that matches this given key in the CTP project + * defined in an injected {@link SphereClient}. If there is no matching type an empty {@link + * Optional} will be returned in the returned future. + * + * @param key the key of the type to fetch. + * @return {@link CompletionStage}<{@link Optional}> in which the result of its completion + * contains an {@link Optional} that contains the matching {@link Type} if exists, otherwise + * empty. + */ + @Nonnull + CompletionStage> fetchType(@Nullable String key); - /** - * Given a resource draft of type {@link TypeDraft}, this method attempts to create a resource - * {@link Type} based on it in the CTP project defined by the sync options. - * - *

A completion stage containing an empty optional and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, then the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of its completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param typeDraft the resource draft to create a resource based off of. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> createType(@Nonnull TypeDraft typeDraft); + /** + * Given a resource draft of type {@link TypeDraft}, this method attempts to create a resource + * {@link Type} based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty optional and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, then the created + * resource's id and key are cached and the method returns a {@link CompletionStage} in which the + * result of its completion contains an instance {@link Optional} of the resource which was + * created. + * + * @param typeDraft the resource draft to create a resource based off of. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createType(@Nonnull TypeDraft typeDraft); - /** - * Given a {@link Type} and a {@link List}<{@link UpdateAction}<{@link Type}>>, this method - * issues an update request with these update actions on this {@link Type} in the CTP project defined in an - * injected {@link io.sphere.sdk.client.SphereClient}. This method returns - * {@link CompletionStage}<{@link Type}> in which the result of its completion contains an instance of - * the {@link Type} which was updated in the CTP project. - * - * @param type the {@link Type} to update. - * @param updateActions the update actions to update the {@link Type} with. - * @return {@link CompletionStage}<{@link Type}> containing as a result of it's completion an instance of - * the {@link Type} which was updated in the CTP project or a {@link io.sphere.sdk.models.SphereException}. - */ - @Nonnull - CompletionStage updateType(@Nonnull Type type, - @Nonnull List> updateActions); + /** + * Given a {@link Type} and a {@link List}<{@link UpdateAction}<{@link Type}>>, this + * method issues an update request with these update actions on this {@link Type} in the CTP + * project defined in an injected {@link io.sphere.sdk.client.SphereClient}. This method returns + * {@link CompletionStage}<{@link Type}> in which the result of its completion contains an + * instance of the {@link Type} which was updated in the CTP project. + * + * @param type the {@link Type} to update. + * @param updateActions the update actions to update the {@link Type} with. + * @return {@link CompletionStage}<{@link Type}> containing as a result of it's completion + * an instance of the {@link Type} which was updated in the CTP project or a {@link + * io.sphere.sdk.models.SphereException}. + */ + @Nonnull + CompletionStage updateType( + @Nonnull Type type, @Nonnull List> updateActions); } diff --git a/src/main/java/com/commercetools/sync/services/UnresolvedReferencesService.java b/src/main/java/com/commercetools/sync/services/UnresolvedReferencesService.java index 8afa61fb6e..9732ca15fc 100644 --- a/src/main/java/com/commercetools/sync/services/UnresolvedReferencesService.java +++ b/src/main/java/com/commercetools/sync/services/UnresolvedReferencesService.java @@ -1,43 +1,41 @@ package com.commercetools.sync.services; - import com.commercetools.sync.commons.models.WaitingToBeResolved; - -import javax.annotation.Nonnull; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; public interface UnresolvedReferencesService { - /** - * Given product draft keys, this method fetches the persisted drafts waiting to be resolved. If there is no - * matching draft, an empty {@link Set} will be returned in the returned future. - * - * @param keys the keys of the persisted product drafts, waiting to be resolved, to fetch. - * @return {@link CompletionStage}<{@link Set}> in which the result of its completion contains a - * {@link Set} that contains the matching drafts if any exist, otherwise empty. - */ - @Nonnull - CompletionStage> fetch(@Nonnull final Set keys); + /** + * Given product draft keys, this method fetches the persisted drafts waiting to be resolved. If + * there is no matching draft, an empty {@link Set} will be returned in the returned future. + * + * @param keys the keys of the persisted product drafts, waiting to be resolved, to fetch. + * @return {@link CompletionStage}<{@link Set}> in which the result of its completion + * contains a {@link Set} that contains the matching drafts if any exist, otherwise empty. + */ + @Nonnull + CompletionStage> fetch(@Nonnull final Set keys); - /** - * Persists a product draft that is not ready to be resolved yet. - * - * @param draft the draft that should be persisted. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> save(@Nonnull final WaitingToBeResolved draft); + /** + * Persists a product draft that is not ready to be resolved yet. + * + * @param draft the draft that should be persisted. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> save(@Nonnull final WaitingToBeResolved draft); - /** - * Given a product draft key, this methods deletes the matching product draft from persistence. - * - * @param key the key of the product draft to delete from persistence. - * @return a {@link CompletionStage} containing an optional with the deleted resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> delete(@Nonnull final String key); -} \ No newline at end of file + /** + * Given a product draft key, this methods deletes the matching product draft from persistence. + * + * @param key the key of the product draft to delete from persistence. + * @return a {@link CompletionStage} containing an optional with the deleted resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> delete(@Nonnull final String key); +} diff --git a/src/main/java/com/commercetools/sync/services/UnresolvedTransitionsService.java b/src/main/java/com/commercetools/sync/services/UnresolvedTransitionsService.java index 82f73a4c04..c36f2002ef 100644 --- a/src/main/java/com/commercetools/sync/services/UnresolvedTransitionsService.java +++ b/src/main/java/com/commercetools/sync/services/UnresolvedTransitionsService.java @@ -1,43 +1,42 @@ package com.commercetools.sync.services; - import com.commercetools.sync.commons.models.WaitingToBeResolvedTransitions; - -import javax.annotation.Nonnull; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; public interface UnresolvedTransitionsService { - /** - * Given state draft keys, this method fetches the persisted drafts waiting to be resolved. If there is no - * matching draft, an empty {@link Set} will be returned in the returned future. - * - * @param keys the keys of the persisted state drafts, waiting to be resolved, to fetch. - * @return {@link CompletionStage}<{@link Set}> in which the result of its completion contains a - * {@link Set} that contains the matching drafts if any exist, otherwise empty. - */ - @Nonnull - CompletionStage> fetch(@Nonnull final Set keys); + /** + * Given state draft keys, this method fetches the persisted drafts waiting to be resolved. If + * there is no matching draft, an empty {@link Set} will be returned in the returned future. + * + * @param keys the keys of the persisted state drafts, waiting to be resolved, to fetch. + * @return {@link CompletionStage}<{@link Set}> in which the result of its completion + * contains a {@link Set} that contains the matching drafts if any exist, otherwise empty. + */ + @Nonnull + CompletionStage> fetch(@Nonnull final Set keys); - /** - * Persists a state draft that is not ready to be resolved yet. - * - * @param draft the draft that should be persisted. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> save(@Nonnull final WaitingToBeResolvedTransitions draft); + /** + * Persists a state draft that is not ready to be resolved yet. + * + * @param draft the draft that should be persisted. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> save( + @Nonnull final WaitingToBeResolvedTransitions draft); - /** - * Given a state draft key, this methods deletes the matching state draft from persistence. - * - * @param key the key of the state draft to delete from persistence. - * @return a {@link CompletionStage} containing an optional with the deleted resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> delete(@Nonnull final String key); + /** + * Given a state draft key, this methods deletes the matching state draft from persistence. + * + * @param key the key of the state draft to delete from persistence. + * @return a {@link CompletionStage} containing an optional with the deleted resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> delete(@Nonnull final String key); } diff --git a/src/main/java/com/commercetools/sync/services/impl/BaseService.java b/src/main/java/com/commercetools/sync/services/impl/BaseService.java index 602449b30d..b6dec1f1fe 100644 --- a/src/main/java/com/commercetools/sync/services/impl/BaseService.java +++ b/src/main/java/com/commercetools/sync/services/impl/BaseService.java @@ -1,5 +1,9 @@ package com.commercetools.sync.services.impl; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; @@ -13,10 +17,6 @@ import io.sphere.sdk.commands.UpdateCommand; import io.sphere.sdk.models.ResourceView; import io.sphere.sdk.queries.MetaModelQueryDsl; -import org.apache.commons.lang3.StringUtils; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.Map; @@ -29,332 +29,363 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; /** - * @param Resource Draft (e.g. {@link io.sphere.sdk.products.ProductDraft}, - * {@link io.sphere.sdk.categories.CategoryDraft}, etc.. - * @param Resource (e.g. {@link io.sphere.sdk.products.Product}, {@link io.sphere.sdk.categories.Category}, etc.. + * @param Resource Draft (e.g. {@link io.sphere.sdk.products.ProductDraft}, {@link + * io.sphere.sdk.categories.CategoryDraft}, etc.. + * @param Resource (e.g. {@link io.sphere.sdk.products.Product}, {@link + * io.sphere.sdk.categories.Category}, etc.. * @param Subclass of {@link BaseSyncOptions} - * @param Query (e.g. {@link io.sphere.sdk.products.queries.ProductQuery}, - * {@link io.sphere.sdk.categories.queries.CategoryQuery}, etc.. ) - * @param Query Model (e.g. {@link io.sphere.sdk.products.queries.ProductQueryModel}, - * {@link io.sphere.sdk.categories.queries.CategoryQueryModel}, etc.. + * @param Query (e.g. {@link io.sphere.sdk.products.queries.ProductQuery}, {@link + * io.sphere.sdk.categories.queries.CategoryQuery}, etc.. ) + * @param Query Model (e.g. {@link io.sphere.sdk.products.queries.ProductQueryModel}, {@link + * io.sphere.sdk.categories.queries.CategoryQueryModel}, etc.. * @param Expansion Model (e.g. {@link io.sphere.sdk.products.expansion.ProductExpansionModel}, - * {@link io.sphere.sdk.categories.expansion.CategoryExpansionModel}, etc.. + * {@link io.sphere.sdk.categories.expansion.CategoryExpansionModel}, etc.. */ -abstract class BaseService, S extends BaseSyncOptions, - Q extends MetaModelQueryDsl, M, E> { - - final S syncOptions; - protected final Cache keyToIdCache; - - private static final int MAXIMUM_ALLOWED_UPDATE_ACTIONS = 500; - static final String CREATE_FAILED = "Failed to create draft with key: '%s'. Reason: %s"; - - BaseService(@Nonnull final S syncOptions) { - this.syncOptions = syncOptions; - this.keyToIdCache = Caffeine.newBuilder() - .maximumSize(syncOptions.getCacheSize()) - .executor(Runnable::run) - .build(); +abstract class BaseService< + T, + U extends ResourceView, + S extends BaseSyncOptions, + Q extends MetaModelQueryDsl, + M, + E> { + + final S syncOptions; + protected final Cache keyToIdCache; + + private static final int MAXIMUM_ALLOWED_UPDATE_ACTIONS = 500; + static final String CREATE_FAILED = "Failed to create draft with key: '%s'. Reason: %s"; + + BaseService(@Nonnull final S syncOptions) { + this.syncOptions = syncOptions; + this.keyToIdCache = + Caffeine.newBuilder() + .maximumSize(syncOptions.getCacheSize()) + .executor(Runnable::run) + .build(); + } + + /** + * Executes update request(s) on the {@code resource} with all the {@code updateActions} using the + * {@code updateCommandFunction} while taking care of the CTP constraint of 500 update actions per + * request by batching update actions into requests of 500 actions each. + * + * @param resource The resource to update. + * @param updateCommandFunction a {@link BiFunction} used to compute the update command required + * to update the resource. + * @param updateActions the update actions to execute on the resource. + * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an + * instance of the resource {@link U} after all the update actions have been executed. + */ + @Nonnull + CompletionStage updateResource( + @Nonnull final U resource, + @Nonnull + final BiFunction>, UpdateCommand> + updateCommandFunction, + @Nonnull final List> updateActions) { + + final List>> actionBatches = + batchElements(updateActions, MAXIMUM_ALLOWED_UPDATE_ACTIONS); + return updateBatches( + CompletableFuture.completedFuture(resource), updateCommandFunction, actionBatches); + } + + /** + * Given a list of update actions batches represented by a {@link List}<{@link List}> of + * {@link UpdateAction}, this method executes the update command, computed by {@code + * updateCommandFunction}, on each batch. + * + * @param result in the first call of this method, this result is normally a completed future + * containing the resource to update, it is then used within each iteration of batch execution + * to have the latest resource (with version) once the previous batch has finished execution. + * @param updateCommandFunction a {@link BiFunction} used to compute the update command required + * to update the resource. + * @param batches the batches of update actions to execute. + * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an + * instance of the resource {@link U} after all the update actions in all batches have been + * executed. + */ + @Nonnull + private CompletionStage updateBatches( + @Nonnull final CompletionStage result, + @Nonnull + final BiFunction>, UpdateCommand> + updateCommandFunction, + @Nonnull final List>> batches) { + + CompletionStage resultStage = result; + for (final List> batch : batches) { + resultStage = + resultStage.thenCompose( + updatedProduct -> + syncOptions + .getCtpClient() + .execute(updateCommandFunction.apply(updatedProduct, batch))); } - - /** - * Executes update request(s) on the {@code resource} with all the {@code updateActions} using the - * {@code updateCommandFunction} while taking care of the CTP constraint of 500 update actions per request by - * batching update actions into requests of 500 actions each. - * - * @param resource The resource to update. - * @param updateCommandFunction a {@link BiFunction} used to compute the update command required to update the - * resource. - * @param updateActions the update actions to execute on the resource. - * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an instance of - * the resource {@link U} after all the update actions have been executed. - */ - @Nonnull - CompletionStage updateResource( - @Nonnull final U resource, - @Nonnull final BiFunction>, UpdateCommand> updateCommandFunction, - @Nonnull final List> updateActions) { - - final List>> actionBatches = batchElements(updateActions, MAXIMUM_ALLOWED_UPDATE_ACTIONS); - return updateBatches(CompletableFuture.completedFuture(resource), updateCommandFunction, actionBatches); + return resultStage; + } + + /** + * Given a resource draft of type {@code T}, this method attempts to create a resource {@code U} + * based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty option and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, then the created + * resource's id and key are cached and the method returns a {@link CompletionStage} in which the + * result of it's completion contains an instance {@link Optional} of the resource which was + * created. + * + * @param draft the resource draft to create a resource based off of. + * @param keyMapper a function to get the key from the supplied draft. + * @param createCommand a function to get the create command using the supplied draft. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @SuppressWarnings("unchecked") + @Nonnull + CompletionStage> createResource( + @Nonnull final T draft, + @Nonnull final Function keyMapper, + @Nonnull final Function> createCommand) { + + final String draftKey = keyMapper.apply(draft); + + if (isBlank(draftKey)) { + syncOptions.applyErrorCallback( + new SyncException(format(CREATE_FAILED, draftKey, "Draft key is blank!")), + null, + draft, + null); + return CompletableFuture.completedFuture(Optional.empty()); + } else { + return executeCreateCommand(draft, keyMapper, createCommand); } - - /** - * Given a list of update actions batches represented by a {@link List}<{@link List}> of {@link UpdateAction}, - * this method executes the update command, computed by {@code updateCommandFunction}, on each batch. - * - * @param result in the first call of this method, this result is normally a completed - * future containing the resource to update, it is then used within each iteration of - * batch execution to have the latest resource (with version) once the previous batch - * has finished execution. - * @param updateCommandFunction a {@link BiFunction} used to compute the update command required to update the - * resource. - * @param batches the batches of update actions to execute. - * @return an instance of {@link CompletionStage}<{@code U}> which contains as a result an instance of - * the resource {@link U} after all the update actions in all batches have been executed. - */ - @Nonnull - private CompletionStage updateBatches( - @Nonnull final CompletionStage result, - @Nonnull final BiFunction>, UpdateCommand> updateCommandFunction, - @Nonnull final List>> batches) { - - CompletionStage resultStage = result; - for (final List> batch : batches) { - resultStage = resultStage.thenCompose(updatedProduct -> - syncOptions.getCtpClient().execute(updateCommandFunction.apply(updatedProduct, batch))); - } - return resultStage; + } + + /** + * Given a {@code key}, if it is blank (null/empty), a completed future with an empty optional is + * returned. This method then checks if the cached map of resource keys -> ids contains the + * key. If it does, then an optional containing the mapped id is returned. If the cache doesn't + * contain the key; this method attempts to fetch the id of the key from the CTP project, caches + * it and returns a {@link CompletionStage}<{@link Optional}<{@link String}>> in which + * the result of it's completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no resource was found in the CTP project with this key. + * + * @param key the key by which a resource id should be fetched from the CTP project. + * @param keyMapper a function to get the key from the resource. + * @param querySupplier supplies the query to fetch the resource with the given key. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of it's completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no resource was found in the CTP project with this key. + */ + @Nonnull + CompletionStage> fetchCachedResourceId( + @Nullable final String key, + @Nonnull final Function keyMapper, + @Nonnull final Supplier querySupplier) { + + if (isBlank(key)) { + return CompletableFuture.completedFuture(Optional.empty()); } - /** - * Given a resource draft of type {@code T}, this method attempts to create a resource {@code U} based on it in - * the CTP project defined by the sync options. - * - *

A completion stage containing an empty option and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, then the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param draft the resource draft to create a resource based off of. - * @param keyMapper a function to get the key from the supplied draft. - * @param createCommand a function to get the create command using the supplied draft. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @SuppressWarnings("unchecked") - @Nonnull - CompletionStage> createResource( - @Nonnull final T draft, - @Nonnull final Function keyMapper, - @Nonnull final Function> createCommand) { - - final String draftKey = keyMapper.apply(draft); - - if (isBlank(draftKey)) { - syncOptions.applyErrorCallback( - new SyncException(format(CREATE_FAILED, draftKey, "Draft key is blank!")), - null, draft, null); - return CompletableFuture.completedFuture(Optional.empty()); - } else { - return executeCreateCommand(draft, keyMapper, createCommand); - } + final String id = keyToIdCache.getIfPresent(key); + if (id != null) { + return CompletableFuture.completedFuture(Optional.of(id)); } - - /** - * Given a {@code key}, if it is blank (null/empty), a completed future with an empty optional is returned. - * This method then checks if the cached map of resource keys -> ids contains the key. If it does, then an - * optional containing the mapped id is returned. If the cache doesn't contain the key; this method attempts to - * fetch the id of the key from the CTP project, caches it and returns a - * {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's completion - * could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no resource - * was found in the CTP project with this key. - * - * @param key the key by which a resource id should be fetched from the CTP project. - * @param keyMapper a function to get the key from the resource. - * @param querySupplier supplies the query to fetch the resource with the given key. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * resource was found in the CTP project with this key. - */ - @Nonnull - CompletionStage> fetchCachedResourceId( - @Nullable final String key, - @Nonnull final Function keyMapper, - @Nonnull final Supplier querySupplier) { - - if (isBlank(key)) { - return CompletableFuture.completedFuture(Optional.empty()); - } - - final String id = keyToIdCache.getIfPresent(key); - if (id != null) { - return CompletableFuture.completedFuture(Optional.of(id)); - } - return fetchAndCache(key, keyMapper, querySupplier); + return fetchAndCache(key, keyMapper, querySupplier); + } + + private CompletionStage> fetchAndCache( + @Nullable final String key, + @Nonnull final Function keyMapper, + @Nonnull final Supplier querySupplier) { + + final Consumer> pageConsumer = + page -> + page.forEach(resource -> keyToIdCache.put(keyMapper.apply(resource), resource.getId())); + + return CtpQueryUtils.queryAll(syncOptions.getCtpClient(), querySupplier.get(), pageConsumer) + .thenApply(result -> Optional.ofNullable(keyToIdCache.getIfPresent(key))); + } + + /** + * Given a set of keys this method caches a mapping of the keys to ids of such keys only for the + * keys which are not already in the cache. + * + * @param keys keys to cache. + * @param keyMapper a function to get the key from the resource. + * @param keysQueryMapper function that accepts a set of keys which are not cached and maps it to + * a query object representing the query to CTP on such keys. + * @return a map of key to ids of the requested keys. + */ + @Nonnull + CompletionStage> cacheKeysToIds( + @Nonnull final Set keys, + @Nonnull final Function keyMapper, + @Nonnull final Function, Q> keysQueryMapper) { + + final Set keysNotCached = getKeysNotCached(keys); + + if (keysNotCached.isEmpty()) { + return CompletableFuture.completedFuture(keyToIdCache.asMap()); } - private CompletionStage> fetchAndCache( - @Nullable final String key, - @Nonnull final Function keyMapper, - @Nonnull final Supplier querySupplier) { - - final Consumer> pageConsumer = page -> page.forEach(resource -> - keyToIdCache.put(keyMapper.apply(resource), resource.getId())); - - return CtpQueryUtils - .queryAll(syncOptions.getCtpClient(), querySupplier.get(), pageConsumer) - .thenApply(result -> Optional.ofNullable(keyToIdCache.getIfPresent(key))); + final Consumer> pageConsumer = + page -> + page.forEach(resource -> keyToIdCache.put(keyMapper.apply(resource), resource.getId())); + + return CtpQueryUtils.queryAll( + syncOptions.getCtpClient(), keysQueryMapper.apply(keysNotCached), pageConsumer) + .thenApply(result -> keyToIdCache.asMap()); + } + + /** + * Given a set of keys this method collects all keys which aren't already contained in the cache + * {@code keyToIdCache} + * + * @param keys {@link Set} of keys + * @return a {@link Set} of keys which aren't already contained in the cache or empty + */ + @Nonnull + private Set getKeysNotCached(@Nonnull final Set keys) { + return keys.stream() + .filter(StringUtils::isNotBlank) + .filter(key -> !keyToIdCache.asMap().containsKey(key)) + .collect(Collectors.toSet()); + } + + /** + * Given a set of keys this method caches a mapping of the keys to ids of such keys only for the + * keys which are not already in the cache. + * + * @param keys keys to cache. + * @param keyToIdMapper a function to get the key from the resource. + * @param keysRequestMapper function that accepts a set of keys which are not cached and maps it + * to a graphQL request object representing the graphQL query to CTP on such keys. + * @return a map of key to ids of the requested keys. + */ + @Nonnull + CompletionStage> cacheKeysToIdsUsingGraphQl( + @Nonnull final Set keys, + @Nonnull final Function> keyToIdMapper, + @Nonnull final Function, ResourceKeyIdGraphQlRequest> keysRequestMapper) { + + final Set keysNotCached = getKeysNotCached(keys); + + if (keysNotCached.isEmpty()) { + return CompletableFuture.completedFuture(keyToIdCache.asMap()); } - /** - * Given a set of keys this method caches a mapping of the keys to ids of such keys only for the keys which are - * not already in the cache. - * - * @param keys keys to cache. - * @param keyMapper a function to get the key from the resource. - * @param keysQueryMapper function that accepts a set of keys which are not cached and maps it to a query object - * representing the query to CTP on such keys. - * @return a map of key to ids of the requested keys. - */ - @Nonnull - CompletionStage> cacheKeysToIds( - @Nonnull final Set keys, - @Nonnull final Function keyMapper, - @Nonnull final Function, Q> keysQueryMapper) { - - final Set keysNotCached = getKeysNotCached(keys); - - if (keysNotCached.isEmpty()) { - return CompletableFuture.completedFuture(keyToIdCache.asMap()); - } - - final Consumer> pageConsumer = page -> page.forEach(resource -> - keyToIdCache.put(keyMapper.apply(resource), resource.getId())); - - return CtpQueryUtils - .queryAll(syncOptions.getCtpClient(), keysQueryMapper.apply(keysNotCached), pageConsumer) - .thenApply(result -> keyToIdCache.asMap()); + final Consumer> resultConsumer = + results -> results.forEach(resource -> keyToIdCache.putAll(keyToIdMapper.apply(resource))); + + return CtpQueryUtils.queryAll( + syncOptions.getCtpClient(), keysRequestMapper.apply(keysNotCached), resultConsumer) + .thenApply(result -> keyToIdCache.asMap()); + } + + /** + * Given a {@link Set} of resource keys, this method fetches a set of all the resources, matching + * this given set of keys in the CTP project, defined in an injected {@link SphereClient}. A + * mapping of the key to the id of the fetched resources is persisted in an in-memory map. + * + * @param keys set of keys to fetch matching resources by. + * @param keyMapper a function to get the key from the resource. + * @param querySupplier supplies the query to fetch the resources with the given keys. + * @return {@link CompletionStage}<{@link Set}<{@code U}>> in which the result of it's + * completion contains a {@link Set} of all matching resources. + */ + @Nonnull + CompletionStage> fetchMatchingResources( + @Nonnull final Set keys, + @Nonnull final Function keyMapper, + @Nonnull final Supplier querySupplier) { + + if (keys.isEmpty()) { + return CompletableFuture.completedFuture(Collections.emptySet()); } - /** - * Given a set of keys this method collects all keys which aren't already contained in the cache - * {@code keyToIdCache} - * - * @param keys {@link Set} of keys - * @return a {@link Set} of keys which aren't already contained in the cache or empty - */ - @Nonnull - private Set getKeysNotCached(@Nonnull final Set keys) { - return keys - .stream() - .filter(StringUtils::isNotBlank) - .filter(key -> !keyToIdCache.asMap().containsKey(key)) - .collect(Collectors.toSet()); + return CtpQueryUtils.queryAll( + syncOptions.getCtpClient(), querySupplier.get(), Function.identity()) + .thenApply( + fetchedResources -> + fetchedResources.stream() + .flatMap(List::stream) + .peek(resource -> keyToIdCache.put(keyMapper.apply(resource), resource.getId())) + .collect(Collectors.toSet())); + } + + /** + * Given a resource key, this method fetches a resource that matches this given key in the CTP + * project defined in a potentially injected {@link SphereClient}. If there is no matching + * resource an empty {@link Optional} will be returned in the returned future. A mapping of the + * key to the id of the fetched resource is persisted in an in -memory map. + * + * @param key the key of the resource to fetch + * @param querySupplier supplies the query to fetch the resource with the given key. + * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion + * contains an {@link Optional} that contains the matching {@code T} if exists, otherwise + * empty. + */ + @Nonnull + CompletionStage> fetchResource( + @Nullable final String key, @Nonnull final Supplier querySupplier) { + + if (isBlank(key)) { + return CompletableFuture.completedFuture(Optional.empty()); } - /** - * Given a set of keys this method caches a mapping of the keys to ids of such keys only for the keys which are - * not already in the cache. - * - * @param keys keys to cache. - * @param keyToIdMapper a function to get the key from the resource. - * @param keysRequestMapper function that accepts a set of keys which are not cached and maps it to a graphQL - * request object representing the graphQL query to CTP on such keys. - * @return a map of key to ids of the requested keys. - */ - @Nonnull - CompletionStage> cacheKeysToIdsUsingGraphQl( - @Nonnull final Set keys, - @Nonnull final Function> keyToIdMapper, - @Nonnull final Function, ResourceKeyIdGraphQlRequest> keysRequestMapper) { - - final Set keysNotCached = getKeysNotCached(keys); - - if (keysNotCached.isEmpty()) { - return CompletableFuture.completedFuture(keyToIdCache.asMap()); - } - - final Consumer> resultConsumer = results -> results.forEach(resource -> - keyToIdCache.putAll(keyToIdMapper.apply(resource))); - - return CtpQueryUtils.queryAll(syncOptions.getCtpClient(), keysRequestMapper.apply(keysNotCached), - resultConsumer).thenApply(result -> keyToIdCache.asMap()); - } - - /** - * Given a {@link Set} of resource keys, this method fetches a set of all the resources, matching this given set of - * keys in the CTP project, defined in an injected {@link SphereClient}. A mapping of the key to the id - * of the fetched resources is persisted in an in-memory map. - * - * @param keys set of keys to fetch matching resources by. - * @param keyMapper a function to get the key from the resource. - * @param querySupplier supplies the query to fetch the resources with the given keys. - * @return {@link CompletionStage}<{@link Set}<{@code U}>> in which the result of it's completion - * contains a {@link Set} of all matching resources. - */ - @Nonnull - CompletionStage> fetchMatchingResources( - @Nonnull final Set keys, - @Nonnull final Function keyMapper, - @Nonnull final Supplier querySupplier) { - - if (keys.isEmpty()) { - return CompletableFuture.completedFuture(Collections.emptySet()); - } - - return CtpQueryUtils - .queryAll(syncOptions.getCtpClient(), querySupplier.get(), Function.identity()) - .thenApply(fetchedResources -> fetchedResources - .stream() - .flatMap(List::stream) - .peek(resource -> keyToIdCache.put(keyMapper.apply(resource), resource.getId())) - .collect(Collectors.toSet())); - } - - /** - * Given a resource key, this method fetches a resource that matches this given key in the CTP project defined in a - * potentially injected {@link SphereClient}. If there is no matching resource an empty {@link Optional} will be - * returned in the returned future. A mapping of the key to the id of the fetched resource is persisted in an in - * -memory map. - * - * @param key the key of the resource to fetch - * @param querySupplier supplies the query to fetch the resource with the given key. - * @return {@link CompletionStage}<{@link Optional}> in which the result of it's completion contains an - * {@link Optional} that contains the matching {@code T} if exists, otherwise empty. - */ - @Nonnull - CompletionStage> fetchResource( - @Nullable final String key, - @Nonnull final Supplier querySupplier) { - - if (isBlank(key)) { - return CompletableFuture.completedFuture(Optional.empty()); - } - - return syncOptions - .getCtpClient() - .execute(querySupplier.get()) - .thenApply(pagedQueryResult -> pagedQueryResult - .head() - .map(resource -> { - keyToIdCache.put(key, resource.getId()); - return resource; - })); - } - - @Nonnull - CompletionStage> executeCreateCommand( - @Nonnull final T draft, - @Nonnull final Function keyMapper, - @Nonnull final Function> createCommand) { - - final String draftKey = keyMapper.apply(draft); - - return syncOptions - .getCtpClient() - .execute(createCommand.apply(draft)) - .handle(((resource, exception) -> { - if (exception == null) { - keyToIdCache.put(draftKey, resource.getId()); - return Optional.of(resource); - } else { - syncOptions.applyErrorCallback( - new SyncException(format(CREATE_FAILED, draftKey, exception.getMessage()), exception), - null, draft, null); - return Optional.empty(); - } + return syncOptions + .getCtpClient() + .execute(querySupplier.get()) + .thenApply( + pagedQueryResult -> + pagedQueryResult + .head() + .map( + resource -> { + keyToIdCache.put(key, resource.getId()); + return resource; + })); + } + + @Nonnull + CompletionStage> executeCreateCommand( + @Nonnull final T draft, + @Nonnull final Function keyMapper, + @Nonnull final Function> createCommand) { + + final String draftKey = keyMapper.apply(draft); + + return syncOptions + .getCtpClient() + .execute(createCommand.apply(draft)) + .handle( + ((resource, exception) -> { + if (exception == null) { + keyToIdCache.put(draftKey, resource.getId()); + return Optional.of(resource); + } else { + syncOptions.applyErrorCallback( + new SyncException( + format(CREATE_FAILED, draftKey, exception.getMessage()), exception), + null, + draft, + null); + return Optional.empty(); + } })); - } + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/BaseServiceWithKey.java b/src/main/java/com/commercetools/sync/services/impl/BaseServiceWithKey.java index a3ef50de94..c6dcfea0fd 100644 --- a/src/main/java/com/commercetools/sync/services/impl/BaseServiceWithKey.java +++ b/src/main/java/com/commercetools/sync/services/impl/BaseServiceWithKey.java @@ -7,9 +7,6 @@ import io.sphere.sdk.models.Resource; import io.sphere.sdk.models.WithKey; import io.sphere.sdk.queries.MetaModelQueryDsl; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.Map; import java.util.Optional; @@ -17,119 +14,127 @@ import java.util.concurrent.CompletionStage; import java.util.function.Function; import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** - * @param Resource Draft (e.g. {@link io.sphere.sdk.products.ProductDraft}, - * {@link io.sphere.sdk.categories.CategoryDraft}, etc.. - * @param Resource (e.g. {@link io.sphere.sdk.products.Product}, {@link io.sphere.sdk.categories.Category}, etc.. + * @param Resource Draft (e.g. {@link io.sphere.sdk.products.ProductDraft}, {@link + * io.sphere.sdk.categories.CategoryDraft}, etc.. + * @param Resource (e.g. {@link io.sphere.sdk.products.Product}, {@link + * io.sphere.sdk.categories.Category}, etc.. * @param Subclass of {@link BaseSyncOptions} - * @param Query (e.g. {@link io.sphere.sdk.products.queries.ProductQuery}, - * {@link io.sphere.sdk.categories.queries.CategoryQuery}, etc.. - * @param Query Model (e.g. {@link io.sphere.sdk.products.queries.ProductQueryModel}, - * {@link io.sphere.sdk.categories.queries.CategoryQueryModel}, etc.. + * @param Query (e.g. {@link io.sphere.sdk.products.queries.ProductQuery}, {@link + * io.sphere.sdk.categories.queries.CategoryQuery}, etc.. + * @param Query Model (e.g. {@link io.sphere.sdk.products.queries.ProductQueryModel}, {@link + * io.sphere.sdk.categories.queries.CategoryQueryModel}, etc.. * @param Expansion Model (e.g. {@link io.sphere.sdk.products.expansion.ProductExpansionModel}, - * {@link io.sphere.sdk.categories.expansion.CategoryExpansionModel}, etc.. - * + * {@link io.sphere.sdk.categories.expansion.CategoryExpansionModel}, etc.. */ -abstract class BaseServiceWithKey & WithKey, S extends BaseSyncOptions, - Q extends MetaModelQueryDsl, M, E> extends BaseService { - - BaseServiceWithKey(@Nonnull final S syncOptions) { - super(syncOptions); - } - - /** - * Given a resource draft of type {@code T}, this method attempts to create a resource {@code U} based on it in - * the CTP project defined by the sync options. - * - *

A completion stage containing an empty option and the error callback will be triggered in those cases: - *

    - *
  • the draft has a blank key
  • - *
  • the create request fails on CTP
  • - *
- * - *

On the other hand, if the resource gets created successfully on CTP, then the created resource's id and - * key are cached and the method returns a {@link CompletionStage} in which the result of it's completion - * contains an instance {@link Optional} of the resource which was created. - * - * @param draft the resource draft to create a resource based off of. - * @param createCommand a function to get the create command using the supplied draft. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * empty optional. - */ - @Nonnull - CompletionStage> createResource( - @Nonnull final T draft, - @Nonnull final Function> createCommand) { +abstract class BaseServiceWithKey< + T extends WithKey, + U extends Resource & WithKey, + S extends BaseSyncOptions, + Q extends MetaModelQueryDsl, + M, + E> + extends BaseService { - return super.createResource(draft, T::getKey, createCommand); - } + BaseServiceWithKey(@Nonnull final S syncOptions) { + super(syncOptions); + } - /** - * Given a {@code key}, if it is blank (null/empty), a completed future with an empty optional is returned. - * This method then checks if the cached map of resource keys -> ids contains the key. If it does, then an - * optional containing the mapped id is returned. If the cache doesn't contain the key; this method attempts to - * fetch the id of the key from the CTP project, caches it and returns a - * {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's completion - * could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no resource - * was found in the CTP project with this key. - * - * @param key the key by which a resource id should be fetched from the CTP project. - * @param querySupplier supplies the query to fetch the resource with the given key. - * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the result of it's - * completion could contain an {@link Optional} with the id inside of it or an empty {@link Optional} if no - * resource was found in the CTP project with this key. - */ - @Nonnull - CompletionStage> fetchCachedResourceId( - @Nullable final String key, - @Nonnull final Supplier querySupplier) { + /** + * Given a resource draft of type {@code T}, this method attempts to create a resource {@code U} + * based on it in the CTP project defined by the sync options. + * + *

A completion stage containing an empty option and the error callback will be triggered in + * those cases: + * + *

    + *
  • the draft has a blank key + *
  • the create request fails on CTP + *
+ * + *

On the other hand, if the resource gets created successfully on CTP, then the created + * resource's id and key are cached and the method returns a {@link CompletionStage} in which the + * result of it's completion contains an instance {@link Optional} of the resource which was + * created. + * + * @param draft the resource draft to create a resource based off of. + * @param createCommand a function to get the create command using the supplied draft. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an empty optional. + */ + @Nonnull + CompletionStage> createResource( + @Nonnull final T draft, + @Nonnull final Function> createCommand) { - // Why method reference is not used: - // http://mail.openjdk.java.net/pipermail/compiler-dev/2015-November/009824.html - return super.fetchCachedResourceId(key, resource -> resource.getKey(), querySupplier); - } + return super.createResource(draft, T::getKey, createCommand); + } + /** + * Given a {@code key}, if it is blank (null/empty), a completed future with an empty optional is + * returned. This method then checks if the cached map of resource keys -> ids contains the + * key. If it does, then an optional containing the mapped id is returned. If the cache doesn't + * contain the key; this method attempts to fetch the id of the key from the CTP project, caches + * it and returns a {@link CompletionStage}<{@link Optional}<{@link String}>> in which + * the result of it's completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no resource was found in the CTP project with this key. + * + * @param key the key by which a resource id should be fetched from the CTP project. + * @param querySupplier supplies the query to fetch the resource with the given key. + * @return {@link CompletionStage}<{@link Optional}<{@link String}>> in which the + * result of it's completion could contain an {@link Optional} with the id inside of it or an + * empty {@link Optional} if no resource was found in the CTP project with this key. + */ + @Nonnull + CompletionStage> fetchCachedResourceId( + @Nullable final String key, @Nonnull final Supplier querySupplier) { - /** - * Given a set of keys this method caches a mapping of the keys to ids of such keys only for the keys which are - * not already in the cache. - * - * @param keys keys to cache. - * @param keysRequestMapper function that accepts a set of keys which are not cached and maps it to a graphQL - * request object representing the graphql query to CTP on such keys. - * @return a map of key to ids of the requested keys. - */ - @Nonnull - CompletionStage> cacheKeysToIds( - @Nonnull final Set keys, - @Nonnull final Function, ResourceKeyIdGraphQlRequest> keysRequestMapper) { + // Why method reference is not used: + // http://mail.openjdk.java.net/pipermail/compiler-dev/2015-November/009824.html + return super.fetchCachedResourceId(key, resource -> resource.getKey(), querySupplier); + } - // Why method reference is not used: - // http://mail.openjdk.java.net/pipermail/compiler-dev/2015-November/009824.html - return super - .cacheKeysToIdsUsingGraphQl(keys, resource -> Collections.singletonMap(resource.getKey(), resource.getId()), - keysRequestMapper); - } + /** + * Given a set of keys this method caches a mapping of the keys to ids of such keys only for the + * keys which are not already in the cache. + * + * @param keys keys to cache. + * @param keysRequestMapper function that accepts a set of keys which are not cached and maps it + * to a graphQL request object representing the graphql query to CTP on such keys. + * @return a map of key to ids of the requested keys. + */ + @Nonnull + CompletionStage> cacheKeysToIds( + @Nonnull final Set keys, + @Nonnull final Function, ResourceKeyIdGraphQlRequest> keysRequestMapper) { - /** - * Given a {@link Set} of resource keys, this method fetches a set of all the resources, matching this given set of - * keys in the CTP project, defined in an injected {@link SphereClient}. A mapping of the key to the id - * of the fetched resources is persisted in an in-memory map. - * - * @param keys set of state keys to fetch matching states by - * @param querySupplier supplies the query to fetch the resources with the given keys. - * @return {@link CompletionStage}<{@link Set}<{@code U}>> in which the result of it's completion - * contains a {@link Set} of all matching resources. - */ - @Nonnull - CompletionStage> fetchMatchingResources( - @Nonnull final Set keys, - @Nonnull final Supplier querySupplier) { + // Why method reference is not used: + // http://mail.openjdk.java.net/pipermail/compiler-dev/2015-November/009824.html + return super.cacheKeysToIdsUsingGraphQl( + keys, + resource -> Collections.singletonMap(resource.getKey(), resource.getId()), + keysRequestMapper); + } + /** + * Given a {@link Set} of resource keys, this method fetches a set of all the resources, matching + * this given set of keys in the CTP project, defined in an injected {@link SphereClient}. A + * mapping of the key to the id of the fetched resources is persisted in an in-memory map. + * + * @param keys set of state keys to fetch matching states by + * @param querySupplier supplies the query to fetch the resources with the given keys. + * @return {@link CompletionStage}<{@link Set}<{@code U}>> in which the result of it's + * completion contains a {@link Set} of all matching resources. + */ + @Nonnull + CompletionStage> fetchMatchingResources( + @Nonnull final Set keys, @Nonnull final Supplier querySupplier) { - // Why method reference is not used: - // http://mail.openjdk.java.net/pipermail/compiler-dev/2015-November/009824.html - return super.fetchMatchingResources(keys, resource -> resource.getKey(), querySupplier); - } -} \ No newline at end of file + // Why method reference is not used: + // http://mail.openjdk.java.net/pipermail/compiler-dev/2015-November/009824.html + return super.fetchMatchingResources(keys, resource -> resource.getKey(), querySupplier); + } +} diff --git a/src/main/java/com/commercetools/sync/services/impl/CartDiscountServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/CartDiscountServiceImpl.java index 567245798f..7e6a839cff 100644 --- a/src/main/java/com/commercetools/sync/services/impl/CartDiscountServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/CartDiscountServiceImpl.java @@ -11,55 +11,65 @@ import io.sphere.sdk.cartdiscounts.queries.CartDiscountQueryBuilder; import io.sphere.sdk.cartdiscounts.queries.CartDiscountQueryModel; import io.sphere.sdk.commands.UpdateAction; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -public class CartDiscountServiceImpl extends BaseServiceWithKey> +public class CartDiscountServiceImpl + extends BaseServiceWithKey< + CartDiscountDraft, + CartDiscount, + CartDiscountSyncOptions, + CartDiscountQuery, + CartDiscountQueryModel, + CartDiscountExpansionModel> implements CartDiscountService { - public CartDiscountServiceImpl(@Nonnull final CartDiscountSyncOptions syncOptions) { - super(syncOptions); - } + public CartDiscountServiceImpl(@Nonnull final CartDiscountSyncOptions syncOptions) { + super(syncOptions); + } - @Nonnull - @Override - public CompletionStage> fetchMatchingCartDiscountsByKeys(@Nonnull final Set keys) { + @Nonnull + @Override + public CompletionStage> fetchMatchingCartDiscountsByKeys( + @Nonnull final Set keys) { - return fetchMatchingResources(keys, - () -> CartDiscountQueryBuilder - .of() + return fetchMatchingResources( + keys, + () -> + CartDiscountQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().isIn(keys)) .build()); - } + } - @Nonnull - @Override - public CompletionStage> fetchCartDiscount(@Nullable final String key) { + @Nonnull + @Override + public CompletionStage> fetchCartDiscount(@Nullable final String key) { - return fetchResource(key, () -> CartDiscountQuery - .of().plusPredicates(cartDiscountQueryModel -> cartDiscountQueryModel.key().is(key))); - } + return fetchResource( + key, + () -> + CartDiscountQuery.of() + .plusPredicates(cartDiscountQueryModel -> cartDiscountQueryModel.key().is(key))); + } - @Nonnull - @Override - public CompletionStage> createCartDiscount( - @Nonnull final CartDiscountDraft cartDiscountDraft) { + @Nonnull + @Override + public CompletionStage> createCartDiscount( + @Nonnull final CartDiscountDraft cartDiscountDraft) { - return createResource(cartDiscountDraft, CartDiscountCreateCommand::of); - } + return createResource(cartDiscountDraft, CartDiscountCreateCommand::of); + } - @Nonnull - @Override - public CompletionStage updateCartDiscount( - @Nonnull final CartDiscount cartDiscount, - @Nonnull final List> updateActions) { + @Nonnull + @Override + public CompletionStage updateCartDiscount( + @Nonnull final CartDiscount cartDiscount, + @Nonnull final List> updateActions) { - return updateResource(cartDiscount, CartDiscountUpdateCommand::of, updateActions); - } + return updateResource(cartDiscount, CartDiscountUpdateCommand::of, updateActions); + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/CategoryServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/CategoryServiceImpl.java index eca7beb7bf..8bde8abd35 100644 --- a/src/main/java/com/commercetools/sync/services/impl/CategoryServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/CategoryServiceImpl.java @@ -1,5 +1,6 @@ package com.commercetools.sync.services.impl; +import static java.util.Collections.singleton; import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; @@ -14,75 +15,85 @@ import io.sphere.sdk.categories.queries.CategoryQueryBuilder; import io.sphere.sdk.categories.queries.CategoryQueryModel; import io.sphere.sdk.commands.UpdateAction; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -import static java.util.Collections.singleton; - -/** - * Implementation of CategoryService interface. - */ -public final class CategoryServiceImpl extends BaseServiceWithKey> implements CategoryService { - - public CategoryServiceImpl(@Nonnull final CategorySyncOptions syncOptions) { - super(syncOptions); - } - - @Nonnull - @Override - public CompletionStage> cacheKeysToIds(@Nonnull final Set categoryKeys) { - return cacheKeysToIds( - categoryKeys, - keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.CATEGORIES)); - } - - @Nonnull - @Override - public CompletionStage> fetchMatchingCategoriesByKeys(@Nonnull final Set categoryKeys) { - - return fetchMatchingResources(categoryKeys, - () -> CategoryQuery - .of() +/** Implementation of CategoryService interface. */ +public final class CategoryServiceImpl + extends BaseServiceWithKey< + CategoryDraft, + Category, + CategorySyncOptions, + CategoryQuery, + CategoryQueryModel, + CategoryExpansionModel> + implements CategoryService { + + public CategoryServiceImpl(@Nonnull final CategorySyncOptions syncOptions) { + super(syncOptions); + } + + @Nonnull + @Override + public CompletionStage> cacheKeysToIds( + @Nonnull final Set categoryKeys) { + return cacheKeysToIds( + categoryKeys, + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.CATEGORIES)); + } + + @Nonnull + @Override + public CompletionStage> fetchMatchingCategoriesByKeys( + @Nonnull final Set categoryKeys) { + + return fetchMatchingResources( + categoryKeys, + () -> + CategoryQuery.of() .plusPredicates(categoryQueryModel -> categoryQueryModel.key().isIn(categoryKeys))); - } - - @Nonnull - @Override - public CompletionStage> fetchCategory(@Nullable final String key) { - - return fetchResource(key, - () -> CategoryQuery.of().plusPredicates(categoryQueryModel -> categoryQueryModel.key().is(key))); - } - - @Nonnull - @Override - public CompletionStage> fetchCachedCategoryId(@Nonnull final String key) { - - return fetchCachedResourceId(key, - () -> CategoryQueryBuilder - .of() + } + + @Nonnull + @Override + public CompletionStage> fetchCategory(@Nullable final String key) { + + return fetchResource( + key, + () -> + CategoryQuery.of() + .plusPredicates(categoryQueryModel -> categoryQueryModel.key().is(key))); + } + + @Nonnull + @Override + public CompletionStage> fetchCachedCategoryId(@Nonnull final String key) { + + return fetchCachedResourceId( + key, + () -> + CategoryQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().isIn(singleton(key))) .build()); - } - - @Nonnull - @Override - public CompletionStage> createCategory(@Nonnull final CategoryDraft categoryDraft) { - return createResource(categoryDraft, CategoryCreateCommand::of); - } - - @Nonnull - @Override - public CompletionStage updateCategory(@Nonnull final Category category, - @Nonnull final List> updateActions) { - return updateResource(category, CategoryUpdateCommand::of, updateActions); - } + } + + @Nonnull + @Override + public CompletionStage> createCategory( + @Nonnull final CategoryDraft categoryDraft) { + return createResource(categoryDraft, CategoryCreateCommand::of); + } + + @Nonnull + @Override + public CompletionStage updateCategory( + @Nonnull final Category category, @Nonnull final List> updateActions) { + return updateResource(category, CategoryUpdateCommand::of, updateActions); + } } - diff --git a/src/main/java/com/commercetools/sync/services/impl/ChannelServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/ChannelServiceImpl.java index 8a80e9b263..6b4efa91a9 100644 --- a/src/main/java/com/commercetools/sync/services/impl/ChannelServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/ChannelServiceImpl.java @@ -13,69 +13,71 @@ import io.sphere.sdk.channels.queries.ChannelQuery; import io.sphere.sdk.channels.queries.ChannelQueryBuilder; import io.sphere.sdk.channels.queries.ChannelQueryModel; - -import javax.annotation.Nonnull; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; - -public final class ChannelServiceImpl extends BaseServiceWithKey> implements ChannelService { - - private final Set channelRoles; - - - public ChannelServiceImpl( - @Nonnull final BaseSyncOptions syncOptions, - @Nonnull final Set channelRoles) { - super(syncOptions); - this.channelRoles = channelRoles; - } - - @Nonnull - @Override - public CompletionStage> cacheKeysToIds(@Nonnull final Set channelKeys) { - - return cacheKeysToIds( - channelKeys, - keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.CHANNELS)); - } - - @Nonnull - @Override - public CompletionStage> fetchCachedChannelId(@Nonnull final String key) { - - return fetchCachedResourceId( - key, - () -> ChannelQueryBuilder - .of() +public final class ChannelServiceImpl + extends BaseServiceWithKey< + ChannelDraft, + Channel, + BaseSyncOptions, + ChannelQuery, + ChannelQueryModel, + ChannelExpansionModel> + implements ChannelService { + + private final Set channelRoles; + + public ChannelServiceImpl( + @Nonnull final BaseSyncOptions syncOptions, @Nonnull final Set channelRoles) { + super(syncOptions); + this.channelRoles = channelRoles; + } + + @Nonnull + @Override + public CompletionStage> cacheKeysToIds( + @Nonnull final Set channelKeys) { + + return cacheKeysToIds( + channelKeys, + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.CHANNELS)); + } + + @Nonnull + @Override + public CompletionStage> fetchCachedChannelId(@Nonnull final String key) { + + return fetchCachedResourceId( + key, + () -> + ChannelQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().is(key)) .build()); + } - } - - @Nonnull - @Override - public CompletionStage> createChannel(@Nonnull final String key) { + @Nonnull + @Override + public CompletionStage> createChannel(@Nonnull final String key) { - final ChannelDraft draft = ChannelDraftBuilder - .of(key) - .roles(channelRoles) - .build(); + final ChannelDraft draft = ChannelDraftBuilder.of(key).roles(channelRoles).build(); - return createResource(draft, ChannelCreateCommand::of); - } + return createResource(draft, ChannelCreateCommand::of); + } - @Nonnull - @Override - public CompletionStage> createAndCacheChannel(@Nonnull final String key) { + @Nonnull + @Override + public CompletionStage> createAndCacheChannel(@Nonnull final String key) { - return createChannel(key) - .thenApply(channelOptional -> { - channelOptional.ifPresent(channel -> keyToIdCache.put(key, channel.getId())); - return channelOptional; + return createChannel(key) + .thenApply( + channelOptional -> { + channelOptional.ifPresent(channel -> keyToIdCache.put(key, channel.getId())); + return channelOptional; }); - } + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/CustomObjectServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/CustomObjectServiceImpl.java index bdb401ed82..426deddd8d 100644 --- a/src/main/java/com/commercetools/sync/services/impl/CustomObjectServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/CustomObjectServiceImpl.java @@ -14,189 +14,182 @@ import io.sphere.sdk.customobjects.queries.CustomObjectQueryBuilder; import io.sphere.sdk.customobjects.queries.CustomObjectQueryModel; import io.sphere.sdk.queries.QueryPredicate; - -import javax.annotation.Nonnull; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nonnull; -/** - * Implementation of CustomObjectService interface. - */ - +/** Implementation of CustomObjectService interface. */ public class CustomObjectServiceImpl - extends BaseService, - CustomObject, - CustomObjectSyncOptions, - CustomObjectQuery, - CustomObjectQueryModel>, CustomObjectExpansionModel>> + extends BaseService< + CustomObjectDraft, + CustomObject, + CustomObjectSyncOptions, + CustomObjectQuery, + CustomObjectQueryModel>, + CustomObjectExpansionModel>> implements CustomObjectService { - public CustomObjectServiceImpl(@Nonnull final CustomObjectSyncOptions syncOptions) { - super(syncOptions); - } - - @Nonnull - @Override - public CompletionStage> cacheKeysToIds( - @Nonnull final Set identifiers) { - - /* - * one example representation of the cache: - * - * [ - * "container_1|key_2" : "7fcd15ca-666e-4639-b25a-0c9f76a66efb" - * "container_2|key_1" : "ad54192c-86cd-4453-a139-85829e2dd891" - * "container_1|key_1" : "33213df2-c09a-426d-8c28-ccc52fdf9744" - * ] - */ - return cacheKeysToIds( - getKeys(identifiers), - this::keyMapper, - keysNotCached -> queryIdentifiers(getIdentifiers(keysNotCached))); - } - - @Nonnull - @Override - public CompletionStage> fetchCachedCustomObjectId( - @Nonnull final CustomObjectCompositeIdentifier identifier) { - - return fetchCachedResourceId( - identifier.toString(), - this::keyMapper, - () -> queryOneIdentifier(identifier)); - } - - - @Nonnull - @Override - public CompletionStage>> fetchMatchingCustomObjects( - @Nonnull final Set identifiers) { - - return fetchMatchingResources( - getKeys(identifiers), - this::keyMapper, - () -> queryIdentifiers(identifiers)); - } - - @Nonnull - private QueryPredicate> createQuery( - @Nonnull final CustomObjectQueryModel> queryModel, - @Nonnull final Set identifiers) { - - QueryPredicate> queryPredicate = QueryPredicate.of(null); - boolean firstAttempt = true; - for (CustomObjectCompositeIdentifier identifier : identifiers) { - String key = identifier.getKey(); - String container = identifier.getContainer(); - if (firstAttempt) { - queryPredicate = queryModel.container().is(container).and(queryModel.key().is(key)); - firstAttempt = false; - } else { - queryPredicate = queryPredicate.or(queryModel.container().is(container).and(queryModel.key().is(key))); - } - } - return queryPredicate; - } - - @Nonnull - @Override - public CompletionStage>> fetchCustomObject( - @Nonnull final CustomObjectCompositeIdentifier identifier) { - - return fetchResource(identifier.toString(), () -> queryOneIdentifier(identifier)); - } - - @Nonnull - @Override - public CompletionStage>> upsertCustomObject( - @Nonnull final CustomObjectDraft customObjectDraft) { + public CustomObjectServiceImpl(@Nonnull final CustomObjectSyncOptions syncOptions) { + super(syncOptions); + } - return createResource(customObjectDraft, - this::keyMapper, - CustomObjectUpsertCommand::of); - } + @Nonnull + @Override + public CompletionStage> cacheKeysToIds( + @Nonnull final Set identifiers) { - /** - * Custom object has special behaviour that it only performs upsert operation. That means both update and create - * custom object operations in the end called {@link BaseService#createResource}, which is different from other - * resources. - * - *

This method provides a specific exception handling after execution of create command for custom objects. - * Any exception that occurs inside executeCreateCommand method is thrown to the caller method in - * {@link CustomObjectSync}, which is necessary to trigger retry on error behaviour. + /* + * one example representation of the cache: * - * @param draft the custom object draft to create a custom object in target CTP project. - * @param keyMapper a function to get the key from the supplied custom object draft. - * @param createCommand a function to get the create command using the supplied custom object draft. - * @return a {@link CompletionStage} containing an optional with the created resource if successful otherwise an - * exception. + * [ + * "container_1|key_2" : "7fcd15ca-666e-4639-b25a-0c9f76a66efb" + * "container_2|key_1" : "ad54192c-86cd-4453-a139-85829e2dd891" + * "container_1|key_1" : "33213df2-c09a-426d-8c28-ccc52fdf9744" + * ] */ - @Nonnull - @Override - CompletionStage>> executeCreateCommand( - @Nonnull final CustomObjectDraft draft, - @Nonnull final Function, String> keyMapper, - @Nonnull final Function, - DraftBasedCreateCommand, CustomObjectDraft>> createCommand) { - - final String draftKey = keyMapper.apply(draft); - - return syncOptions - .getCtpClient() - .execute(createCommand.apply(draft)) - .thenApply(resource -> { - keyToIdCache.put(draftKey, resource.getId()); - return Optional.of(resource); - }); - } - - @Nonnull - private Set getKeys(@Nonnull final Set identifiers) { - return identifiers - .stream() - .map(CustomObjectCompositeIdentifier::toString) - .collect(Collectors.toSet()); - } - - @Nonnull - private String keyMapper(@Nonnull final CustomObjectDraft customObjectDraft) { - return CustomObjectCompositeIdentifier.of(customObjectDraft).toString(); - } - - @Nonnull - private String keyMapper(@Nonnull final CustomObject customObject) { - return CustomObjectCompositeIdentifier.of(customObject).toString(); - } - - @Nonnull - private Set getIdentifiers(@Nonnull final Set keys) { - return keys - .stream() - .map(CustomObjectCompositeIdentifier::of) - .collect(Collectors.toSet()); - } - - @Nonnull - private CustomObjectQuery queryIdentifiers( - @Nonnull final Set identifiers) { - - return CustomObjectQueryBuilder - .ofJsonNode() - .plusPredicates(q -> createQuery(q, identifiers)) - .build(); - } - - @Nonnull - private CustomObjectQuery queryOneIdentifier( - @Nonnull final CustomObjectCompositeIdentifier identifier) { - - return CustomObjectQueryBuilder - .ofJsonNode() - .plusPredicates(q -> q.container().is(identifier.getContainer()).and(q.key().is(identifier.getKey()))) - .build(); + return cacheKeysToIds( + getKeys(identifiers), + this::keyMapper, + keysNotCached -> queryIdentifiers(getIdentifiers(keysNotCached))); + } + + @Nonnull + @Override + public CompletionStage> fetchCachedCustomObjectId( + @Nonnull final CustomObjectCompositeIdentifier identifier) { + + return fetchCachedResourceId( + identifier.toString(), this::keyMapper, () -> queryOneIdentifier(identifier)); + } + + @Nonnull + @Override + public CompletionStage>> fetchMatchingCustomObjects( + @Nonnull final Set identifiers) { + + return fetchMatchingResources( + getKeys(identifiers), this::keyMapper, () -> queryIdentifiers(identifiers)); + } + + @Nonnull + private QueryPredicate> createQuery( + @Nonnull final CustomObjectQueryModel> queryModel, + @Nonnull final Set identifiers) { + + QueryPredicate> queryPredicate = QueryPredicate.of(null); + boolean firstAttempt = true; + for (CustomObjectCompositeIdentifier identifier : identifiers) { + String key = identifier.getKey(); + String container = identifier.getContainer(); + if (firstAttempt) { + queryPredicate = queryModel.container().is(container).and(queryModel.key().is(key)); + firstAttempt = false; + } else { + queryPredicate = + queryPredicate.or(queryModel.container().is(container).and(queryModel.key().is(key))); + } } + return queryPredicate; + } + + @Nonnull + @Override + public CompletionStage>> fetchCustomObject( + @Nonnull final CustomObjectCompositeIdentifier identifier) { + + return fetchResource(identifier.toString(), () -> queryOneIdentifier(identifier)); + } + + @Nonnull + @Override + public CompletionStage>> upsertCustomObject( + @Nonnull final CustomObjectDraft customObjectDraft) { + + return createResource(customObjectDraft, this::keyMapper, CustomObjectUpsertCommand::of); + } + + /** + * Custom object has special behaviour that it only performs upsert operation. That means both + * update and create custom object operations in the end called {@link + * BaseService#createResource}, which is different from other resources. + * + *

This method provides a specific exception handling after execution of create command for + * custom objects. Any exception that occurs inside executeCreateCommand method is thrown to the + * caller method in {@link CustomObjectSync}, which is necessary to trigger retry on error + * behaviour. + * + * @param draft the custom object draft to create a custom object in target CTP project. + * @param keyMapper a function to get the key from the supplied custom object draft. + * @param createCommand a function to get the create command using the supplied custom object + * draft. + * @return a {@link CompletionStage} containing an optional with the created resource if + * successful otherwise an exception. + */ + @Nonnull + @Override + CompletionStage>> executeCreateCommand( + @Nonnull final CustomObjectDraft draft, + @Nonnull final Function, String> keyMapper, + @Nonnull + final Function< + CustomObjectDraft, + DraftBasedCreateCommand, CustomObjectDraft>> + createCommand) { + + final String draftKey = keyMapper.apply(draft); + + return syncOptions + .getCtpClient() + .execute(createCommand.apply(draft)) + .thenApply( + resource -> { + keyToIdCache.put(draftKey, resource.getId()); + return Optional.of(resource); + }); + } + + @Nonnull + private Set getKeys(@Nonnull final Set identifiers) { + return identifiers.stream() + .map(CustomObjectCompositeIdentifier::toString) + .collect(Collectors.toSet()); + } + + @Nonnull + private String keyMapper(@Nonnull final CustomObjectDraft customObjectDraft) { + return CustomObjectCompositeIdentifier.of(customObjectDraft).toString(); + } + + @Nonnull + private String keyMapper(@Nonnull final CustomObject customObject) { + return CustomObjectCompositeIdentifier.of(customObject).toString(); + } + + @Nonnull + private Set getIdentifiers(@Nonnull final Set keys) { + return keys.stream().map(CustomObjectCompositeIdentifier::of).collect(Collectors.toSet()); + } + + @Nonnull + private CustomObjectQuery queryIdentifiers( + @Nonnull final Set identifiers) { + + return CustomObjectQueryBuilder.ofJsonNode() + .plusPredicates(q -> createQuery(q, identifiers)) + .build(); + } + + @Nonnull + private CustomObjectQuery queryOneIdentifier( + @Nonnull final CustomObjectCompositeIdentifier identifier) { + + return CustomObjectQueryBuilder.ofJsonNode() + .plusPredicates( + q -> q.container().is(identifier.getContainer()).and(q.key().is(identifier.getKey()))) + .build(); + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/CustomerGroupServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/CustomerGroupServiceImpl.java index 342327120e..c4c2ebe1b0 100644 --- a/src/main/java/com/commercetools/sync/services/impl/CustomerGroupServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/CustomerGroupServiceImpl.java @@ -10,39 +10,47 @@ import io.sphere.sdk.customergroups.queries.CustomerGroupQuery; import io.sphere.sdk.customergroups.queries.CustomerGroupQueryBuilder; import io.sphere.sdk.customergroups.queries.CustomerGroupQueryModel; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class CustomerGroupServiceImpl - extends BaseServiceWithKey> implements CustomerGroupService { - - public CustomerGroupServiceImpl(@Nonnull final BaseSyncOptions syncOptions) { - super(syncOptions); - } - - @Nonnull - @Override - public CompletionStage> cacheKeysToIds(@Nonnull final Set customerGroupKeys) { - - return cacheKeysToIds( - customerGroupKeys, keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, - GraphQlQueryResources.CUSTOMER_GROUPS)); - } - - @Nonnull - @Override - public CompletionStage> fetchCachedCustomerGroupId(@Nullable final String key) { - - return fetchCachedResourceId(key, - () -> CustomerGroupQueryBuilder - .of() + extends BaseServiceWithKey< + CustomerGroupDraft, + CustomerGroup, + BaseSyncOptions, + CustomerGroupQuery, + CustomerGroupQueryModel, + CustomerGroupExpansionModel> + implements CustomerGroupService { + + public CustomerGroupServiceImpl(@Nonnull final BaseSyncOptions syncOptions) { + super(syncOptions); + } + + @Nonnull + @Override + public CompletionStage> cacheKeysToIds( + @Nonnull final Set customerGroupKeys) { + + return cacheKeysToIds( + customerGroupKeys, + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.CUSTOMER_GROUPS)); + } + + @Nonnull + @Override + public CompletionStage> fetchCachedCustomerGroupId(@Nullable final String key) { + + return fetchCachedResourceId( + key, + () -> + CustomerGroupQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().is(key)) .build()); - } + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/CustomerServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/CustomerServiceImpl.java index a804477f9a..2393983244 100644 --- a/src/main/java/com/commercetools/sync/services/impl/CustomerServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/CustomerServiceImpl.java @@ -1,5 +1,8 @@ package com.commercetools.sync.services.impl; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.GraphQlQueryResources; @@ -14,107 +17,120 @@ import io.sphere.sdk.customers.queries.CustomerQuery; import io.sphere.sdk.customers.queries.CustomerQueryBuilder; import io.sphere.sdk.customers.queries.CustomerQueryModel; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.isBlank; - - -public final class CustomerServiceImpl extends BaseServiceWithKey> implements CustomerService { - - public CustomerServiceImpl(@Nonnull final CustomerSyncOptions syncOptions) { - super(syncOptions); - } - - @Nonnull - @Override - public CompletionStage> cacheKeysToIds( - @Nonnull final Set keysToCache) { - return cacheKeysToIds(keysToCache, keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, - GraphQlQueryResources.CUSTOMERS)); - } - - @Nonnull - @Override - public CompletionStage> fetchMatchingCustomersByKeys( - @Nonnull final Set customerKeys) { - return fetchMatchingResources(customerKeys, () -> CustomerQueryBuilder - .of() - .plusPredicates(customerQueryModel -> customerQueryModel.key().isIn(customerKeys)) - .build()); - } - - @Nonnull - @Override - public CompletionStage> fetchCustomerByKey(@Nullable final String key) { - return fetchResource(key, () -> CustomerQueryBuilder - .of() - .plusPredicates(customerQueryModel -> customerQueryModel.key().is(key)) - .build()); - } - - @Nonnull - @Override - public CompletionStage> fetchCachedCustomerId(@Nonnull final String key) { - return fetchCachedResourceId(key, () -> CustomerQueryBuilder - .of() - .plusPredicates(customerQueryModel -> customerQueryModel.key().is(key)) - .build()); - } - - @Nonnull - @Override - public CompletionStage> createCustomer( - @Nonnull final CustomerDraft customerDraft) { - - // Uses a different implementation than in the base service because CustomerCreateCommand uses a - // different library as CTP responds with a CustomerSignInResult which is not extending resource but a - // different model, containing the customer resource. - final String draftKey = customerDraft.getKey(); - final CustomerCreateCommand createCommand = CustomerCreateCommand.of(customerDraft); - - if (isBlank(draftKey)) { - syncOptions.applyErrorCallback( - new SyncException(format(CREATE_FAILED, draftKey, "Draft key is blank!")), - null, customerDraft, null); - return CompletableFuture.completedFuture(Optional.empty()); - } else { - return syncOptions - .getCtpClient() - .execute(createCommand) - .handle(((resource, exception) -> { - if (exception == null && resource.getCustomer() != null) { - keyToIdCache.put(draftKey, resource.getCustomer().getId()); - return Optional.of(resource.getCustomer()); - } else if (exception != null) { - syncOptions.applyErrorCallback( - new SyncException(format(CREATE_FAILED, draftKey, exception.getMessage()), - exception), - null, customerDraft, null); - return Optional.empty(); - } else { - return Optional.empty(); - } - })); - } - - } - - @Nonnull - @Override - public CompletionStage updateCustomer(@Nonnull final Customer customer, - @Nonnull final - List> updateActions) { - return updateResource(customer, CustomerUpdateCommand::of, updateActions); +public final class CustomerServiceImpl + extends BaseServiceWithKey< + CustomerDraft, + Customer, + CustomerSyncOptions, + CustomerQuery, + CustomerQueryModel, + CustomerExpansionModel> + implements CustomerService { + + public CustomerServiceImpl(@Nonnull final CustomerSyncOptions syncOptions) { + super(syncOptions); + } + + @Nonnull + @Override + public CompletionStage> cacheKeysToIds( + @Nonnull final Set keysToCache) { + return cacheKeysToIds( + keysToCache, + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.CUSTOMERS)); + } + + @Nonnull + @Override + public CompletionStage> fetchMatchingCustomersByKeys( + @Nonnull final Set customerKeys) { + return fetchMatchingResources( + customerKeys, + () -> + CustomerQueryBuilder.of() + .plusPredicates(customerQueryModel -> customerQueryModel.key().isIn(customerKeys)) + .build()); + } + + @Nonnull + @Override + public CompletionStage> fetchCustomerByKey(@Nullable final String key) { + return fetchResource( + key, + () -> + CustomerQueryBuilder.of() + .plusPredicates(customerQueryModel -> customerQueryModel.key().is(key)) + .build()); + } + + @Nonnull + @Override + public CompletionStage> fetchCachedCustomerId(@Nonnull final String key) { + return fetchCachedResourceId( + key, + () -> + CustomerQueryBuilder.of() + .plusPredicates(customerQueryModel -> customerQueryModel.key().is(key)) + .build()); + } + + @Nonnull + @Override + public CompletionStage> createCustomer( + @Nonnull final CustomerDraft customerDraft) { + + // Uses a different implementation than in the base service because CustomerCreateCommand uses a + // different library as CTP responds with a CustomerSignInResult which is not extending resource + // but a + // different model, containing the customer resource. + final String draftKey = customerDraft.getKey(); + final CustomerCreateCommand createCommand = CustomerCreateCommand.of(customerDraft); + + if (isBlank(draftKey)) { + syncOptions.applyErrorCallback( + new SyncException(format(CREATE_FAILED, draftKey, "Draft key is blank!")), + null, + customerDraft, + null); + return CompletableFuture.completedFuture(Optional.empty()); + } else { + return syncOptions + .getCtpClient() + .execute(createCommand) + .handle( + ((resource, exception) -> { + if (exception == null && resource.getCustomer() != null) { + keyToIdCache.put(draftKey, resource.getCustomer().getId()); + return Optional.of(resource.getCustomer()); + } else if (exception != null) { + syncOptions.applyErrorCallback( + new SyncException( + format(CREATE_FAILED, draftKey, exception.getMessage()), exception), + null, + customerDraft, + null); + return Optional.empty(); + } else { + return Optional.empty(); + } + })); } - -} \ No newline at end of file + } + + @Nonnull + @Override + public CompletionStage updateCustomer( + @Nonnull final Customer customer, @Nonnull final List> updateActions) { + return updateResource(customer, CustomerUpdateCommand::of, updateActions); + } +} diff --git a/src/main/java/com/commercetools/sync/services/impl/InventoryServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/InventoryServiceImpl.java index 387e97d979..4ff083182d 100644 --- a/src/main/java/com/commercetools/sync/services/impl/InventoryServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/InventoryServiceImpl.java @@ -12,51 +12,57 @@ import io.sphere.sdk.inventory.queries.InventoryEntryQuery; import io.sphere.sdk.inventory.queries.InventoryEntryQueryBuilder; import io.sphere.sdk.inventory.queries.InventoryEntryQueryModel; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; -public final class InventoryServiceImpl extends BaseService> +public final class InventoryServiceImpl + extends BaseService< + InventoryEntryDraft, + InventoryEntry, + InventorySyncOptions, + InventoryEntryQuery, + InventoryEntryQueryModel, + InventoryEntryExpansionModel> implements InventoryService { + public InventoryServiceImpl(@Nonnull final InventorySyncOptions syncOptions) { + super(syncOptions); + } - public InventoryServiceImpl(@Nonnull final InventorySyncOptions syncOptions) { - super(syncOptions); - } - - @Nonnull - @Override - public CompletionStage> fetchInventoryEntriesBySkus(@Nonnull final Set skus) { + @Nonnull + @Override + public CompletionStage> fetchInventoryEntriesBySkus( + @Nonnull final Set skus) { - return fetchMatchingResources(skus, - draft -> String.valueOf(InventoryEntryIdentifier.of(draft)), - () -> InventoryEntryQueryBuilder - .of() + return fetchMatchingResources( + skus, + draft -> String.valueOf(InventoryEntryIdentifier.of(draft)), + () -> + InventoryEntryQueryBuilder.of() .plusPredicates(queryModel -> queryModel.sku().isIn(skus)) .build()); - } - - @Nonnull - @Override - public CompletionStage> createInventoryEntry( - @Nonnull final InventoryEntryDraft inventoryEntryDraft) { - - return createResource( - inventoryEntryDraft, - draft -> String.valueOf(InventoryEntryIdentifier.of(draft)), - InventoryEntryCreateCommand::of); - } - - @Nonnull - @Override - public CompletionStage updateInventoryEntry( - @Nonnull final InventoryEntry inventoryEntry, - @Nonnull final List> updateActions) { - - return updateResource(inventoryEntry, InventoryEntryUpdateCommand::of, updateActions); - } + } + + @Nonnull + @Override + public CompletionStage> createInventoryEntry( + @Nonnull final InventoryEntryDraft inventoryEntryDraft) { + + return createResource( + inventoryEntryDraft, + draft -> String.valueOf(InventoryEntryIdentifier.of(draft)), + InventoryEntryCreateCommand::of); + } + + @Nonnull + @Override + public CompletionStage updateInventoryEntry( + @Nonnull final InventoryEntry inventoryEntry, + @Nonnull final List> updateActions) { + + return updateResource(inventoryEntry, InventoryEntryUpdateCommand::of, updateActions); + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/ProductServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/ProductServiceImpl.java index 83abd9d208..ef0775d032 100644 --- a/src/main/java/com/commercetools/sync/services/impl/ProductServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/ProductServiceImpl.java @@ -1,5 +1,8 @@ package com.commercetools.sync.services.impl; +import static java.lang.String.format; +import static java.util.Collections.singleton; + import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.GraphQlQueryResources; import com.commercetools.sync.products.ProductSyncOptions; @@ -13,84 +16,92 @@ import io.sphere.sdk.products.queries.ProductQuery; import io.sphere.sdk.products.queries.ProductQueryModel; import io.sphere.sdk.queries.QueryPredicate; -import org.apache.commons.lang3.StringUtils; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; -import static java.lang.String.format; -import static java.util.Collections.singleton; - - -public final class ProductServiceImpl extends BaseServiceWithKey> implements ProductService { - - public ProductServiceImpl(@Nonnull final ProductSyncOptions syncOptions) { - super(syncOptions); - } - - @Nonnull - @Override - public CompletionStage> getIdFromCacheOrFetch(@Nullable final String key) { - - return fetchCachedResourceId(key, - () -> ProductQuery.of() - .withPredicates(buildProductKeysQueryPredicate(singleton(key)))); - } - - @Nonnull - @Override - public CompletionStage> cacheKeysToIds(@Nonnull final Set productKeys) { - - return cacheKeysToIds( - productKeys, - keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.PRODUCTS)); - } - - QueryPredicate buildProductKeysQueryPredicate(@Nonnull final Set productKeys) { - final List keysSurroundedWithDoubleQuotes = productKeys.stream() - .filter(StringUtils::isNotBlank) - .map(productKey -> format("\"%s\"", productKey)) - .collect(Collectors.toList()); - String keysQueryString = keysSurroundedWithDoubleQuotes.toString(); - // Strip square brackets from list string. For example: ["key1", "key2"] -> "key1", "key2" - keysQueryString = keysQueryString.substring(1, keysQueryString.length() - 1); - return QueryPredicate.of(format("key in (%s)", keysQueryString)); - } - - @Nonnull - @Override - public CompletionStage> fetchMatchingProductsByKeys(@Nonnull final Set productKeys) { - - return fetchMatchingResources(productKeys, - () -> ProductQuery.of().withPredicates(buildProductKeysQueryPredicate(productKeys))); - } - - @Nonnull - @Override - public CompletionStage> fetchProduct(@Nullable final String key) { - - return fetchResource(key, - () -> ProductQuery - .of().withPredicates(buildProductKeysQueryPredicate(singleton(key)))); - } - - @Nonnull - @Override - public CompletionStage> createProduct(@Nonnull final ProductDraft productDraft) { - return createResource(productDraft, ProductCreateCommand::of); - } - - @Nonnull - @Override - public CompletionStage updateProduct(@Nonnull final Product product, - @Nonnull final List> updateActions) { - return updateResource(product, ProductUpdateCommand::of, updateActions); - } -} \ No newline at end of file +public final class ProductServiceImpl + extends BaseServiceWithKey< + ProductDraft, + Product, + ProductSyncOptions, + ProductQuery, + ProductQueryModel, + ProductExpansionModel> + implements ProductService { + + public ProductServiceImpl(@Nonnull final ProductSyncOptions syncOptions) { + super(syncOptions); + } + + @Nonnull + @Override + public CompletionStage> getIdFromCacheOrFetch(@Nullable final String key) { + + return fetchCachedResourceId( + key, + () -> ProductQuery.of().withPredicates(buildProductKeysQueryPredicate(singleton(key)))); + } + + @Nonnull + @Override + public CompletionStage> cacheKeysToIds( + @Nonnull final Set productKeys) { + + return cacheKeysToIds( + productKeys, + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.PRODUCTS)); + } + + QueryPredicate buildProductKeysQueryPredicate(@Nonnull final Set productKeys) { + final List keysSurroundedWithDoubleQuotes = + productKeys.stream() + .filter(StringUtils::isNotBlank) + .map(productKey -> format("\"%s\"", productKey)) + .collect(Collectors.toList()); + String keysQueryString = keysSurroundedWithDoubleQuotes.toString(); + // Strip square brackets from list string. For example: ["key1", "key2"] -> "key1", "key2" + keysQueryString = keysQueryString.substring(1, keysQueryString.length() - 1); + return QueryPredicate.of(format("key in (%s)", keysQueryString)); + } + + @Nonnull + @Override + public CompletionStage> fetchMatchingProductsByKeys( + @Nonnull final Set productKeys) { + + return fetchMatchingResources( + productKeys, + () -> ProductQuery.of().withPredicates(buildProductKeysQueryPredicate(productKeys))); + } + + @Nonnull + @Override + public CompletionStage> fetchProduct(@Nullable final String key) { + + return fetchResource( + key, + () -> ProductQuery.of().withPredicates(buildProductKeysQueryPredicate(singleton(key)))); + } + + @Nonnull + @Override + public CompletionStage> createProduct( + @Nonnull final ProductDraft productDraft) { + return createResource(productDraft, ProductCreateCommand::of); + } + + @Nonnull + @Override + public CompletionStage updateProduct( + @Nonnull final Product product, @Nonnull final List> updateActions) { + return updateResource(product, ProductUpdateCommand::of, updateActions); + } +} diff --git a/src/main/java/com/commercetools/sync/services/impl/ProductTypeServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/ProductTypeServiceImpl.java index 550a858c02..cdc9f25e48 100644 --- a/src/main/java/com/commercetools/sync/services/impl/ProductTypeServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/ProductTypeServiceImpl.java @@ -1,5 +1,7 @@ package com.commercetools.sync.services.impl; +import static java.util.Collections.singleton; + import com.commercetools.sync.commons.BaseSyncOptions; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.GraphQlQueryResources; @@ -15,9 +17,6 @@ import io.sphere.sdk.producttypes.queries.ProductTypeQuery; import io.sphere.sdk.producttypes.queries.ProductTypeQueryBuilder; import io.sphere.sdk.producttypes.queries.ProductTypeQueryModel; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; @@ -27,105 +26,123 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -import static java.util.Collections.singleton; - -public final class ProductTypeServiceImpl extends BaseServiceWithKey> implements ProductTypeService { - - private final Map> productsAttributesMetaData = new ConcurrentHashMap<>(); - - public ProductTypeServiceImpl(@Nonnull final BaseSyncOptions syncOptions) { - super(syncOptions); - } - - @Nonnull - @Override - public CompletionStage> cacheKeysToIds(@Nonnull final Set keys) { - - return cacheKeysToIds( - keys, - keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.PRODUCT_TYPES)); - } - - @Nonnull - @Override - public CompletionStage> fetchCachedProductTypeId(@Nonnull final String key) { - - return fetchCachedResourceId(key, - () -> ProductTypeQueryBuilder - .of() +public final class ProductTypeServiceImpl + extends BaseServiceWithKey< + ProductTypeDraft, + ProductType, + BaseSyncOptions, + ProductTypeQuery, + ProductTypeQueryModel, + ProductTypeExpansionModel> + implements ProductTypeService { + + private final Map> productsAttributesMetaData = + new ConcurrentHashMap<>(); + + public ProductTypeServiceImpl(@Nonnull final BaseSyncOptions syncOptions) { + super(syncOptions); + } + + @Nonnull + @Override + public CompletionStage> cacheKeysToIds(@Nonnull final Set keys) { + + return cacheKeysToIds( + keys, + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.PRODUCT_TYPES)); + } + + @Nonnull + @Override + public CompletionStage> fetchCachedProductTypeId(@Nonnull final String key) { + + return fetchCachedResourceId( + key, + () -> + ProductTypeQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().isIn(singleton(key))) .build()); + } + + @Nonnull + private static Map getAttributeMetaDataMap( + @Nonnull final ProductType productType) { + return productType.getAttributes().stream() + .map(AttributeMetaData::of) + .collect( + Collectors.toMap(AttributeMetaData::getName, attributeMetaData -> attributeMetaData)); + } + + @Nonnull + @Override + public CompletionStage>> + fetchCachedProductAttributeMetaDataMap(@Nonnull final String productTypeId) { + + if (productsAttributesMetaData.isEmpty()) { + return fetchAndCacheProductMetaData(productTypeId); } - - @Nonnull - private static Map getAttributeMetaDataMap(@Nonnull final ProductType productType) { - return productType - .getAttributes().stream() - .map(AttributeMetaData::of) - .collect( - Collectors.toMap(AttributeMetaData::getName, attributeMetaData -> attributeMetaData) - ); - } - - @Nonnull - @Override - public CompletionStage>> fetchCachedProductAttributeMetaDataMap( - @Nonnull final String productTypeId) { - - if (productsAttributesMetaData.isEmpty()) { - return fetchAndCacheProductMetaData(productTypeId); - } - return CompletableFuture.completedFuture( - Optional.ofNullable(productsAttributesMetaData.get(productTypeId)) - ); - } - - @Nonnull - private CompletionStage>> fetchAndCacheProductMetaData( - @Nonnull final String productTypeId) { - - final Consumer> productTypePageConsumer = productTypePage -> - productTypePage.forEach(type -> { - final String id = type.getId(); - productsAttributesMetaData.put(id, getAttributeMetaDataMap(type)); - }); - - return CtpQueryUtils.queryAll(syncOptions.getCtpClient(), ProductTypeQuery.of(), productTypePageConsumer) - .thenApply(result -> - Optional.ofNullable(productsAttributesMetaData.get(productTypeId))); - } - - @Nonnull - @Override - public CompletionStage> fetchMatchingProductTypesByKeys(@Nonnull final Set keys) { - return fetchMatchingResources(keys, - () -> ProductTypeQueryBuilder - .of() + return CompletableFuture.completedFuture( + Optional.ofNullable(productsAttributesMetaData.get(productTypeId))); + } + + @Nonnull + private CompletionStage>> fetchAndCacheProductMetaData( + @Nonnull final String productTypeId) { + + final Consumer> productTypePageConsumer = + productTypePage -> + productTypePage.forEach( + type -> { + final String id = type.getId(); + productsAttributesMetaData.put(id, getAttributeMetaDataMap(type)); + }); + + return CtpQueryUtils.queryAll( + syncOptions.getCtpClient(), ProductTypeQuery.of(), productTypePageConsumer) + .thenApply(result -> Optional.ofNullable(productsAttributesMetaData.get(productTypeId))); + } + + @Nonnull + @Override + public CompletionStage> fetchMatchingProductTypesByKeys( + @Nonnull final Set keys) { + return fetchMatchingResources( + keys, + () -> + ProductTypeQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().isIn(keys)) .build()); - } - - @Nonnull - @Override - public CompletionStage> createProductType(@Nonnull final ProductTypeDraft productTypeDraft) { - return createResource(productTypeDraft, ProductTypeCreateCommand::of); - } - - @Nonnull - @Override - public CompletionStage updateProductType( - @Nonnull final ProductType productType, @Nonnull final List> updateActions) { - - return updateResource(productType, ProductTypeUpdateCommand::of, updateActions); - } - - @Nonnull - @Override - public CompletionStage> fetchProductType(@Nullable final String key) { - - return fetchResource(key, - () -> ProductTypeQueryBuilder.of().plusPredicates(queryModel -> queryModel.key().is(key)).build()); - } + } + + @Nonnull + @Override + public CompletionStage> createProductType( + @Nonnull final ProductTypeDraft productTypeDraft) { + return createResource(productTypeDraft, ProductTypeCreateCommand::of); + } + + @Nonnull + @Override + public CompletionStage updateProductType( + @Nonnull final ProductType productType, + @Nonnull final List> updateActions) { + + return updateResource(productType, ProductTypeUpdateCommand::of, updateActions); + } + + @Nonnull + @Override + public CompletionStage> fetchProductType(@Nullable final String key) { + + return fetchResource( + key, + () -> + ProductTypeQueryBuilder.of() + .plusPredicates(queryModel -> queryModel.key().is(key)) + .build()); + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/ShoppingListServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/ShoppingListServiceImpl.java index 2465225fce..7ae769fe28 100644 --- a/src/main/java/com/commercetools/sync/services/impl/ShoppingListServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/ShoppingListServiceImpl.java @@ -13,71 +13,83 @@ import io.sphere.sdk.shoppinglists.expansion.ShoppingListExpansionModel; import io.sphere.sdk.shoppinglists.queries.ShoppingListQuery; import io.sphere.sdk.shoppinglists.queries.ShoppingListQueryModel; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -/** - * Implementation of ShoppingListService interface. - */ -public final class ShoppingListServiceImpl extends BaseService> +/** Implementation of ShoppingListService interface. */ +public final class ShoppingListServiceImpl + extends BaseService< + ShoppingListDraft, + ShoppingList, + ShoppingListSyncOptions, + ShoppingListQuery, + ShoppingListQueryModel, + ShoppingListExpansionModel> implements ShoppingListService { - public ShoppingListServiceImpl(@Nonnull final ShoppingListSyncOptions syncOptions) { - super(syncOptions); - } + public ShoppingListServiceImpl(@Nonnull final ShoppingListSyncOptions syncOptions) { + super(syncOptions); + } - @Nonnull - @Override - public CompletionStage> cacheKeysToIds(@Nonnull final Set shoppingListKeys) { + @Nonnull + @Override + public CompletionStage> cacheKeysToIds( + @Nonnull final Set shoppingListKeys) { - return cacheKeysToIdsUsingGraphQl( - shoppingListKeys, resource -> Collections.singletonMap(resource.getKey(), resource.getId()), - keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.SHOPPING_LISTS)); - } + return cacheKeysToIdsUsingGraphQl( + shoppingListKeys, + resource -> Collections.singletonMap(resource.getKey(), resource.getId()), + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.SHOPPING_LISTS)); + } - @Nonnull - @Override - public CompletionStage> fetchMatchingShoppingListsByKeys(@Nonnull final Set keys) { + @Nonnull + @Override + public CompletionStage> fetchMatchingShoppingListsByKeys( + @Nonnull final Set keys) { - return fetchMatchingResources(keys, ShoppingList::getKey, - () -> ShoppingListQuery - .of() + return fetchMatchingResources( + keys, + ShoppingList::getKey, + () -> + ShoppingListQuery.of() .plusPredicates(queryModel -> queryModel.key().isIn(keys)) .plusExpansionPaths(ExpansionPath.of("lineItems[*].variant"))); - } + } - @Nonnull - @Override - public CompletionStage> fetchShoppingList(@Nullable final String key) { + @Nonnull + @Override + public CompletionStage> fetchShoppingList(@Nullable final String key) { - return fetchResource(key, - () -> ShoppingListQuery.of() - .plusPredicates(queryModel -> queryModel.key().is(key)) - .plusExpansionPaths(ExpansionPath.of("lineItems[*].variant"))); - } + return fetchResource( + key, + () -> + ShoppingListQuery.of() + .plusPredicates(queryModel -> queryModel.key().is(key)) + .plusExpansionPaths(ExpansionPath.of("lineItems[*].variant"))); + } - @Nonnull - @Override - public CompletionStage> createShoppingList( - @Nonnull final ShoppingListDraft shoppingListDraft) { + @Nonnull + @Override + public CompletionStage> createShoppingList( + @Nonnull final ShoppingListDraft shoppingListDraft) { - return createResource(shoppingListDraft, ShoppingListDraft::getKey, ShoppingListCreateCommand::of); - } + return createResource( + shoppingListDraft, ShoppingListDraft::getKey, ShoppingListCreateCommand::of); + } - @Nonnull - @Override - public CompletionStage updateShoppingList( - @Nonnull final ShoppingList shoppingList, - @Nonnull final List> updateActions) { + @Nonnull + @Override + public CompletionStage updateShoppingList( + @Nonnull final ShoppingList shoppingList, + @Nonnull final List> updateActions) { - return updateResource(shoppingList, ShoppingListUpdateCommand::of, updateActions); - } + return updateResource(shoppingList, ShoppingListUpdateCommand::of, updateActions); + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/StateServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/StateServiceImpl.java index 1f6cb9a770..87dd67182a 100644 --- a/src/main/java/com/commercetools/sync/services/impl/StateServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/StateServiceImpl.java @@ -1,5 +1,7 @@ package com.commercetools.sync.services.impl; +import static java.util.Collections.singleton; + import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.GraphQlQueryResources; import com.commercetools.sync.services.StateService; @@ -13,90 +15,99 @@ import io.sphere.sdk.states.queries.StateQuery; import io.sphere.sdk.states.queries.StateQueryBuilder; import io.sphere.sdk.states.queries.StateQueryModel; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; - -import static java.util.Collections.singleton; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class StateServiceImpl - extends BaseServiceWithKey> implements StateService { + extends BaseServiceWithKey< + StateDraft, + State, + StateSyncOptions, + StateQuery, + StateQueryModel, + StateExpansionModel> + implements StateService { - private static final String STATE_KEY_NOT_SET = "State with id: '%s' has no key set. Keys are required for " - + "state matching."; + private static final String STATE_KEY_NOT_SET = + "State with id: '%s' has no key set. Keys are required for " + "state matching."; - public StateServiceImpl(@Nonnull final StateSyncOptions syncOptions) { - super(syncOptions); - } + public StateServiceImpl(@Nonnull final StateSyncOptions syncOptions) { + super(syncOptions); + } - @Nonnull - @Override - public CompletionStage> cacheKeysToIds(@Nonnull final Set keys) { + @Nonnull + @Override + public CompletionStage> cacheKeysToIds(@Nonnull final Set keys) { - return cacheKeysToIds( - keys, - keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.STATES)); - } + return cacheKeysToIds( + keys, + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.STATES)); + } - @Nonnull - @Override - public CompletionStage> fetchCachedStateId(@Nullable final String key) { - return fetchCachedResourceId(key, - () -> StateQueryBuilder - .of() + @Nonnull + @Override + public CompletionStage> fetchCachedStateId(@Nullable final String key) { + return fetchCachedResourceId( + key, + () -> + StateQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().isIn(singleton(key))) .build()); - } + } - @Nonnull - @Override - public CompletionStage> fetchMatchingStatesByKeys(@Nonnull final Set stateKeys) { - return fetchMatchingStates(stateKeys, false); - } + @Nonnull + @Override + public CompletionStage> fetchMatchingStatesByKeys( + @Nonnull final Set stateKeys) { + return fetchMatchingStates(stateKeys, false); + } - @Nonnull - @Override - public CompletionStage> fetchMatchingStatesByKeysWithTransitions(@Nonnull final Set stateKeys) { - return fetchMatchingStates(stateKeys, true); - } + @Nonnull + @Override + public CompletionStage> fetchMatchingStatesByKeysWithTransitions( + @Nonnull final Set stateKeys) { + return fetchMatchingStates(stateKeys, true); + } - private CompletionStage> fetchMatchingStates(@Nonnull final Set stateKeys, - final boolean withTransitions) { - return fetchMatchingResources(stateKeys, - () -> { - StateQuery stateQuery = StateQuery.of() - .plusPredicates(stateQueryModel -> stateQueryModel.key().isIn(stateKeys)); - if (withTransitions) { - stateQuery = stateQuery.withExpansionPaths(StateExpansionModel::transitions); - } - return stateQuery; - }); - } + private CompletionStage> fetchMatchingStates( + @Nonnull final Set stateKeys, final boolean withTransitions) { + return fetchMatchingResources( + stateKeys, + () -> { + StateQuery stateQuery = + StateQuery.of() + .plusPredicates(stateQueryModel -> stateQueryModel.key().isIn(stateKeys)); + if (withTransitions) { + stateQuery = stateQuery.withExpansionPaths(StateExpansionModel::transitions); + } + return stateQuery; + }); + } - @Nonnull - @Override - public CompletionStage> fetchState(@Nullable final String key) { - return fetchResource(key, - () -> StateQuery.of().plusPredicates(stateQueryModel -> stateQueryModel.key().is(key))); - } + @Nonnull + @Override + public CompletionStage> fetchState(@Nullable final String key) { + return fetchResource( + key, + () -> StateQuery.of().plusPredicates(stateQueryModel -> stateQueryModel.key().is(key))); + } - @Nonnull - @Override - public CompletionStage> createState(@Nonnull final StateDraft stateDraft) { - return createResource(stateDraft, StateCreateCommand::of); - } + @Nonnull + @Override + public CompletionStage> createState(@Nonnull final StateDraft stateDraft) { + return createResource(stateDraft, StateCreateCommand::of); + } - @Nonnull - @Override - public CompletionStage updateState( - @Nonnull final State state, - @Nonnull final List> updateActions) { - return updateResource(state, StateUpdateCommand::of, updateActions); - } + @Nonnull + @Override + public CompletionStage updateState( + @Nonnull final State state, @Nonnull final List> updateActions) { + return updateResource(state, StateUpdateCommand::of, updateActions); + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/TaxCategoryServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/TaxCategoryServiceImpl.java index 5d5d871d5c..0b9c28ec20 100644 --- a/src/main/java/com/commercetools/sync/services/impl/TaxCategoryServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/TaxCategoryServiceImpl.java @@ -1,5 +1,7 @@ package com.commercetools.sync.services.impl; +import static java.util.Collections.singleton; + import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.GraphQlQueryResources; import com.commercetools.sync.services.TaxCategoryService; @@ -13,73 +15,85 @@ import io.sphere.sdk.taxcategories.queries.TaxCategoryQuery; import io.sphere.sdk.taxcategories.queries.TaxCategoryQueryBuilder; import io.sphere.sdk.taxcategories.queries.TaxCategoryQueryModel; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; - -import static java.util.Collections.singleton; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class TaxCategoryServiceImpl - extends BaseServiceWithKey> implements TaxCategoryService { + extends BaseServiceWithKey< + TaxCategoryDraft, + TaxCategory, + TaxCategorySyncOptions, + TaxCategoryQuery, + TaxCategoryQueryModel, + TaxCategoryExpansionModel> + implements TaxCategoryService { - public TaxCategoryServiceImpl(@Nonnull final TaxCategorySyncOptions syncOptions) { - super(syncOptions); - } + public TaxCategoryServiceImpl(@Nonnull final TaxCategorySyncOptions syncOptions) { + super(syncOptions); + } - @Nonnull - @Override - public CompletionStage> cacheKeysToIds(@Nonnull final Set taxCategoryKeys) { + @Nonnull + @Override + public CompletionStage> cacheKeysToIds( + @Nonnull final Set taxCategoryKeys) { - return cacheKeysToIds( - taxCategoryKeys, - keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.TAX_CATEGORIES)); - } + return cacheKeysToIds( + taxCategoryKeys, + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.TAX_CATEGORIES)); + } - @Nonnull - @Override - public CompletionStage> fetchCachedTaxCategoryId(@Nullable final String key) { - return fetchCachedResourceId( - key, - () -> TaxCategoryQueryBuilder - .of() + @Nonnull + @Override + public CompletionStage> fetchCachedTaxCategoryId(@Nullable final String key) { + return fetchCachedResourceId( + key, + () -> + TaxCategoryQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().isIn(singleton(key))) .build()); - } + } - @Nonnull - @Override - public CompletionStage> fetchMatchingTaxCategoriesByKeys(@Nonnull final Set keys) { - return fetchMatchingResources(keys, - () -> TaxCategoryQueryBuilder - .of() + @Nonnull + @Override + public CompletionStage> fetchMatchingTaxCategoriesByKeys( + @Nonnull final Set keys) { + return fetchMatchingResources( + keys, + () -> + TaxCategoryQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().isIn(keys)) .build()); - } + } - @Nonnull - @Override - public CompletionStage> fetchTaxCategory(@Nullable final String key) { - return fetchResource(key, - () -> TaxCategoryQueryBuilder.of().plusPredicates(queryModel -> queryModel.key().is(key)).build()); - } + @Nonnull + @Override + public CompletionStage> fetchTaxCategory(@Nullable final String key) { + return fetchResource( + key, + () -> + TaxCategoryQueryBuilder.of() + .plusPredicates(queryModel -> queryModel.key().is(key)) + .build()); + } - @Nonnull - @Override - public CompletionStage> createTaxCategory(@Nonnull final TaxCategoryDraft taxCategoryDraft) { - return createResource(taxCategoryDraft, TaxCategoryCreateCommand::of); - } + @Nonnull + @Override + public CompletionStage> createTaxCategory( + @Nonnull final TaxCategoryDraft taxCategoryDraft) { + return createResource(taxCategoryDraft, TaxCategoryCreateCommand::of); + } - @Nonnull - @Override - public CompletionStage updateTaxCategory( - @Nonnull final TaxCategory taxCategory, - @Nonnull final List> updateActions) { - return updateResource(taxCategory, TaxCategoryUpdateCommand::of, updateActions); - } + @Nonnull + @Override + public CompletionStage updateTaxCategory( + @Nonnull final TaxCategory taxCategory, + @Nonnull final List> updateActions) { + return updateResource(taxCategory, TaxCategoryUpdateCommand::of, updateActions); + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/TypeServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/TypeServiceImpl.java index dc6a650291..067cc43e38 100644 --- a/src/main/java/com/commercetools/sync/services/impl/TypeServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/TypeServiceImpl.java @@ -13,75 +13,74 @@ import io.sphere.sdk.types.queries.TypeQuery; import io.sphere.sdk.types.queries.TypeQueryBuilder; import io.sphere.sdk.types.queries.TypeQueryModel; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -/** - * Implementation of TypeService interface. - * TODO: USE graphQL to get only keys. GITHUB ISSUE#84 - */ -public final class TypeServiceImpl extends BaseServiceWithKey> implements TypeService { +/** Implementation of TypeService interface. TODO: USE graphQL to get only keys. GITHUB ISSUE#84 */ +public final class TypeServiceImpl + extends BaseServiceWithKey< + TypeDraft, Type, BaseSyncOptions, TypeQuery, TypeQueryModel, TypeExpansionModel> + implements TypeService { - public TypeServiceImpl(@Nonnull final BaseSyncOptions syncOptions) { - super(syncOptions); - } + public TypeServiceImpl(@Nonnull final BaseSyncOptions syncOptions) { + super(syncOptions); + } - @Nonnull - @Override - public CompletionStage> cacheKeysToIds(@Nonnull final Set typeKeys) { + @Nonnull + @Override + public CompletionStage> cacheKeysToIds(@Nonnull final Set typeKeys) { - return cacheKeysToIds( - typeKeys, keysNotCached -> new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.TYPES)); - } + return cacheKeysToIds( + typeKeys, + keysNotCached -> + new ResourceKeyIdGraphQlRequest(keysNotCached, GraphQlQueryResources.TYPES)); + } - @Nonnull - @Override - public CompletionStage> fetchCachedTypeId(@Nonnull final String key) { + @Nonnull + @Override + public CompletionStage> fetchCachedTypeId(@Nonnull final String key) { - return fetchCachedResourceId( - key, - () -> TypeQueryBuilder.of() - .plusPredicates(queryModel -> queryModel.key().is(key)) - .build()); - } + return fetchCachedResourceId( + key, + () -> TypeQueryBuilder.of().plusPredicates(queryModel -> queryModel.key().is(key)).build()); + } - @Nonnull - @Override - public CompletionStage> fetchMatchingTypesByKeys(@Nonnull final Set keys) { + @Nonnull + @Override + public CompletionStage> fetchMatchingTypesByKeys(@Nonnull final Set keys) { - return fetchMatchingResources(keys, - () -> TypeQueryBuilder - .of() + return fetchMatchingResources( + keys, + () -> + TypeQueryBuilder.of() .plusPredicates(queryModel -> queryModel.key().isIn(keys)) .build()); - } + } - @Nonnull - @Override - public CompletionStage> fetchType(@Nullable final String key) { + @Nonnull + @Override + public CompletionStage> fetchType(@Nullable final String key) { - return fetchResource(key, - () -> TypeQueryBuilder.of().plusPredicates(queryModel -> queryModel.key().is(key)).build()); - } + return fetchResource( + key, + () -> TypeQueryBuilder.of().plusPredicates(queryModel -> queryModel.key().is(key)).build()); + } - @Nonnull - @Override - public CompletionStage> createType(@Nonnull final TypeDraft typeDraft) { - return createResource(typeDraft, TypeCreateCommand::of); - } + @Nonnull + @Override + public CompletionStage> createType(@Nonnull final TypeDraft typeDraft) { + return createResource(typeDraft, TypeCreateCommand::of); + } - @Nonnull - @Override - public CompletionStage updateType( - @Nonnull final Type type, - @Nonnull final List> updateActions) { - return updateResource(type, TypeUpdateCommand::of, updateActions); - } + @Nonnull + @Override + public CompletionStage updateType( + @Nonnull final Type type, @Nonnull final List> updateActions) { + return updateResource(type, TypeUpdateCommand::of, updateActions); + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImpl.java index a0e3f98175..623c81e83c 100644 --- a/src/main/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImpl.java @@ -1,5 +1,9 @@ package com.commercetools.sync.services.impl; +import static java.lang.String.format; +import static java.util.stream.Collectors.toList; +import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.models.WaitingToBeResolved; import com.commercetools.sync.products.ProductSyncOptions; @@ -10,9 +14,6 @@ import io.sphere.sdk.customobjects.commands.CustomObjectUpsertCommand; import io.sphere.sdk.customobjects.queries.CustomObjectQuery; import io.sphere.sdk.queries.QueryExecutionUtils; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.HashSet; import java.util.Optional; @@ -20,100 +21,100 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; - -import static java.lang.String.format; -import static java.util.stream.Collectors.toList; -import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class UnresolvedReferencesServiceImpl implements UnresolvedReferencesService { - private final ProductSyncOptions syncOptions; + private final ProductSyncOptions syncOptions; - private static final String SAVE_FAILED = - "Failed to save CustomObject with key: '%s' (hash of product key: '%s')."; - private static final String DELETE_FAILED = - "Failed to delete CustomObject with key: '%s' (hash of product key: '%s')."; - public static final String CUSTOM_OBJECT_CONTAINER_KEY = - "commercetools-sync-java.UnresolvedReferencesService.productDrafts"; + private static final String SAVE_FAILED = + "Failed to save CustomObject with key: '%s' (hash of product key: '%s')."; + private static final String DELETE_FAILED = + "Failed to delete CustomObject with key: '%s' (hash of product key: '%s')."; + public static final String CUSTOM_OBJECT_CONTAINER_KEY = + "commercetools-sync-java.UnresolvedReferencesService.productDrafts"; + public UnresolvedReferencesServiceImpl(@Nonnull final ProductSyncOptions baseSyncOptions) { + this.syncOptions = baseSyncOptions; + } - public UnresolvedReferencesServiceImpl(@Nonnull final ProductSyncOptions baseSyncOptions) { - this.syncOptions = baseSyncOptions; - } + @Nonnull + private String hash(@Nullable final String customObjectKey) { + return sha1Hex(customObjectKey); + } - @Nonnull - private String hash(@Nullable final String customObjectKey) { - return sha1Hex(customObjectKey); - } + @Nonnull + @Override + public CompletionStage> fetch(@Nonnull final Set keys) { - @Nonnull - @Override - public CompletionStage> fetch(@Nonnull final Set keys) { - - if (keys.isEmpty()) { - return CompletableFuture.completedFuture(Collections.emptySet()); - } - - Set hashedKeys = keys.stream() - .map(this::hash).collect(Collectors.toSet()); - - final CustomObjectQuery customObjectQuery = - CustomObjectQuery - .of(WaitingToBeResolved.class) - .byContainer(CUSTOM_OBJECT_CONTAINER_KEY) - .plusPredicates(p -> p.key().isIn(hashedKeys)); - - return QueryExecutionUtils - .queryAll(syncOptions.getCtpClient(), customObjectQuery) - .thenApply(customObjects -> customObjects - .stream() - .map(CustomObject::getValue) - .collect(toList())) - .thenApply(HashSet::new); + if (keys.isEmpty()) { + return CompletableFuture.completedFuture(Collections.emptySet()); } - @Nonnull - @Override - public CompletionStage> save(@Nonnull final WaitingToBeResolved draft) { - - final CustomObjectDraft customObjectDraft = CustomObjectDraft - .ofUnversionedUpsert( - CUSTOM_OBJECT_CONTAINER_KEY, - hash(draft.getProductDraft().getKey()), - draft, - WaitingToBeResolved.class); - - return syncOptions - .getCtpClient() - .execute(CustomObjectUpsertCommand.of(customObjectDraft)) - .handle((resource, exception) -> { - if (exception == null) { - return Optional.of(resource.getValue()); - } else { - syncOptions.applyErrorCallback( - new SyncException(format(SAVE_FAILED, customObjectDraft.getKey(), - draft.getProductDraft().getKey()), exception)); - return Optional.empty(); - } + Set hashedKeys = keys.stream().map(this::hash).collect(Collectors.toSet()); + + final CustomObjectQuery customObjectQuery = + CustomObjectQuery.of(WaitingToBeResolved.class) + .byContainer(CUSTOM_OBJECT_CONTAINER_KEY) + .plusPredicates(p -> p.key().isIn(hashedKeys)); + + return QueryExecutionUtils.queryAll(syncOptions.getCtpClient(), customObjectQuery) + .thenApply( + customObjects -> customObjects.stream().map(CustomObject::getValue).collect(toList())) + .thenApply(HashSet::new); + } + + @Nonnull + @Override + public CompletionStage> save( + @Nonnull final WaitingToBeResolved draft) { + + final CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert( + CUSTOM_OBJECT_CONTAINER_KEY, + hash(draft.getProductDraft().getKey()), + draft, + WaitingToBeResolved.class); + + return syncOptions + .getCtpClient() + .execute(CustomObjectUpsertCommand.of(customObjectDraft)) + .handle( + (resource, exception) -> { + if (exception == null) { + return Optional.of(resource.getValue()); + } else { + syncOptions.applyErrorCallback( + new SyncException( + format( + SAVE_FAILED, + customObjectDraft.getKey(), + draft.getProductDraft().getKey()), + exception)); + return Optional.empty(); + } }); - } - - @Nonnull - @Override - public CompletionStage> delete(@Nonnull final String key) { - - return syncOptions - .getCtpClient() - .execute(CustomObjectDeleteCommand - .of(CUSTOM_OBJECT_CONTAINER_KEY, hash(key), WaitingToBeResolved.class)) - .handle((resource, exception) -> { - if (exception == null) { - return Optional.of(resource.getValue()); - } else { - syncOptions.applyErrorCallback( - new SyncException(format(DELETE_FAILED, hash(key), key), exception)); - return Optional.empty(); - } + } + + @Nonnull + @Override + public CompletionStage> delete(@Nonnull final String key) { + + return syncOptions + .getCtpClient() + .execute( + CustomObjectDeleteCommand.of( + CUSTOM_OBJECT_CONTAINER_KEY, hash(key), WaitingToBeResolved.class)) + .handle( + (resource, exception) -> { + if (exception == null) { + return Optional.of(resource.getValue()); + } else { + syncOptions.applyErrorCallback( + new SyncException(format(DELETE_FAILED, hash(key), key), exception)); + return Optional.empty(); + } }); - } + } } diff --git a/src/main/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImpl.java b/src/main/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImpl.java index c3c909bb38..64c62e1dd0 100644 --- a/src/main/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImpl.java +++ b/src/main/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImpl.java @@ -1,5 +1,9 @@ package com.commercetools.sync.services.impl; +import static java.lang.String.format; +import static java.util.stream.Collectors.toList; +import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.models.WaitingToBeResolvedTransitions; import com.commercetools.sync.services.UnresolvedTransitionsService; @@ -10,9 +14,6 @@ import io.sphere.sdk.customobjects.commands.CustomObjectUpsertCommand; import io.sphere.sdk.customobjects.queries.CustomObjectQuery; import io.sphere.sdk.queries.QueryExecutionUtils; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.HashSet; import java.util.Optional; @@ -20,100 +21,102 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; - -import static java.lang.String.format; -import static java.util.stream.Collectors.toList; -import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class UnresolvedTransitionsServiceImpl implements UnresolvedTransitionsService { - private final StateSyncOptions syncOptions; + private final StateSyncOptions syncOptions; - private static final String SAVE_FAILED = - "Failed to save CustomObject with key: '%s' (hash of state key: '%s')."; - private static final String DELETE_FAILED = - "Failed to delete CustomObject with key: '%s' (hash of state key: '%s')."; - public static final String CUSTOM_OBJECT_CONTAINER_KEY = - "commercetools-sync-java.UnresolvedTransitionsService.stateDrafts"; + private static final String SAVE_FAILED = + "Failed to save CustomObject with key: '%s' (hash of state key: '%s')."; + private static final String DELETE_FAILED = + "Failed to delete CustomObject with key: '%s' (hash of state key: '%s')."; + public static final String CUSTOM_OBJECT_CONTAINER_KEY = + "commercetools-sync-java.UnresolvedTransitionsService.stateDrafts"; + public UnresolvedTransitionsServiceImpl(@Nonnull final StateSyncOptions baseSyncOptions) { + this.syncOptions = baseSyncOptions; + } - public UnresolvedTransitionsServiceImpl(@Nonnull final StateSyncOptions baseSyncOptions) { - this.syncOptions = baseSyncOptions; - } - - @Nonnull - private String hash(@Nullable final String customObjectKey) { - return sha1Hex(customObjectKey); - } + @Nonnull + private String hash(@Nullable final String customObjectKey) { + return sha1Hex(customObjectKey); + } - @Nonnull - @Override - public CompletionStage> fetch(@Nonnull final Set keys) { - - if (keys.isEmpty()) { - return CompletableFuture.completedFuture(Collections.emptySet()); - } - - Set hashedKeys = keys.stream() - .map(this::hash).collect(Collectors.toSet()); - - final CustomObjectQuery customObjectQuery = - CustomObjectQuery - .of(WaitingToBeResolvedTransitions.class) - .byContainer(CUSTOM_OBJECT_CONTAINER_KEY) - .plusPredicates(p -> p.key().isIn(hashedKeys)); - - return QueryExecutionUtils - .queryAll(syncOptions.getCtpClient(), customObjectQuery) - .thenApply(customObjects -> customObjects - .stream() - .map(CustomObject::getValue) - .collect(toList())) - .thenApply(HashSet::new); - } + @Nonnull + @Override + public CompletionStage> fetch( + @Nonnull final Set keys) { - @Nonnull - @Override - public CompletionStage> save( - @Nonnull final WaitingToBeResolvedTransitions draft) { - - final CustomObjectDraft customObjectDraft = CustomObjectDraft - .ofUnversionedUpsert( - CUSTOM_OBJECT_CONTAINER_KEY, - hash(draft.getStateDraft().getKey()), - draft, - WaitingToBeResolvedTransitions.class); - - return syncOptions - .getCtpClient() - .execute(CustomObjectUpsertCommand.of(customObjectDraft)) - .handle((resource, exception) -> { - if (exception == null) { - return Optional.of(resource.getValue()); - } else { - syncOptions.applyErrorCallback(new SyncException(format(SAVE_FAILED, customObjectDraft.getKey(), - draft.getStateDraft().getKey()), exception)); - return Optional.empty(); - } - }); + if (keys.isEmpty()) { + return CompletableFuture.completedFuture(Collections.emptySet()); } - @Nonnull - @Override - public CompletionStage> delete(@Nonnull final String key) { - - return syncOptions - .getCtpClient() - .execute(CustomObjectDeleteCommand - .of(CUSTOM_OBJECT_CONTAINER_KEY, hash(key), WaitingToBeResolvedTransitions.class)) - .handle((resource, exception) -> { - if (exception == null) { - return Optional.of(resource.getValue()); - } else { - syncOptions.applyErrorCallback( new SyncException( format(DELETE_FAILED, hash(key), key), + Set hashedKeys = keys.stream().map(this::hash).collect(Collectors.toSet()); + + final CustomObjectQuery customObjectQuery = + CustomObjectQuery.of(WaitingToBeResolvedTransitions.class) + .byContainer(CUSTOM_OBJECT_CONTAINER_KEY) + .plusPredicates(p -> p.key().isIn(hashedKeys)); + + return QueryExecutionUtils.queryAll(syncOptions.getCtpClient(), customObjectQuery) + .thenApply( + customObjects -> customObjects.stream().map(CustomObject::getValue).collect(toList())) + .thenApply(HashSet::new); + } + + @Nonnull + @Override + public CompletionStage> save( + @Nonnull final WaitingToBeResolvedTransitions draft) { + + final CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert( + CUSTOM_OBJECT_CONTAINER_KEY, + hash(draft.getStateDraft().getKey()), + draft, + WaitingToBeResolvedTransitions.class); + + return syncOptions + .getCtpClient() + .execute(CustomObjectUpsertCommand.of(customObjectDraft)) + .handle( + (resource, exception) -> { + if (exception == null) { + return Optional.of(resource.getValue()); + } else { + syncOptions.applyErrorCallback( + new SyncException( + format( + SAVE_FAILED, + customObjectDraft.getKey(), + draft.getStateDraft().getKey()), exception)); - return Optional.empty(); - } + return Optional.empty(); + } }); - } + } + + @Nonnull + @Override + public CompletionStage> delete( + @Nonnull final String key) { + + return syncOptions + .getCtpClient() + .execute( + CustomObjectDeleteCommand.of( + CUSTOM_OBJECT_CONTAINER_KEY, hash(key), WaitingToBeResolvedTransitions.class)) + .handle( + (resource, exception) -> { + if (exception == null) { + return Optional.of(resource.getValue()); + } else { + syncOptions.applyErrorCallback( + new SyncException(format(DELETE_FAILED, hash(key), key), exception)); + return Optional.empty(); + } + }); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSync.java b/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSync.java index d512d02ca7..676129b432 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSync.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSync.java @@ -1,5 +1,13 @@ package com.commercetools.sync.shoppinglists; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListSyncUtils.buildActions; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; + import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; @@ -15,283 +23,315 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.shoppinglists.ShoppingList; import io.sphere.sdk.shoppinglists.ShoppingListDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListSyncUtils.buildActions; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; /** - * This class syncs shopping list drafts with corresponding shopping list resources in the CTP project. + * This class syncs shopping list drafts with corresponding shopping list resources in the CTP + * project. */ -public class ShoppingListSync extends BaseSync { - - private static final String CTP_SHOPPING_LIST_UPDATE_FAILED = - "Failed to update shopping lists with key: '%s'. Reason: %s"; - private static final String CTP_SHOPPING_LIST_FETCH_FAILED = "Failed to fetch existing shopping lists with keys: " - + "'%s'."; - private static final String FAILED_TO_PROCESS = "Failed to process the ShoppingListDraft with key:'%s'. Reason: %s"; - - - private final ShoppingListService shoppingListService; - private final ShoppingListReferenceResolver shoppingListReferenceResolver; - private final ShoppingListBatchValidator shoppingListBatchValidator; - - /** - * Takes a {@link ShoppingListSyncOptions} to instantiate a new {@link ShoppingListSync} instance that could be used - * to sync shopping list drafts in the CTP project specified in the injected {@link ShoppingListSyncOptions} - * instance. - * - * @param shoppingListSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - */ - public ShoppingListSync(@Nonnull final ShoppingListSyncOptions shoppingListSyncOptions) { - - this(shoppingListSyncOptions, - new ShoppingListServiceImpl(shoppingListSyncOptions), - new CustomerServiceImpl(CustomerSyncOptionsBuilder.of(shoppingListSyncOptions.getCtpClient()).build()), - new TypeServiceImpl(shoppingListSyncOptions)); +public class ShoppingListSync + extends BaseSync { + + private static final String CTP_SHOPPING_LIST_UPDATE_FAILED = + "Failed to update shopping lists with key: '%s'. Reason: %s"; + private static final String CTP_SHOPPING_LIST_FETCH_FAILED = + "Failed to fetch existing shopping lists with keys: " + "'%s'."; + private static final String FAILED_TO_PROCESS = + "Failed to process the ShoppingListDraft with key:'%s'. Reason: %s"; + + private final ShoppingListService shoppingListService; + private final ShoppingListReferenceResolver shoppingListReferenceResolver; + private final ShoppingListBatchValidator shoppingListBatchValidator; + + /** + * Takes a {@link ShoppingListSyncOptions} to instantiate a new {@link ShoppingListSync} instance + * that could be used to sync shopping list drafts in the CTP project specified in the injected + * {@link ShoppingListSyncOptions} instance. + * + * @param shoppingListSyncOptions the container of all the options of the sync process including + * the CTP project client and/or configuration and other sync-specific options. + */ + public ShoppingListSync(@Nonnull final ShoppingListSyncOptions shoppingListSyncOptions) { + + this( + shoppingListSyncOptions, + new ShoppingListServiceImpl(shoppingListSyncOptions), + new CustomerServiceImpl( + CustomerSyncOptionsBuilder.of(shoppingListSyncOptions.getCtpClient()).build()), + new TypeServiceImpl(shoppingListSyncOptions)); + } + + /** + * Takes a {@link ShoppingListSyncOptions} and service instances to instantiate a new {@link + * ShoppingListSync} instance that could be used to sync shopping list drafts in the CTP project + * specified in the injected {@link ShoppingListSyncOptions} instance. + * + *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and + * passed to. + * + * @param syncOptions the container of all the options of the sync process including the CTP + * project client and/or configuration and other sync-specific options. + * @param shoppingListService the shopping list service which is responsible for fetching/caching + * the ShoppingLists from the CTP project. + * @param customerService the customer service which is responsible for fetching/caching the + * Customers from the CTP project. + * @param typeService the type service which is responsible for fetching/caching the Types from + * the CTP project. + */ + protected ShoppingListSync( + @Nonnull final ShoppingListSyncOptions syncOptions, + @Nonnull final ShoppingListService shoppingListService, + @Nonnull final CustomerService customerService, + @Nonnull final TypeService typeService) { + + super(new ShoppingListSyncStatistics(), syncOptions); + this.shoppingListService = shoppingListService; + this.shoppingListReferenceResolver = + new ShoppingListReferenceResolver(getSyncOptions(), customerService, typeService); + this.shoppingListBatchValidator = + new ShoppingListBatchValidator(getSyncOptions(), getStatistics()); + } + + /** + * Iterates through the whole {@code ShoppingListDraft}'s list and accumulates its valid drafts to + * batches. Every batch is then processed by {@link ShoppingListSync#processBatch(List)}. + * + *

Inherited doc: {@inheritDoc} + * + * @param shoppingListDrafts {@link List} of {@link ShoppingListDraft}'s that would be synced into + * CTP project. + * @return {@link CompletionStage} with {@link ShoppingListSyncStatistics} holding statistics of + * all sync processes performed by this sync instance. + */ + @Override + protected CompletionStage process( + @Nonnull final List shoppingListDrafts) { + + List> batches = + batchElements(shoppingListDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, completedFuture(statistics)); + } + + @Override + protected CompletionStage processBatch( + @Nonnull final List batch) { + + ImmutablePair, ShoppingListBatchValidator.ReferencedKeys> + validationResult = shoppingListBatchValidator.validateAndCollectReferencedKeys(batch); + + final Set shoppingListDrafts = validationResult.getLeft(); + if (shoppingListDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return completedFuture(statistics); } - /** - * Takes a {@link ShoppingListSyncOptions} and service instances to instantiate a new {@link ShoppingListSync} - * instance that could be used to sync shopping list drafts in the CTP project specified in the injected - * {@link ShoppingListSyncOptions} instance. - * - *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and passed to. - * - * @param syncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param shoppingListService the shopping list service which is responsible for fetching/caching the - * ShoppingLists from the CTP project. - * @param customerService the customer service which is responsible for fetching/caching the Customers from the - * CTP project. - * @param typeService the type service which is responsible for fetching/caching the Types from the CTP project. - */ - protected ShoppingListSync(@Nonnull final ShoppingListSyncOptions syncOptions, - @Nonnull final ShoppingListService shoppingListService, - @Nonnull final CustomerService customerService, - @Nonnull final TypeService typeService) { - - super(new ShoppingListSyncStatistics(), syncOptions); - this.shoppingListService = shoppingListService; - this.shoppingListReferenceResolver = new ShoppingListReferenceResolver(getSyncOptions(), customerService, - typeService); - this.shoppingListBatchValidator = new ShoppingListBatchValidator(getSyncOptions(), getStatistics()); - } - - /** - * Iterates through the whole {@code ShoppingListDraft}'s list and accumulates its valid drafts to batches. - * Every batch is then processed by {@link ShoppingListSync#processBatch(List)}. - * - *

Inherited doc: - * {@inheritDoc} - * - * @param shoppingListDrafts {@link List} of {@link ShoppingListDraft}'s that would be synced into CTP project. - * @return {@link CompletionStage} with {@link ShoppingListSyncStatistics} holding statistics of all sync - * processes performed by this sync instance. - */ - @Override - protected CompletionStage process( - @Nonnull final List shoppingListDrafts) { - - List> batches = batchElements(shoppingListDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, completedFuture(statistics)); - } - - @Override - protected CompletionStage processBatch(@Nonnull final List batch) { - - ImmutablePair, ShoppingListBatchValidator.ReferencedKeys> - validationResult = shoppingListBatchValidator.validateAndCollectReferencedKeys(batch); - - final Set shoppingListDrafts = validationResult.getLeft(); - if (shoppingListDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return completedFuture(statistics); - } - - return shoppingListReferenceResolver - .populateKeyToIdCachesForReferencedKeys(validationResult.getRight()) - .handle(ImmutablePair::new) - .thenCompose(cachingResponse -> { - final Throwable cachingException = cachingResponse.getRight(); - if (cachingException != null) { - handleError(new SyncException("Failed to build a cache of keys to ids.", cachingException), - shoppingListDrafts.size()); - return CompletableFuture.completedFuture(null); - } - - final Set shoppingListDraftKeys = - shoppingListDrafts.stream().map(ShoppingListDraft::getKey).collect(toSet()); - - return shoppingListService - .fetchMatchingShoppingListsByKeys(shoppingListDraftKeys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { + return shoppingListReferenceResolver + .populateKeyToIdCachesForReferencedKeys(validationResult.getRight()) + .handle(ImmutablePair::new) + .thenCompose( + cachingResponse -> { + final Throwable cachingException = cachingResponse.getRight(); + if (cachingException != null) { + handleError( + new SyncException("Failed to build a cache of keys to ids.", cachingException), + shoppingListDrafts.size()); + return CompletableFuture.completedFuture(null); + } + + final Set shoppingListDraftKeys = + shoppingListDrafts.stream().map(ShoppingListDraft::getKey).collect(toSet()); + + return shoppingListService + .fetchMatchingShoppingListsByKeys(shoppingListDraftKeys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { final Set fetchedShoppingLists = fetchResponse.getLeft(); final Throwable exception = fetchResponse.getRight(); if (exception != null) { - final String errorMessage = format(CTP_SHOPPING_LIST_FETCH_FAILED, shoppingListDraftKeys); - handleError(new SyncException(errorMessage, exception), shoppingListDraftKeys.size()); - return CompletableFuture.completedFuture(null); + final String errorMessage = + format(CTP_SHOPPING_LIST_FETCH_FAILED, shoppingListDraftKeys); + handleError( + new SyncException(errorMessage, exception), + shoppingListDraftKeys.size()); + return CompletableFuture.completedFuture(null); } else { - return syncBatch(fetchedShoppingLists, shoppingListDrafts); + return syncBatch(fetchedShoppingLists, shoppingListDrafts); } - }); + }); }) - .thenApply(ignoredResult -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignoredResult -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); - } - - @Nonnull - private CompletionStage syncBatch(@Nonnull final Set oldShoppingLists, - @Nonnull final Set newShoppingListDrafts) { - - Map keyShoppingListsMap = oldShoppingLists - .stream() - .collect(toMap(ShoppingList::getKey, identity())); - - return CompletableFuture.allOf(newShoppingListDrafts - .stream() - .map(shoppingListDraft -> - shoppingListReferenceResolver - .resolveReferences(shoppingListDraft) - .thenCompose(resolvedShoppingListDraft -> syncDraft(keyShoppingListsMap, resolvedShoppingListDraft)) - .exceptionally(completionException -> { - final String errorMessage = format(FAILED_TO_PROCESS, shoppingListDraft.getKey(), - completionException.getMessage()); - handleError(new SyncException(errorMessage, completionException), 1); - return null; - }) - ) + } + + @Nonnull + private CompletionStage syncBatch( + @Nonnull final Set oldShoppingLists, + @Nonnull final Set newShoppingListDrafts) { + + Map keyShoppingListsMap = + oldShoppingLists.stream().collect(toMap(ShoppingList::getKey, identity())); + + return CompletableFuture.allOf( + newShoppingListDrafts.stream() + .map( + shoppingListDraft -> + shoppingListReferenceResolver + .resolveReferences(shoppingListDraft) + .thenCompose( + resolvedShoppingListDraft -> + syncDraft(keyShoppingListsMap, resolvedShoppingListDraft)) + .exceptionally( + completionException -> { + final String errorMessage = + format( + FAILED_TO_PROCESS, + shoppingListDraft.getKey(), + completionException.getMessage()); + handleError(new SyncException(errorMessage, completionException), 1); + return null; + })) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); - } - - @Nonnull - private CompletionStage syncDraft(@Nonnull final Map keyShoppingListMap, - @Nonnull final ShoppingListDraft newShoppingListDraft) { + } - final ShoppingList shoppingListFromMap = keyShoppingListMap.get(newShoppingListDraft.getKey()); - return Optional.ofNullable(shoppingListFromMap) - .map(oldShoppingList -> buildActionsAndUpdate(oldShoppingList, newShoppingListDraft)) - .orElseGet(() -> applyCallbackAndCreate(newShoppingListDraft)); - } + @Nonnull + private CompletionStage syncDraft( + @Nonnull final Map keyShoppingListMap, + @Nonnull final ShoppingListDraft newShoppingListDraft) { - @Nonnull - private CompletionStage buildActionsAndUpdate( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingListDraft) { + final ShoppingList shoppingListFromMap = keyShoppingListMap.get(newShoppingListDraft.getKey()); + return Optional.ofNullable(shoppingListFromMap) + .map(oldShoppingList -> buildActionsAndUpdate(oldShoppingList, newShoppingListDraft)) + .orElseGet(() -> applyCallbackAndCreate(newShoppingListDraft)); + } - final List> updateActions = - buildActions(oldShoppingList, newShoppingListDraft, syncOptions); + @Nonnull + private CompletionStage buildActionsAndUpdate( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingListDraft) { - final List> updateActionsAfterCallback - = syncOptions.applyBeforeUpdateCallback(updateActions, newShoppingListDraft, oldShoppingList); + final List> updateActions = + buildActions(oldShoppingList, newShoppingListDraft, syncOptions); - if (!updateActionsAfterCallback.isEmpty()) { - return updateShoppinglist(oldShoppingList, newShoppingListDraft, updateActionsAfterCallback); - } + final List> updateActionsAfterCallback = + syncOptions.applyBeforeUpdateCallback(updateActions, newShoppingListDraft, oldShoppingList); - return completedFuture(null); + if (!updateActionsAfterCallback.isEmpty()) { + return updateShoppinglist(oldShoppingList, newShoppingListDraft, updateActionsAfterCallback); } - @Nonnull - private CompletionStage updateShoppinglist( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingListDraft, - @Nonnull final List> updateActionsAfterCallback) { - - return shoppingListService - .updateShoppingList(oldShoppingList, updateActionsAfterCallback) - .handle(ImmutablePair::of) - .thenCompose(updateResponse -> { - final Throwable exception = updateResponse.getValue(); - if (exception != null) { - return executeSupplierIfConcurrentModificationException(exception, - () -> fetchAndUpdate(oldShoppingList, newShoppingListDraft), - () -> { - final String errorMessage = - format(CTP_SHOPPING_LIST_UPDATE_FAILED, - newShoppingListDraft.getKey(), - exception.getMessage()); - handleError(new SyncException(errorMessage, exception), 1); - return CompletableFuture.completedFuture(null); - }); - } else { - statistics.incrementUpdated(); - return CompletableFuture.completedFuture(null); - } - }); - } - - @Nonnull - private CompletionStage fetchAndUpdate( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingListDraft) { - - final String shoppingListKey = oldShoppingList.getKey(); - return shoppingListService - .fetchShoppingList(shoppingListKey) - .handle(ImmutablePair::of) - .thenCompose(fetchResponse -> { - final Optional fetchedShoppingListOptional = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(CTP_SHOPPING_LIST_UPDATE_FAILED, shoppingListKey, - "Failed to fetch from CTP while retrying after concurrency modification."); - handleError(new SyncException(errorMessage, exception), 1); - return CompletableFuture.completedFuture(null); - } - - return fetchedShoppingListOptional - .map(fetchedShoppingList -> buildActionsAndUpdate(fetchedShoppingList, newShoppingListDraft)) - .orElseGet(() -> { - final String errorMessage = format(CTP_SHOPPING_LIST_UPDATE_FAILED, shoppingListKey, + return completedFuture(null); + } + + @Nonnull + private CompletionStage updateShoppinglist( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingListDraft, + @Nonnull final List> updateActionsAfterCallback) { + + return shoppingListService + .updateShoppingList(oldShoppingList, updateActionsAfterCallback) + .handle(ImmutablePair::of) + .thenCompose( + updateResponse -> { + final Throwable exception = updateResponse.getValue(); + if (exception != null) { + return executeSupplierIfConcurrentModificationException( + exception, + () -> fetchAndUpdate(oldShoppingList, newShoppingListDraft), + () -> { + final String errorMessage = + format( + CTP_SHOPPING_LIST_UPDATE_FAILED, + newShoppingListDraft.getKey(), + exception.getMessage()); + handleError(new SyncException(errorMessage, exception), 1); + return CompletableFuture.completedFuture(null); + }); + } else { + statistics.incrementUpdated(); + return CompletableFuture.completedFuture(null); + } + }); + } + + @Nonnull + private CompletionStage fetchAndUpdate( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingListDraft) { + + final String shoppingListKey = oldShoppingList.getKey(); + return shoppingListService + .fetchShoppingList(shoppingListKey) + .handle(ImmutablePair::of) + .thenCompose( + fetchResponse -> { + final Optional fetchedShoppingListOptional = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + CTP_SHOPPING_LIST_UPDATE_FAILED, + shoppingListKey, + "Failed to fetch from CTP while retrying after concurrency modification."); + handleError(new SyncException(errorMessage, exception), 1); + return CompletableFuture.completedFuture(null); + } + + return fetchedShoppingListOptional + .map( + fetchedShoppingList -> + buildActionsAndUpdate(fetchedShoppingList, newShoppingListDraft)) + .orElseGet( + () -> { + final String errorMessage = + format( + CTP_SHOPPING_LIST_UPDATE_FAILED, + shoppingListKey, "Not found when attempting to fetch while retrying after concurrency modification."); handleError(new SyncException(errorMessage, null), 1); return CompletableFuture.completedFuture(null); - }); + }); }); - } - - @Nonnull - private CompletionStage applyCallbackAndCreate(@Nonnull final ShoppingListDraft shoppingListDraft) { - - return syncOptions - .applyBeforeCreateCallback(shoppingListDraft) - .map(draft -> shoppingListService - .createShoppingList(draft) - .thenAccept(optional -> { - if (optional.isPresent()) { - statistics.incrementCreated(); - } else { - statistics.incrementFailed(); - } - })) - .orElseGet(() -> CompletableFuture.completedFuture(null)); - } - - private void handleError(@Nonnull final SyncException syncException, final int failedTimes) { - - syncOptions.applyErrorCallback(syncException); - statistics.incrementFailed(failedTimes); - } + } + + @Nonnull + private CompletionStage applyCallbackAndCreate( + @Nonnull final ShoppingListDraft shoppingListDraft) { + + return syncOptions + .applyBeforeCreateCallback(shoppingListDraft) + .map( + draft -> + shoppingListService + .createShoppingList(draft) + .thenAccept( + optional -> { + if (optional.isPresent()) { + statistics.incrementCreated(); + } else { + statistics.incrementFailed(); + } + })) + .orElseGet(() -> CompletableFuture.completedFuture(null)); + } + + private void handleError(@Nonnull final SyncException syncException, final int failedTimes) { + + syncOptions.applyErrorCallback(syncException); + statistics.incrementFailed(failedTimes); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptions.java b/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptions.java index a2974da050..b460f73dd5 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptions.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptions.java @@ -9,36 +9,44 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.shoppinglists.ShoppingList; import io.sphere.sdk.shoppinglists.ShoppingListDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -public final class ShoppingListSyncOptions extends BaseSyncOptions { - - ShoppingListSyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallback, - @Nullable final TriConsumer, Optional> - warningCallback, - final int batchSize, - @Nullable final TriFunction>, ShoppingListDraft, - ShoppingList, List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, - final long cacheSize - ) { - super( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } +public final class ShoppingListSyncOptions + extends BaseSyncOptions { + ShoppingListSyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallback, + @Nullable + final TriConsumer, Optional> + warningCallback, + final int batchSize, + @Nullable + final TriFunction< + List>, + ShoppingListDraft, + ShoppingList, + List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { + super( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptionsBuilder.java index a2e036288c..530993e071 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptionsBuilder.java @@ -4,56 +4,57 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.shoppinglists.ShoppingList; import io.sphere.sdk.shoppinglists.ShoppingListDraft; - import javax.annotation.Nonnull; public final class ShoppingListSyncOptionsBuilder - extends BaseSyncOptionsBuilder { - - public static final int BATCH_SIZE_DEFAULT = 50; - - private ShoppingListSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } - - /** - * Creates a new instance of {@link ShoppingListSyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 50). - * - * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link ShoppingListSyncOptionsBuilder} - */ - public static ShoppingListSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new ShoppingListSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); - } - - /** - * Creates new instance of {@link ShoppingListSyncOptions} enriched with all fields provided to {@code this} builder - * - * @return new instance of {@link ShoppingListSyncOptions} - */ - @Override - public ShoppingListSyncOptions build() { - return new ShoppingListSyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } - - /** - * Returns an instance of this class to be used in the superclass's generic methods. Please see the JavaDoc in the - * overridden method for further details. - * - * @return an instance of this class. - */ - @Override - protected ShoppingListSyncOptionsBuilder getThis() { - return this; - } + extends BaseSyncOptionsBuilder< + ShoppingListSyncOptionsBuilder, ShoppingListSyncOptions, ShoppingList, ShoppingListDraft> { + + public static final int BATCH_SIZE_DEFAULT = 50; + + private ShoppingListSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } + + /** + * Creates a new instance of {@link ShoppingListSyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 50). + * + * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the + * target CTP project. + * @return new instance of {@link ShoppingListSyncOptionsBuilder} + */ + public static ShoppingListSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new ShoppingListSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } + + /** + * Creates new instance of {@link ShoppingListSyncOptions} enriched with all fields provided to + * {@code this} builder + * + * @return new instance of {@link ShoppingListSyncOptions} + */ + @Override + public ShoppingListSyncOptions build() { + return new ShoppingListSyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } + + /** + * Returns an instance of this class to be used in the superclass's generic methods. Please see + * the JavaDoc in the overridden method for further details. + * + * @return an instance of this class. + */ + @Override + protected ShoppingListSyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/commands/updateactions/AddLineItemWithSku.java b/src/main/java/com/commercetools/sync/shoppinglists/commands/updateactions/AddLineItemWithSku.java index 8585d2849d..79bf073005 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/commands/updateactions/AddLineItemWithSku.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/commands/updateactions/AddLineItemWithSku.java @@ -6,75 +6,71 @@ import io.sphere.sdk.shoppinglists.ShoppingList; import io.sphere.sdk.types.CustomDraft; import io.sphere.sdk.types.CustomFieldsDraft; - +import java.time.ZonedDateTime; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.time.ZonedDateTime; /** * TODO (JVM-SDK): https://github.com/commercetools/commercetools-jvm-sdk/issues/2079 - * ShoppingList#AddLineItem action does not support product variant selection by SKU, - * so we needed to add this custom action as a workaround. + * ShoppingList#AddLineItem action does not support product variant selection by SKU, so we needed + * to add this custom action as a workaround. */ -public final class AddLineItemWithSku extends UpdateActionImpl implements CustomDraft { +public final class AddLineItemWithSku extends UpdateActionImpl + implements CustomDraft { - @Nullable - private final String sku; - @Nullable - private final Long quantity; - @Nullable - private final ZonedDateTime addedAt; - @Nullable - private final CustomFieldsDraft custom; + @Nullable private final String sku; + @Nullable private final Long quantity; + @Nullable private final ZonedDateTime addedAt; + @Nullable private final CustomFieldsDraft custom; - private AddLineItemWithSku( - @Nullable final String sku, - @Nullable final Long quantity, - @Nullable final ZonedDateTime addedAt, - @Nullable final CustomFieldsDraft custom) { + private AddLineItemWithSku( + @Nullable final String sku, + @Nullable final Long quantity, + @Nullable final ZonedDateTime addedAt, + @Nullable final CustomFieldsDraft custom) { - super("addLineItem"); + super("addLineItem"); - this.sku = sku; - this.quantity = quantity; - this.addedAt = addedAt; - this.custom = custom; - } + this.sku = sku; + this.quantity = quantity; + this.addedAt = addedAt; + this.custom = custom; + } - /** - * Creates an update action "addLineItem" which adds a line item to a shopping list. - * - * @param lineItemDraft Line item draft template to map update action's fields. - * @return an update action "addLineItem" which adds a line item to a shopping list. - */ - @Nonnull - public static UpdateAction of(@Nonnull final LineItemDraft lineItemDraft) { + /** + * Creates an update action "addLineItem" which adds a line item to a shopping list. + * + * @param lineItemDraft Line item draft template to map update action's fields. + * @return an update action "addLineItem" which adds a line item to a shopping list. + */ + @Nonnull + public static UpdateAction of(@Nonnull final LineItemDraft lineItemDraft) { - return new AddLineItemWithSku(lineItemDraft.getSku(), - lineItemDraft.getQuantity(), - lineItemDraft.getAddedAt(), - lineItemDraft.getCustom()); - } + return new AddLineItemWithSku( + lineItemDraft.getSku(), + lineItemDraft.getQuantity(), + lineItemDraft.getAddedAt(), + lineItemDraft.getCustom()); + } - @Nullable - public String getSku() { - return sku; - } + @Nullable + public String getSku() { + return sku; + } - @Nullable - public Long getQuantity() { - return quantity; - } + @Nullable + public Long getQuantity() { + return quantity; + } - @Nullable - public ZonedDateTime getAddedAt() { - return addedAt; - } + @Nullable + public ZonedDateTime getAddedAt() { + return addedAt; + } - @Override - @Nullable - public CustomFieldsDraft getCustom() { - return custom; - } + @Override + @Nullable + public CustomFieldsDraft getCustom() { + return custom; + } } - diff --git a/src/main/java/com/commercetools/sync/shoppinglists/commands/updateactions/AddTextLineItemWithAddedAt.java b/src/main/java/com/commercetools/sync/shoppinglists/commands/updateactions/AddTextLineItemWithAddedAt.java index 490d4adf7c..49220dcc57 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/commands/updateactions/AddTextLineItemWithAddedAt.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/commands/updateactions/AddTextLineItemWithAddedAt.java @@ -7,79 +7,79 @@ import io.sphere.sdk.shoppinglists.TextLineItemDraft; import io.sphere.sdk.types.CustomDraft; import io.sphere.sdk.types.CustomFieldsDraft; - +import java.time.ZonedDateTime; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.time.ZonedDateTime; /** * TODO (JVM-SDK): https://github.com/commercetools/commercetools-jvm-sdk/issues/2079 - * ShoppingList#AddTextLineItem action does not support `addedAt` value, - * so we needed to add this custom action as a workaround. + * ShoppingList#AddTextLineItem action does not support `addedAt` value, so we needed to add this + * custom action as a workaround. */ -public final class AddTextLineItemWithAddedAt extends UpdateActionImpl implements CustomDraft { +public final class AddTextLineItemWithAddedAt extends UpdateActionImpl + implements CustomDraft { - private final LocalizedString name; - private final LocalizedString description; - private final Long quantity; - private final ZonedDateTime addedAt; - private final CustomFieldsDraft custom; + private final LocalizedString name; + private final LocalizedString description; + private final Long quantity; + private final ZonedDateTime addedAt; + private final CustomFieldsDraft custom; - private AddTextLineItemWithAddedAt( - @Nonnull final LocalizedString name, - @Nullable final LocalizedString description, - @Nullable final Long quantity, - @Nullable final ZonedDateTime addedAt, - @Nullable final CustomFieldsDraft custom) { + private AddTextLineItemWithAddedAt( + @Nonnull final LocalizedString name, + @Nullable final LocalizedString description, + @Nullable final Long quantity, + @Nullable final ZonedDateTime addedAt, + @Nullable final CustomFieldsDraft custom) { - super("addTextLineItem"); - this.name = name; - this.description = description; - this.quantity = quantity; - this.addedAt = addedAt; - this.custom = custom; - } + super("addTextLineItem"); + this.name = name; + this.description = description; + this.quantity = quantity; + this.addedAt = addedAt; + this.custom = custom; + } - /** - * Creates an update action "addTextLineItem" which adds a text line item to a shopping list. - * - * @param textLineItemDraft text line item draft template to map update action's fields. - * @return an update action "addTextLineItem" which adds a text line item to a shopping list. - */ - @Nonnull - public static UpdateAction of(@Nonnull final TextLineItemDraft textLineItemDraft) { + /** + * Creates an update action "addTextLineItem" which adds a text line item to a shopping list. + * + * @param textLineItemDraft text line item draft template to map update action's fields. + * @return an update action "addTextLineItem" which adds a text line item to a shopping list. + */ + @Nonnull + public static UpdateAction of(@Nonnull final TextLineItemDraft textLineItemDraft) { - return new AddTextLineItemWithAddedAt( - textLineItemDraft.getName(), - textLineItemDraft.getDescription(), - textLineItemDraft.getQuantity(), - textLineItemDraft.getAddedAt(), - textLineItemDraft.getCustom()); - } + return new AddTextLineItemWithAddedAt( + textLineItemDraft.getName(), + textLineItemDraft.getDescription(), + textLineItemDraft.getQuantity(), + textLineItemDraft.getAddedAt(), + textLineItemDraft.getCustom()); + } - @Nonnull - public LocalizedString getName() { - return name; - } + @Nonnull + public LocalizedString getName() { + return name; + } - @Nullable - public LocalizedString getDescription() { - return description; - } + @Nullable + public LocalizedString getDescription() { + return description; + } - @Nullable - public Long getQuantity() { - return quantity; - } + @Nullable + public Long getQuantity() { + return quantity; + } - @Nullable - public ZonedDateTime getAddedAt() { - return addedAt; - } + @Nullable + public ZonedDateTime getAddedAt() { + return addedAt; + } - @Nullable - @Override - public CustomFieldsDraft getCustom() { - return custom; - } + @Nullable + @Override + public CustomFieldsDraft getCustom() { + return custom; + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/helpers/LineItemReferenceResolver.java b/src/main/java/com/commercetools/sync/shoppinglists/helpers/LineItemReferenceResolver.java index 74d5ec85d4..75d865fdd6 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/helpers/LineItemReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/helpers/LineItemReferenceResolver.java @@ -1,5 +1,7 @@ package com.commercetools.sync.shoppinglists.helpers; +import static java.lang.String.format; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.CustomReferenceResolver; import com.commercetools.sync.services.TypeService; @@ -7,59 +9,58 @@ import io.sphere.sdk.shoppinglists.LineItemDraft; import io.sphere.sdk.shoppinglists.LineItemDraftBuilder; import io.sphere.sdk.shoppinglists.ShoppingListDraft; - -import javax.annotation.Nonnull; import java.util.concurrent.CompletionStage; - -import static java.lang.String.format; +import javax.annotation.Nonnull; public final class LineItemReferenceResolver extends CustomReferenceResolver { - static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type reference on " - + "LineItemDraft with SKU: '%s'."; - - /** - * Takes a {@link ShoppingListSyncOptions} instance, a {@link TypeService} to instantiate a - * {@link LineItemReferenceResolver} instance that could be used to resolve the custom type references of the - * line item drafts in the CTP project specified in the injected {@link ShoppingListSyncOptions} instance. - * - * @param shoppingListSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param typeService the service to fetch the types for reference resolution. - */ - public LineItemReferenceResolver(@Nonnull final ShoppingListSyncOptions shoppingListSyncOptions, - @Nonnull final TypeService typeService) { - - super(shoppingListSyncOptions, typeService); + static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = + "Failed to resolve custom type reference on " + "LineItemDraft with SKU: '%s'."; - } + /** + * Takes a {@link ShoppingListSyncOptions} instance, a {@link TypeService} to instantiate a {@link + * LineItemReferenceResolver} instance that could be used to resolve the custom type references of + * the line item drafts in the CTP project specified in the injected {@link + * ShoppingListSyncOptions} instance. + * + * @param shoppingListSyncOptions the container of all the options of the sync process including + * the CTP project client and/or configuration and other sync-specific options. + * @param typeService the service to fetch the types for reference resolution. + */ + public LineItemReferenceResolver( + @Nonnull final ShoppingListSyncOptions shoppingListSyncOptions, + @Nonnull final TypeService typeService) { - /** - * Given a {@link ShoppingListDraft} this method attempts to resolve the custom type reference to return - * a {@link CompletionStage} which contains a new instance of the draft with the resolved references. - * - * @param lineItemDraft the lineItemDraft to resolve its references. - * @return a {@link CompletionStage} that contains as a result a new lineItemDraft instance with resolved - * references or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Override - @Nonnull - public CompletionStage resolveReferences(@Nonnull final LineItemDraft lineItemDraft) { - return resolveCustomTypeReference(LineItemDraftBuilder.of(lineItemDraft)) - .thenApply(LineItemDraftBuilder::build); - } + super(shoppingListSyncOptions, typeService); + } - @Nonnull - protected CompletionStage resolveCustomTypeReference( - @Nonnull final LineItemDraftBuilder draftBuilder) { + /** + * Given a {@link ShoppingListDraft} this method attempts to resolve the custom type reference to + * return a {@link CompletionStage} which contains a new instance of the draft with the resolved + * references. + * + * @param lineItemDraft the lineItemDraft to resolve its references. + * @return a {@link CompletionStage} that contains as a result a new lineItemDraft instance with + * resolved references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Override + @Nonnull + public CompletionStage resolveReferences( + @Nonnull final LineItemDraft lineItemDraft) { + return resolveCustomTypeReference(LineItemDraftBuilder.of(lineItemDraft)) + .thenApply(LineItemDraftBuilder::build); + } - return resolveCustomTypeReference( - draftBuilder, - LineItemDraftBuilder::getCustom, - LineItemDraftBuilder::custom, - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getSku())); + @Nonnull + protected CompletionStage resolveCustomTypeReference( + @Nonnull final LineItemDraftBuilder draftBuilder) { - } + return resolveCustomTypeReference( + draftBuilder, + LineItemDraftBuilder::getCustom, + LineItemDraftBuilder::custom, + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getSku())); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListBatchValidator.java b/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListBatchValidator.java index acc6c8a3b8..176c3ed5cd 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListBatchValidator.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListBatchValidator.java @@ -1,220 +1,230 @@ package com.commercetools.sync.shoppinglists.helpers; +import static java.lang.String.format; +import static java.util.stream.Collectors.toSet; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.helpers.BaseBatchValidator; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.shoppinglists.LineItemDraft; import io.sphere.sdk.shoppinglists.ShoppingListDraft; import io.sphere.sdk.shoppinglists.TextLineItemDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; - -import static java.lang.String.format; -import static java.util.stream.Collectors.toSet; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class ShoppingListBatchValidator - extends BaseBatchValidator { - - public static final String SHOPPING_LIST_DRAFT_KEY_NOT_SET = "ShoppingListDraft with name: %s doesn't have a key. " - + "Please make sure all shopping list drafts have keys."; - public static final String SHOPPING_LIST_DRAFT_IS_NULL = "ShoppingListDraft is null."; - public static final String SHOPPING_LIST_DRAFT_NAME_NOT_SET = "ShoppingListDraft with key: %s doesn't have a name. " - + "Please make sure all shopping list drafts have names."; - static final String LINE_ITEM_DRAFT_IS_NULL = "LineItemDraft at position '%d' of ShoppingListDraft " - + "with key '%s' is null."; - static final String LINE_ITEM_DRAFT_SKU_NOT_SET = "LineItemDraft at position '%d' of " - + "ShoppingListDraft with key '%s' has no SKU set. Please make sure all lineItems have SKUs."; - static final String TEXT_LINE_ITEM_DRAFT_IS_NULL = "TextLineItemDraft at position '%d' of ShoppingListDraft " - + "with key '%s' is null."; - static final String TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET = "TextLineItemDraft at position '%d' of " - + "ShoppingListDraft with key '%s' has no name set. Please make sure all textLineItems have names."; - - public ShoppingListBatchValidator(@Nonnull final ShoppingListSyncOptions syncOptions, - @Nonnull final ShoppingListSyncStatistics syncStatistics) { - super(syncOptions, syncStatistics); - } - - /** - * Given the {@link List}<{@link ShoppingListDraft}> of drafts this method attempts to validate - * drafts and collect referenced type keys from the draft and return an {@link ImmutablePair}<{@link Set}< - * {@link ShoppingListDraft}>,{@link ReferencedKeys}> which contains the {@link Set} of valid drafts and - * referenced keys. - * - *

A valid shopping list draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a key which is not blank (null/empty)
  4. - *
  5. It has a name which is not null
  6. - *
  7. It has all lineItems AND textLineItems valid
  8. - *
  9. A lineItem is valid if it satisfies the following conditions: - *
      - *
    1. It has a SKU which is not blank (null/empty)
    2. - *
    - *
  10. A textLineItem is valid if it satisfies the following conditions: - *
      - *
    1. It has a name which is not blank (null/empty)
    2. - *
    - *
  11. - *
- * - * @param shoppingListDrafts the shopping list drafts to validate and collect referenced keys. - * @return {@link ImmutablePair}<{@link Set}<{@link ShoppingListDraft}>, - * {@link ReferencedKeys}> which contains the {@link Set} of valid drafts and - * referenced keys within a wrapper. - */ - @Override - public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( - @Nonnull final List shoppingListDrafts) { - final ReferencedKeys referencedKeys = new ReferencedKeys(); - final Set validDrafts = shoppingListDrafts - .stream() + extends BaseBatchValidator< + ShoppingListDraft, ShoppingListSyncOptions, ShoppingListSyncStatistics> { + + public static final String SHOPPING_LIST_DRAFT_KEY_NOT_SET = + "ShoppingListDraft with name: %s doesn't have a key. " + + "Please make sure all shopping list drafts have keys."; + public static final String SHOPPING_LIST_DRAFT_IS_NULL = "ShoppingListDraft is null."; + public static final String SHOPPING_LIST_DRAFT_NAME_NOT_SET = + "ShoppingListDraft with key: %s doesn't have a name. " + + "Please make sure all shopping list drafts have names."; + static final String LINE_ITEM_DRAFT_IS_NULL = + "LineItemDraft at position '%d' of ShoppingListDraft " + "with key '%s' is null."; + static final String LINE_ITEM_DRAFT_SKU_NOT_SET = + "LineItemDraft at position '%d' of " + + "ShoppingListDraft with key '%s' has no SKU set. Please make sure all lineItems have SKUs."; + static final String TEXT_LINE_ITEM_DRAFT_IS_NULL = + "TextLineItemDraft at position '%d' of ShoppingListDraft " + "with key '%s' is null."; + static final String TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET = + "TextLineItemDraft at position '%d' of " + + "ShoppingListDraft with key '%s' has no name set. Please make sure all textLineItems have names."; + + public ShoppingListBatchValidator( + @Nonnull final ShoppingListSyncOptions syncOptions, + @Nonnull final ShoppingListSyncStatistics syncStatistics) { + super(syncOptions, syncStatistics); + } + + /** + * Given the {@link List}<{@link ShoppingListDraft}> of drafts this method attempts to + * validate drafts and collect referenced type keys from the draft and return an {@link + * ImmutablePair}<{@link Set}< {@link ShoppingListDraft}>,{@link ReferencedKeys}> + * which contains the {@link Set} of valid drafts and referenced keys. + * + *

A valid shopping list draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a key which is not blank (null/empty) + *
  3. It has a name which is not null + *
  4. It has all lineItems AND textLineItems valid + *
  5. A lineItem is valid if it satisfies the following conditions: + *
      + *
    1. It has a SKU which is not blank (null/empty) + *
    + *
  6. A textLineItem is valid if it satisfies the following conditions: + *
      + *
    1. It has a name which is not blank (null/empty) + *
    + *
+ * + * @param shoppingListDrafts the shopping list drafts to validate and collect referenced keys. + * @return {@link ImmutablePair}<{@link Set}<{@link ShoppingListDraft}>, {@link + * ReferencedKeys}> which contains the {@link Set} of valid drafts and referenced keys + * within a wrapper. + */ + @Override + public ImmutablePair, ReferencedKeys> validateAndCollectReferencedKeys( + @Nonnull final List shoppingListDrafts) { + final ReferencedKeys referencedKeys = new ReferencedKeys(); + final Set validDrafts = + shoppingListDrafts.stream() .filter(this::isValidShoppingListDraft) - .peek(shoppingListDraft -> - collectReferencedKeys(referencedKeys, shoppingListDraft)) + .peek(shoppingListDraft -> collectReferencedKeys(referencedKeys, shoppingListDraft)) .collect(toSet()); - return ImmutablePair.of(validDrafts, referencedKeys); - } - - private boolean isValidShoppingListDraft( - @Nullable final ShoppingListDraft shoppingListDraft) { - - if (shoppingListDraft == null) { - handleError(SHOPPING_LIST_DRAFT_IS_NULL); - } else if (isBlank(shoppingListDraft.getKey())) { - handleError(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); - } else if (isNullOrEmptyLocalizedString(shoppingListDraft.getName())) { - handleError(format(SHOPPING_LIST_DRAFT_NAME_NOT_SET, shoppingListDraft.getKey())); - } else { - final List draftErrors = getErrorsInAllLineItemsAndTextLineItems(shoppingListDraft); - if (!draftErrors.isEmpty()) { - final String concatenatedErrors = String.join(",", draftErrors); - this.handleError(concatenatedErrors); - } else { - return true; - } - } - - return false; + return ImmutablePair.of(validDrafts, referencedKeys); + } + + private boolean isValidShoppingListDraft(@Nullable final ShoppingListDraft shoppingListDraft) { + + if (shoppingListDraft == null) { + handleError(SHOPPING_LIST_DRAFT_IS_NULL); + } else if (isBlank(shoppingListDraft.getKey())) { + handleError(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); + } else if (isNullOrEmptyLocalizedString(shoppingListDraft.getName())) { + handleError(format(SHOPPING_LIST_DRAFT_NAME_NOT_SET, shoppingListDraft.getKey())); + } else { + final List draftErrors = getErrorsInAllLineItemsAndTextLineItems(shoppingListDraft); + if (!draftErrors.isEmpty()) { + final String concatenatedErrors = String.join(",", draftErrors); + this.handleError(concatenatedErrors); + } else { + return true; + } } - - @Nonnull - private List getErrorsInAllLineItemsAndTextLineItems(@Nonnull final ShoppingListDraft shoppingListDraft) { - final List errorMessages = new ArrayList<>(); - - if (shoppingListDraft.getLineItems() != null) { - final List lineItemDrafts = shoppingListDraft.getLineItems(); - for (int i = 0; i < lineItemDrafts.size(); i++) { - errorMessages.addAll(getLineItemDraftErrorsInAllLineItems(lineItemDrafts.get(i), - i, shoppingListDraft.getKey())); - } - } - - if (shoppingListDraft.getTextLineItems() != null) { - final List textLineItems = shoppingListDraft.getTextLineItems(); - for (int i = 0; i < textLineItems.size(); i++) { - errorMessages.addAll(getTextLineItemDraftErrorsInAllTextLineItems(textLineItems.get(i), - i, shoppingListDraft.getKey())); - } - } - - return errorMessages; + return false; + } + + @Nonnull + private List getErrorsInAllLineItemsAndTextLineItems( + @Nonnull final ShoppingListDraft shoppingListDraft) { + final List errorMessages = new ArrayList<>(); + + if (shoppingListDraft.getLineItems() != null) { + final List lineItemDrafts = shoppingListDraft.getLineItems(); + for (int i = 0; i < lineItemDrafts.size(); i++) { + errorMessages.addAll( + getLineItemDraftErrorsInAllLineItems( + lineItemDrafts.get(i), i, shoppingListDraft.getKey())); + } } - @Nonnull - private List getLineItemDraftErrorsInAllLineItems(@Nullable final LineItemDraft lineItemDraft, - final int itemPosition, - @Nonnull final String shoppingListDraftKey) { - final List errorMessages = new ArrayList<>(); - if (lineItemDraft != null) { - if (isBlank(lineItemDraft.getSku())) { - errorMessages.add(format(LINE_ITEM_DRAFT_SKU_NOT_SET, itemPosition, shoppingListDraftKey)); - } - } else { - errorMessages.add(format(LINE_ITEM_DRAFT_IS_NULL, itemPosition, shoppingListDraftKey)); - } - return errorMessages; + if (shoppingListDraft.getTextLineItems() != null) { + final List textLineItems = shoppingListDraft.getTextLineItems(); + for (int i = 0; i < textLineItems.size(); i++) { + errorMessages.addAll( + getTextLineItemDraftErrorsInAllTextLineItems( + textLineItems.get(i), i, shoppingListDraft.getKey())); + } } - @Nonnull - private List getTextLineItemDraftErrorsInAllTextLineItems( - @Nullable final TextLineItemDraft textLineItemDraft, final int itemPosition, - @Nonnull final String shoppingListDraftKey) { - final List errorMessages = new ArrayList<>(); - if (textLineItemDraft != null) { - if (isNullOrEmptyLocalizedString(textLineItemDraft.getName())) { - errorMessages.add(format(TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET, itemPosition, shoppingListDraftKey)); - } - } else { - errorMessages.add(format(TEXT_LINE_ITEM_DRAFT_IS_NULL, itemPosition, shoppingListDraftKey)); - } - return errorMessages; + return errorMessages; + } + + @Nonnull + private List getLineItemDraftErrorsInAllLineItems( + @Nullable final LineItemDraft lineItemDraft, + final int itemPosition, + @Nonnull final String shoppingListDraftKey) { + final List errorMessages = new ArrayList<>(); + if (lineItemDraft != null) { + if (isBlank(lineItemDraft.getSku())) { + errorMessages.add(format(LINE_ITEM_DRAFT_SKU_NOT_SET, itemPosition, shoppingListDraftKey)); + } + } else { + errorMessages.add(format(LINE_ITEM_DRAFT_IS_NULL, itemPosition, shoppingListDraftKey)); } - - private boolean isNullOrEmptyLocalizedString(@Nullable final LocalizedString localizedString) { - return localizedString == null || localizedString.getLocales().isEmpty(); + return errorMessages; + } + + @Nonnull + private List getTextLineItemDraftErrorsInAllTextLineItems( + @Nullable final TextLineItemDraft textLineItemDraft, + final int itemPosition, + @Nonnull final String shoppingListDraftKey) { + final List errorMessages = new ArrayList<>(); + if (textLineItemDraft != null) { + if (isNullOrEmptyLocalizedString(textLineItemDraft.getName())) { + errorMessages.add( + format(TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET, itemPosition, shoppingListDraftKey)); + } + } else { + errorMessages.add(format(TEXT_LINE_ITEM_DRAFT_IS_NULL, itemPosition, shoppingListDraftKey)); } - - private void collectReferencedKeys( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final ShoppingListDraft shoppingListDraft) { - - collectReferencedKeyFromResourceIdentifier(shoppingListDraft.getCustomer(), - referencedKeys.customerKeys::add); - collectReferencedKeyFromCustomFieldsDraft(shoppingListDraft.getCustom(), - referencedKeys.typeKeys::add); - collectReferencedKeysInLineItems(referencedKeys, shoppingListDraft); - collectReferencedKeysInTextLineItems(referencedKeys, shoppingListDraft); + return errorMessages; + } + + private boolean isNullOrEmptyLocalizedString(@Nullable final LocalizedString localizedString) { + return localizedString == null || localizedString.getLocales().isEmpty(); + } + + private void collectReferencedKeys( + @Nonnull final ReferencedKeys referencedKeys, + @Nonnull final ShoppingListDraft shoppingListDraft) { + + collectReferencedKeyFromResourceIdentifier( + shoppingListDraft.getCustomer(), referencedKeys.customerKeys::add); + collectReferencedKeyFromCustomFieldsDraft( + shoppingListDraft.getCustom(), referencedKeys.typeKeys::add); + collectReferencedKeysInLineItems(referencedKeys, shoppingListDraft); + collectReferencedKeysInTextLineItems(referencedKeys, shoppingListDraft); + } + + private void collectReferencedKeysInLineItems( + @Nonnull final ReferencedKeys referencedKeys, + @Nonnull final ShoppingListDraft shoppingListDraft) { + + if (shoppingListDraft.getLineItems() == null) { + return; } - private void collectReferencedKeysInLineItems( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final ShoppingListDraft shoppingListDraft) { - - if (shoppingListDraft.getLineItems() == null) { - return; - } - - shoppingListDraft - .getLineItems() - .forEach(lineItemDraft -> + shoppingListDraft + .getLineItems() + .forEach( + lineItemDraft -> collectReferencedKeyFromCustomFieldsDraft( lineItemDraft.getCustom(), referencedKeys.typeKeys::add)); - } + } - private void collectReferencedKeysInTextLineItems( - @Nonnull final ReferencedKeys referencedKeys, - @Nonnull final ShoppingListDraft shoppingListDraft) { + private void collectReferencedKeysInTextLineItems( + @Nonnull final ReferencedKeys referencedKeys, + @Nonnull final ShoppingListDraft shoppingListDraft) { - if (shoppingListDraft.getTextLineItems() == null) { - return; - } + if (shoppingListDraft.getTextLineItems() == null) { + return; + } - shoppingListDraft - .getTextLineItems() - .forEach(textLineItemDraft -> + shoppingListDraft + .getTextLineItems() + .forEach( + textLineItemDraft -> collectReferencedKeyFromCustomFieldsDraft( textLineItemDraft.getCustom(), referencedKeys.typeKeys::add)); - } + } - public static class ReferencedKeys { - private final Set customerKeys = new HashSet<>(); - private final Set typeKeys = new HashSet<>(); + public static class ReferencedKeys { + private final Set customerKeys = new HashSet<>(); + private final Set typeKeys = new HashSet<>(); - public Set getTypeKeys() { - return typeKeys; - } + public Set getTypeKeys() { + return typeKeys; + } - public Set getCustomerKeys() { - return customerKeys; - } + public Set getCustomerKeys() { + return customerKeys; } + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolver.java b/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolver.java index e96454caee..7067488a6c 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolver.java @@ -1,5 +1,12 @@ package com.commercetools.sync.shoppinglists.helpers; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.CustomReferenceResolver; import com.commercetools.sync.services.CustomerService; @@ -9,177 +16,192 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.shoppinglists.ShoppingListDraft; import io.sphere.sdk.shoppinglists.ShoppingListDraftBuilder; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; public final class ShoppingListReferenceResolver - extends CustomReferenceResolver { - - static final String FAILED_TO_RESOLVE_CUSTOMER_REFERENCE = "Failed to resolve customer resource identifier on " - + "ShoppingListDraft with key:'%s'. Reason: %s"; - static final String CUSTOMER_DOES_NOT_EXIST = "Customer with key '%s' doesn't exist."; - static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type reference on " - + "ShoppingListDraft with key:'%s'. "; - - private final CustomerService customerService; - private final TypeService typeService; - private final LineItemReferenceResolver lineItemReferenceResolver; - private final TextLineItemReferenceResolver textLineItemReferenceResolver; - - /** - * Takes a {@link ShoppingListSyncOptions} instance, a {@link CustomerService} and {@link TypeService} to - * instantiate a {@link ShoppingListReferenceResolver} instance that could be used to resolve the shopping list - * drafts in the CTP project specified in the injected {@link ShoppingListSyncOptions} instance. - * - * @param shoppingListSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param customerService the service to fetch the customers for reference resolution. - * @param typeService the service to fetch the types for reference resolution. - */ - public ShoppingListReferenceResolver(@Nonnull final ShoppingListSyncOptions shoppingListSyncOptions, - @Nonnull final CustomerService customerService, - @Nonnull final TypeService typeService) { - - super(shoppingListSyncOptions, typeService); - this.lineItemReferenceResolver = new LineItemReferenceResolver(shoppingListSyncOptions, typeService); - this.textLineItemReferenceResolver = new TextLineItemReferenceResolver(shoppingListSyncOptions, typeService); - this.customerService = customerService; - this.typeService = typeService; + extends CustomReferenceResolver< + ShoppingListDraft, ShoppingListDraftBuilder, ShoppingListSyncOptions> { + + static final String FAILED_TO_RESOLVE_CUSTOMER_REFERENCE = + "Failed to resolve customer resource identifier on " + + "ShoppingListDraft with key:'%s'. Reason: %s"; + static final String CUSTOMER_DOES_NOT_EXIST = "Customer with key '%s' doesn't exist."; + static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = + "Failed to resolve custom type reference on " + "ShoppingListDraft with key:'%s'. "; + + private final CustomerService customerService; + private final TypeService typeService; + private final LineItemReferenceResolver lineItemReferenceResolver; + private final TextLineItemReferenceResolver textLineItemReferenceResolver; + + /** + * Takes a {@link ShoppingListSyncOptions} instance, a {@link CustomerService} and {@link + * TypeService} to instantiate a {@link ShoppingListReferenceResolver} instance that could be used + * to resolve the shopping list drafts in the CTP project specified in the injected {@link + * ShoppingListSyncOptions} instance. + * + * @param shoppingListSyncOptions the container of all the options of the sync process including + * the CTP project client and/or configuration and other sync-specific options. + * @param customerService the service to fetch the customers for reference resolution. + * @param typeService the service to fetch the types for reference resolution. + */ + public ShoppingListReferenceResolver( + @Nonnull final ShoppingListSyncOptions shoppingListSyncOptions, + @Nonnull final CustomerService customerService, + @Nonnull final TypeService typeService) { + + super(shoppingListSyncOptions, typeService); + this.lineItemReferenceResolver = + new LineItemReferenceResolver(shoppingListSyncOptions, typeService); + this.textLineItemReferenceResolver = + new TextLineItemReferenceResolver(shoppingListSyncOptions, typeService); + this.customerService = customerService; + this.typeService = typeService; + } + + /** + * Given a {@link ShoppingListDraft} this method attempts to resolve the customer and custom type + * references to return a {@link CompletionStage} which contains a new instance of the draft with + * the resolved references. + * + * @param shoppingListDraft the shoppingListDraft to resolve its references. + * @return a {@link CompletionStage} that contains as a result a new shoppingListDraft instance + * with resolved references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Nonnull + public CompletionStage resolveReferences( + @Nonnull final ShoppingListDraft shoppingListDraft) { + return resolveCustomerReference(ShoppingListDraftBuilder.of(shoppingListDraft)) + .thenCompose(this::resolveCustomTypeReference) + .thenCompose(this::resolveLineItemReferences) + .thenCompose(this::resolveTextLineItemReferences) + .thenApply(ShoppingListDraftBuilder::build); + } + + @Nonnull + protected CompletionStage resolveCustomerReference( + @Nonnull final ShoppingListDraftBuilder draftBuilder) { + + final ResourceIdentifier customerResourceIdentifier = draftBuilder.getCustomer(); + if (customerResourceIdentifier != null && customerResourceIdentifier.getId() == null) { + String customerKey; + try { + customerKey = getKeyFromResourceIdentifier(customerResourceIdentifier); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, + draftBuilder.getKey(), + referenceResolutionException.getMessage()))); + } + + return fetchAndResolveCustomerReference(draftBuilder, customerKey); } - - /** - * Given a {@link ShoppingListDraft} this method attempts to resolve the customer and custom type references to - * return a {@link CompletionStage} which contains a new instance of the draft with the resolved references. - * - * @param shoppingListDraft the shoppingListDraft to resolve its references. - * @return a {@link CompletionStage} that contains as a result a new shoppingListDraft instance with resolved - * references or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Nonnull - public CompletionStage resolveReferences(@Nonnull final ShoppingListDraft shoppingListDraft) { - return resolveCustomerReference(ShoppingListDraftBuilder.of(shoppingListDraft)) - .thenCompose(this::resolveCustomTypeReference) - .thenCompose(this::resolveLineItemReferences) - .thenCompose(this::resolveTextLineItemReferences) - .thenApply(ShoppingListDraftBuilder::build); + return completedFuture(draftBuilder); + } + + @Nonnull + private CompletionStage fetchAndResolveCustomerReference( + @Nonnull final ShoppingListDraftBuilder draftBuilder, @Nonnull final String customerKey) { + + return customerService + .fetchCachedCustomerId(customerKey) + .thenCompose( + resolvedCustomerIdOptional -> + resolvedCustomerIdOptional + .map( + resolvedCustomerId -> + completedFuture( + draftBuilder.customer( + Customer.referenceOfId(resolvedCustomerId) + .toResourceIdentifier()))) + .orElseGet( + () -> { + final String errorMessage = format(CUSTOMER_DOES_NOT_EXIST, customerKey); + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, + draftBuilder.getKey(), + errorMessage))); + })); + } + + @Nonnull + protected CompletionStage resolveCustomTypeReference( + @Nonnull final ShoppingListDraftBuilder draftBuilder) { + + return resolveCustomTypeReference( + draftBuilder, + ShoppingListDraftBuilder::getCustom, + ShoppingListDraftBuilder::custom, + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getKey())); + } + + @Nonnull + private CompletionStage resolveLineItemReferences( + @Nonnull final ShoppingListDraftBuilder draftBuilder) { + + if (draftBuilder.getLineItems() != null) { + return mapValuesToFutureOfCompletedValues( + draftBuilder.getLineItems(), lineItemReferenceResolver::resolveReferences, toList()) + .thenApply(draftBuilder::lineItems); } - @Nonnull - protected CompletionStage resolveCustomerReference( - @Nonnull final ShoppingListDraftBuilder draftBuilder) { - - final ResourceIdentifier customerResourceIdentifier = draftBuilder.getCustomer(); - if (customerResourceIdentifier != null && customerResourceIdentifier.getId() == null) { - String customerKey; - try { - customerKey = getKeyFromResourceIdentifier(customerResourceIdentifier); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, draftBuilder.getKey(), - referenceResolutionException.getMessage()))); - } - - return fetchAndResolveCustomerReference(draftBuilder, customerKey); - } - return completedFuture(draftBuilder); - } + return completedFuture(draftBuilder); + } - @Nonnull - private CompletionStage fetchAndResolveCustomerReference( - @Nonnull final ShoppingListDraftBuilder draftBuilder, - @Nonnull final String customerKey) { - - return customerService - .fetchCachedCustomerId(customerKey) - .thenCompose(resolvedCustomerIdOptional -> resolvedCustomerIdOptional - .map(resolvedCustomerId -> - completedFuture(draftBuilder.customer( - Customer.referenceOfId(resolvedCustomerId).toResourceIdentifier()))) - .orElseGet(() -> { - final String errorMessage = format(CUSTOMER_DOES_NOT_EXIST, customerKey); - return exceptionallyCompletedFuture(new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, draftBuilder.getKey(), errorMessage))); - })); - } + @Nonnull + private CompletionStage resolveTextLineItemReferences( + @Nonnull final ShoppingListDraftBuilder draftBuilder) { - @Nonnull - protected CompletionStage resolveCustomTypeReference( - @Nonnull final ShoppingListDraftBuilder draftBuilder) { - - return resolveCustomTypeReference( - draftBuilder, - ShoppingListDraftBuilder::getCustom, - ShoppingListDraftBuilder::custom, - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getKey())); + if (draftBuilder.getTextLineItems() != null) { + return mapValuesToFutureOfCompletedValues( + draftBuilder.getTextLineItems(), + textLineItemReferenceResolver::resolveReferences, + toList()) + .thenApply(draftBuilder::textLineItems); } - @Nonnull - private CompletionStage resolveLineItemReferences( - @Nonnull final ShoppingListDraftBuilder draftBuilder) { - - if (draftBuilder.getLineItems() != null) { - return mapValuesToFutureOfCompletedValues( - draftBuilder.getLineItems(), lineItemReferenceResolver::resolveReferences, toList()) - .thenApply(draftBuilder::lineItems); - } - - return completedFuture(draftBuilder); + return completedFuture(draftBuilder); + } + + /** + * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys (i.e custom + * type, customer) from the CTP to populate caches for the reference resolution. + * + *

Note: This method is only to be used internally by the library to improve performance. + * + * @param referencedKeys a wrapper for the custom type and customer references to fetch the keys, + * and store the corresponding keys -> ids into cached maps. + * @return {@link CompletionStage}<{@link Map}<{@link String}>{@link String}>> in + * which the results of its completions contains a map of requested references keys -> ids + * of customer references. + */ + @Nonnull + public CompletableFuture>> populateKeyToIdCachesForReferencedKeys( + @Nonnull final ShoppingListBatchValidator.ReferencedKeys referencedKeys) { + + final List>> futures = new ArrayList<>(); + + final Set typeKeys = referencedKeys.getTypeKeys(); + if (!typeKeys.isEmpty()) { + futures.add(typeService.cacheKeysToIds(typeKeys)); } - @Nonnull - private CompletionStage resolveTextLineItemReferences( - @Nonnull final ShoppingListDraftBuilder draftBuilder) { - - if (draftBuilder.getTextLineItems() != null) { - return mapValuesToFutureOfCompletedValues( - draftBuilder.getTextLineItems(), textLineItemReferenceResolver::resolveReferences, toList()) - .thenApply(draftBuilder::textLineItems); - } - - return completedFuture(draftBuilder); + final Set customerKeys = referencedKeys.getCustomerKeys(); + if (!customerKeys.isEmpty()) { + futures.add(customerService.cacheKeysToIds(customerKeys)); } - /** - * Calls the {@code cacheKeysToIds} service methods to fetch all the referenced keys - * (i.e custom type, customer) from the CTP to populate caches for the reference resolution. - * - *

Note: This method is only to be used internally by the library to improve performance. - * - * @param referencedKeys a wrapper for the custom type and customer references to fetch the keys, and store the - * corresponding keys -> ids into cached maps. - * @return {@link CompletionStage}<{@link Map}<{@link String}>{@link String}>> in which the results - * of its completions contains a map of requested references keys -> ids of customer references. - */ - @Nonnull - public CompletableFuture>> populateKeyToIdCachesForReferencedKeys( - @Nonnull final ShoppingListBatchValidator.ReferencedKeys referencedKeys) { - - final List>> futures = new ArrayList<>(); - - final Set typeKeys = referencedKeys.getTypeKeys(); - if (!typeKeys.isEmpty()) { - futures.add(typeService.cacheKeysToIds(typeKeys)); - } - - final Set customerKeys = referencedKeys.getCustomerKeys(); - if (!customerKeys.isEmpty()) { - futures.add(customerService.cacheKeysToIds(customerKeys)); - } - - return collectionOfFuturesToFutureOfCollection(futures, toList()); - } + return collectionOfFuturesToFutureOfCollection(futures, toList()); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListSyncStatistics.java b/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListSyncStatistics.java index eee55b0235..78e0f358c8 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListSyncStatistics.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListSyncStatistics.java @@ -4,15 +4,17 @@ public final class ShoppingListSyncStatistics extends BaseSyncStatistics { - /** - * Builds a summary of the shopping list sync statistics instance that looks like the following example: - * - *

"Summary: 2 shopping lists were processed in total (0 created, 0 updated and 0 failed to sync)." - * - * @return a summary message of the shopping list sync statistics instance. - */ - @Override - public String getReportMessage() { - return getDefaultReportMessageForResource("shopping lists"); - } + /** + * Builds a summary of the shopping list sync statistics instance that looks like the following + * example: + * + *

"Summary: 2 shopping lists were processed in total (0 created, 0 updated and 0 failed to + * sync)." + * + * @return a summary message of the shopping list sync statistics instance. + */ + @Override + public String getReportMessage() { + return getDefaultReportMessageForResource("shopping lists"); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/helpers/TextLineItemReferenceResolver.java b/src/main/java/com/commercetools/sync/shoppinglists/helpers/TextLineItemReferenceResolver.java index 08de765723..b6faf4c59d 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/helpers/TextLineItemReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/helpers/TextLineItemReferenceResolver.java @@ -1,63 +1,65 @@ package com.commercetools.sync.shoppinglists.helpers; +import static java.lang.String.format; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.CustomReferenceResolver; import com.commercetools.sync.services.TypeService; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; import io.sphere.sdk.shoppinglists.TextLineItemDraft; import io.sphere.sdk.shoppinglists.TextLineItemDraftBuilder; - -import javax.annotation.Nonnull; import java.util.concurrent.CompletionStage; - -import static java.lang.String.format; +import javax.annotation.Nonnull; public final class TextLineItemReferenceResolver - extends CustomReferenceResolver { - - static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = "Failed to resolve custom type reference on " - + "TextLineItemDraft with name: '%s'."; - - /** - * Takes a {@link ShoppingListSyncOptions} instance, a {@link TypeService} to instantiate a - * {@link TextLineItemReferenceResolver} instance that could be used to resolve the text line-item drafts in the - * CTP project specified in the injected {@link ShoppingListSyncOptions} instance. - * - * @param shoppingListSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param typeService the service to fetch the types for reference resolution. - */ - public TextLineItemReferenceResolver(@Nonnull final ShoppingListSyncOptions shoppingListSyncOptions, - @Nonnull final TypeService typeService) { - - super(shoppingListSyncOptions, typeService); - - } - - /** - * Given a {@link TextLineItemDraft} this method attempts to resolve the attribute definition references to return - * a {@link CompletionStage} which contains a new instance of the draft with the resolved references. - * - * @param textLineItemDraft the textLineItemDraft to resolve its references. - * @return a {@link CompletionStage} that contains as a result a new textLineItemDraft instance with resolved - * references or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Override - @Nonnull - public CompletionStage resolveReferences(@Nonnull final TextLineItemDraft textLineItemDraft) { - return resolveCustomTypeReference(TextLineItemDraftBuilder.of(textLineItemDraft)) - .thenApply(TextLineItemDraftBuilder::build); - } - - @Nonnull - protected CompletionStage resolveCustomTypeReference( - @Nonnull final TextLineItemDraftBuilder draftBuilder) { - - return resolveCustomTypeReference( - draftBuilder, - TextLineItemDraftBuilder::getCustom, - TextLineItemDraftBuilder::custom, - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getName())); - } + extends CustomReferenceResolver< + TextLineItemDraft, TextLineItemDraftBuilder, ShoppingListSyncOptions> { + + static final String FAILED_TO_RESOLVE_CUSTOM_TYPE = + "Failed to resolve custom type reference on " + "TextLineItemDraft with name: '%s'."; + + /** + * Takes a {@link ShoppingListSyncOptions} instance, a {@link TypeService} to instantiate a {@link + * TextLineItemReferenceResolver} instance that could be used to resolve the text line-item drafts + * in the CTP project specified in the injected {@link ShoppingListSyncOptions} instance. + * + * @param shoppingListSyncOptions the container of all the options of the sync process including + * the CTP project client and/or configuration and other sync-specific options. + * @param typeService the service to fetch the types for reference resolution. + */ + public TextLineItemReferenceResolver( + @Nonnull final ShoppingListSyncOptions shoppingListSyncOptions, + @Nonnull final TypeService typeService) { + + super(shoppingListSyncOptions, typeService); + } + + /** + * Given a {@link TextLineItemDraft} this method attempts to resolve the attribute definition + * references to return a {@link CompletionStage} which contains a new instance of the draft with + * the resolved references. + * + * @param textLineItemDraft the textLineItemDraft to resolve its references. + * @return a {@link CompletionStage} that contains as a result a new textLineItemDraft instance + * with resolved references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Override + @Nonnull + public CompletionStage resolveReferences( + @Nonnull final TextLineItemDraft textLineItemDraft) { + return resolveCustomTypeReference(TextLineItemDraftBuilder.of(textLineItemDraft)) + .thenApply(TextLineItemDraftBuilder::build); + } + + @Nonnull + protected CompletionStage resolveCustomTypeReference( + @Nonnull final TextLineItemDraftBuilder draftBuilder) { + + return resolveCustomTypeReference( + draftBuilder, + TextLineItemDraftBuilder::getCustom, + TextLineItemDraftBuilder::custom, + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getName())); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/utils/LineItemCustomActionBuilder.java b/src/main/java/com/commercetools/sync/shoppinglists/utils/LineItemCustomActionBuilder.java index c0c3ca5345..3c2f847d57 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/LineItemCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/LineItemCustomActionBuilder.java @@ -6,41 +6,39 @@ import io.sphere.sdk.shoppinglists.ShoppingList; import io.sphere.sdk.shoppinglists.commands.updateactions.SetLineItemCustomField; import io.sphere.sdk.shoppinglists.commands.updateactions.SetLineItemCustomType; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; public final class LineItemCustomActionBuilder implements GenericCustomActionBuilder { - @Nonnull - @Override - public UpdateAction buildRemoveCustomTypeAction( - @Nullable final Integer variantId, - @Nullable final String lineItemId) { - - return SetLineItemCustomType.ofRemoveType(lineItemId); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomTypeAction( - @Nullable final Integer variantId, - @Nullable final String lineItemId, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - - return SetLineItemCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap, lineItemId); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomFieldAction( - @Nullable final Integer variantId, - @Nullable final String lineItemId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - - return SetLineItemCustomField.ofJson(customFieldName, customFieldValue, lineItemId); - } + @Nonnull + @Override + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String lineItemId) { + + return SetLineItemCustomType.ofRemoveType(lineItemId); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String lineItemId, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + + return SetLineItemCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap, lineItemId); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String lineItemId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + + return SetLineItemCustomField.ofJson(customFieldName, customFieldValue, lineItemId); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/utils/LineItemUpdateActionUtils.java b/src/main/java/com/commercetools/sync/shoppinglists/utils/LineItemUpdateActionUtils.java index 78a0cfc5c4..1916e2a9d6 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/LineItemUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/LineItemUpdateActionUtils.java @@ -1,5 +1,11 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.CustomUpdateActionUtils; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; @@ -11,259 +17,262 @@ import io.sphere.sdk.shoppinglists.ShoppingListDraft; import io.sphere.sdk.shoppinglists.commands.updateactions.ChangeLineItemQuantity; import io.sphere.sdk.shoppinglists.commands.updateactions.RemoveLineItem; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; public final class LineItemUpdateActionUtils { - /** - * Compares a list of {@link LineItem}s with a list of {@link LineItemDraft}s. - * The method takes in functions for building the required update actions (AddLineItem, RemoveLineItem and - * 1-1 update actions on line items (e.g. changeLineItemQuantity, setLineItemCustomType, etc..). - * - *

If the list of new {@link LineItemDraft}s is {@code null}, then remove actions are built for every existing - * line item. - * - * @param oldShoppingList shopping list resource, whose line item should be updated. - * @param newShoppingList new shopping list draft, which contains the line item to update. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return a list of line item update actions on the resource of shopping lists, if the list of line items are not - * identical. Otherwise, if the line items are identical, an empty list is returned. - */ - @Nonnull - public static List> buildLineItemsUpdateActions( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList, - @Nonnull final ShoppingListSyncOptions syncOptions) { - - final boolean hasOldLineItems = oldShoppingList.getLineItems() != null - && !oldShoppingList.getLineItems().isEmpty(); - final boolean hasNewLineItems = newShoppingList.getLineItems() != null + /** + * Compares a list of {@link LineItem}s with a list of {@link LineItemDraft}s. The method takes in + * functions for building the required update actions (AddLineItem, RemoveLineItem and 1-1 update + * actions on line items (e.g. changeLineItemQuantity, setLineItemCustomType, etc..). + * + *

If the list of new {@link LineItemDraft}s is {@code null}, then remove actions are built for + * every existing line item. + * + * @param oldShoppingList shopping list resource, whose line item should be updated. + * @param newShoppingList new shopping list draft, which contains the line item to update. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return a list of line item update actions on the resource of shopping lists, if the list of + * line items are not identical. Otherwise, if the line items are identical, an empty list is + * returned. + */ + @Nonnull + public static List> buildLineItemsUpdateActions( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList, + @Nonnull final ShoppingListSyncOptions syncOptions) { + + final boolean hasOldLineItems = + oldShoppingList.getLineItems() != null && !oldShoppingList.getLineItems().isEmpty(); + final boolean hasNewLineItems = + newShoppingList.getLineItems() != null && !newShoppingList.getLineItems().isEmpty() && newShoppingList.getLineItems().stream().anyMatch(Objects::nonNull); - if (hasOldLineItems && !hasNewLineItems) { + if (hasOldLineItems && !hasNewLineItems) { - return oldShoppingList.getLineItems() - .stream() - .map(RemoveLineItem::of) - .collect(toList()); + return oldShoppingList.getLineItems().stream().map(RemoveLineItem::of).collect(toList()); - } else if (!hasOldLineItems) { + } else if (!hasOldLineItems) { - if (!hasNewLineItems) { - return emptyList(); - } + if (!hasNewLineItems) { + return emptyList(); + } - return newShoppingList.getLineItems() - .stream() - .filter(Objects::nonNull) - .filter(LineItemUpdateActionUtils::hasQuantity) - .map(AddLineItemWithSku::of) - .collect(toList()); - } + return newShoppingList.getLineItems().stream() + .filter(Objects::nonNull) + .filter(LineItemUpdateActionUtils::hasQuantity) + .map(AddLineItemWithSku::of) + .collect(toList()); + } - final List oldLineItems = oldShoppingList.getLineItems(); - final List newlineItems = newShoppingList.getLineItems() - .stream() - .filter(Objects::nonNull) - .collect(toList()); + final List oldLineItems = oldShoppingList.getLineItems(); + final List newlineItems = + newShoppingList.getLineItems().stream().filter(Objects::nonNull).collect(toList()); - return buildUpdateActions(oldShoppingList, newShoppingList, oldLineItems, newlineItems, syncOptions); - } + return buildUpdateActions( + oldShoppingList, newShoppingList, oldLineItems, newlineItems, syncOptions); + } - private static boolean hasQuantity(@Nonnull final LineItemDraft lineItemDraft) { - /* + private static boolean hasQuantity(@Nonnull final LineItemDraft lineItemDraft) { + /* - with this check, it's avoided bad request case like below: + with this check, it's avoided bad request case like below: - "code": "InvalidField", - "message": "The value '0' is not valid for field 'quantity'. Quantity 0 is not allowed.", + "code": "InvalidField", + "message": "The value '0' is not valid for field 'quantity'. Quantity 0 is not allowed.", - */ - return lineItemDraft.getQuantity() != null && lineItemDraft.getQuantity() > 0; - } + */ + return lineItemDraft.getQuantity() != null && lineItemDraft.getQuantity() > 0; + } - /** - * The decisions in the calculating update actions are documented on the - * `docs/adr/0002-shopping-lists-lineitem-and-textlineitem-update-actions.md` - */ - @Nonnull - private static List> buildUpdateActions( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList, - @Nonnull final List oldLineItems, - @Nonnull final List newlineItems, - @Nonnull final ShoppingListSyncOptions syncOptions) { - - final List> updateActions = new ArrayList<>(); - - final int minSize = Math.min(oldLineItems.size(), newlineItems.size()); - int indexOfFirstDifference = minSize; - for (int i = 0; i < minSize; i++) { - - final LineItem oldLineItem = oldLineItems.get(i); - final LineItemDraft newLineItem = newlineItems.get(i); - - if (oldLineItem.getVariant() == null || StringUtils.isBlank(oldLineItem.getVariant().getSku())) { - - syncOptions.applyErrorCallback(new SyncException( - format("LineItem at position '%d' of the ShoppingList with key '%s' has no SKU set. " - + "Please make sure all line items have SKUs.", i, oldShoppingList.getKey())), - oldShoppingList, newShoppingList, updateActions); - - return emptyList(); - - } else if (StringUtils.isBlank(newLineItem.getSku())) { - - syncOptions.applyErrorCallback(new SyncException( - format("LineItemDraft at position '%d' of the ShoppingListDraft with key '%s' has no SKU set. " - + "Please make sure all line item drafts have SKUs.", i, newShoppingList.getKey())), - oldShoppingList, newShoppingList, updateActions); - - return emptyList(); - } - - if (oldLineItem.getVariant().getSku().equals(newLineItem.getSku())) { - - updateActions.addAll(buildLineItemUpdateActions( - oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions)); - } else { - // different sku means the order is different. - // To be able to ensure the order, we need to remove and add this line item back - // with the up to date values. - indexOfFirstDifference = i; - break; - } - } - - // for example: - // old: li-1, li-2 - // new: li-1, li-3, li-2 - // indexOfFirstDifference: 1 (li-2 vs li-3) - // expected: remove from old li-2, add from draft li-3, li-2 starting from the index. - for (int i = indexOfFirstDifference; i < oldLineItems.size(); i++) { - updateActions.add(RemoveLineItem.of(oldLineItems.get(i).getId())); - } - - for (int i = indexOfFirstDifference; i < newlineItems.size(); i++) { - if (hasQuantity(newlineItems.get(i))) { - updateActions.add(AddLineItemWithSku.of(newlineItems.get(i))); - } - } - - return updateActions; - } + /** + * The decisions in the calculating update actions are documented on the + * `docs/adr/0002-shopping-lists-lineitem-and-textlineitem-update-actions.md` + */ + @Nonnull + private static List> buildUpdateActions( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList, + @Nonnull final List oldLineItems, + @Nonnull final List newlineItems, + @Nonnull final ShoppingListSyncOptions syncOptions) { + final List> updateActions = new ArrayList<>(); + final int minSize = Math.min(oldLineItems.size(), newlineItems.size()); + int indexOfFirstDifference = minSize; + for (int i = 0; i < minSize; i++) { - /** - * Compares all the fields of a {@link LineItem} and a {@link LineItemDraft} and returns a list of - * {@link UpdateAction}<{@link ShoppingList}> as a result. If both the {@link LineItem} and - * the {@link LineItemDraft} have identical fields, then no update action is needed and hence an empty {@link List} - * is returned. - * - * @param oldShoppingList shopping list resource, whose line item should be updated. - * @param newShoppingList new shopping list draft, which contains the line item to update. - * @param oldLineItem the line item which should be updated. - * @param newLineItem the line item draft where we get the new fields (i.e. quantity, custom fields and types). - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the update actions or an empty list if the line item fields are identical. - */ - @Nonnull - public static List> buildLineItemUpdateActions( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList, - @Nonnull final LineItem oldLineItem, - @Nonnull final LineItemDraft newLineItem, - @Nonnull final ShoppingListSyncOptions syncOptions) { - - final List> updateActions = filterEmptyOptionals( - buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem) - ); + final LineItem oldLineItem = oldLineItems.get(i); + final LineItemDraft newLineItem = newlineItems.get(i); - updateActions.addAll( - buildLineItemCustomUpdateActions(oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions)); + if (oldLineItem.getVariant() == null + || StringUtils.isBlank(oldLineItem.getVariant().getSku())) { - return updateActions; - } + syncOptions.applyErrorCallback( + new SyncException( + format( + "LineItem at position '%d' of the ShoppingList with key '%s' has no SKU set. " + + "Please make sure all line items have SKUs.", + i, oldShoppingList.getKey())), + oldShoppingList, + newShoppingList, + updateActions); - /** - * Compares the {@code quantity} values of a {@link LineItem} and a {@link LineItemDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeLineItemQuantity"} - * {@link UpdateAction}. If both {@link LineItem} and {@link LineItemDraft} have the same - * {@code quantity} values, then no update action is needed and empty optional will be returned. - * - *

Note: If {@code quantity} from the {@code newLineItem} is {@code null}, the new {@code quantity} - * will be set to default value {@code 1L}. If {@code quantity} from the {@code newLineItem} is {@code 0}, then it - * means removing the line item. - * - * @param oldLineItem the line item which should be updated. - * @param newLineItem the line item draft where we get the new quantity. - * @return A filled optional with the update action or an empty optional if the quantities are identical. - */ - @Nonnull - public static Optional> buildChangeLineItemQuantityUpdateAction( - @Nonnull final LineItem oldLineItem, - @Nonnull final LineItemDraft newLineItem) { - - final Long newLineItemQuantity = newLineItem.getQuantity() == null - ? NumberUtils.LONG_ONE : newLineItem.getQuantity(); - - return buildUpdateAction(oldLineItem.getQuantity(), newLineItemQuantity, - () -> ChangeLineItemQuantity.of(oldLineItem.getId(), newLineItemQuantity)); - } + return emptyList(); + + } else if (StringUtils.isBlank(newLineItem.getSku())) { - /** - * Compares the custom fields and custom types of a {@link LineItem} and a {@link LineItemDraft} and returns a - * list of {@link UpdateAction}<{@link ShoppingList}> as a result. If both the {@link LineItem} and the - * {@link LineItemDraft} have identical custom fields and types, then no update action is needed and hence an empty - * {@link List} is returned. - * - * @param oldShoppingList shopping list resource, whose line item should be updated. - * @param newShoppingList new shopping list draft, which contains the line item to update. - * @param oldLineItem the line item which should be updated. - * @param newLineItem the line item draft where we get the new custom fields and types. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the custom field/type update actions or an empty list if the custom fields/types are - * identical. - */ - @Nonnull - public static List> buildLineItemCustomUpdateActions( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList, - @Nonnull final LineItem oldLineItem, - @Nonnull final LineItemDraft newLineItem, - @Nonnull final ShoppingListSyncOptions syncOptions) { - - return CustomUpdateActionUtils.buildCustomUpdateActions( + syncOptions.applyErrorCallback( + new SyncException( + format( + "LineItemDraft at position '%d' of the ShoppingListDraft with key '%s' has no SKU set. " + + "Please make sure all line item drafts have SKUs.", + i, newShoppingList.getKey())), oldShoppingList, newShoppingList, - oldLineItem::getCustom, - newLineItem::getCustom, - new LineItemCustomActionBuilder(), - null, // not used by util. - t -> oldLineItem.getId(), - lineItem -> LineItem.resourceTypeId(), - t -> oldLineItem.getId(), - syncOptions); + updateActions); + + return emptyList(); + } + + if (oldLineItem.getVariant().getSku().equals(newLineItem.getSku())) { + + updateActions.addAll( + buildLineItemUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions)); + } else { + // different sku means the order is different. + // To be able to ensure the order, we need to remove and add this line item back + // with the up to date values. + indexOfFirstDifference = i; + break; + } + } + + // for example: + // old: li-1, li-2 + // new: li-1, li-3, li-2 + // indexOfFirstDifference: 1 (li-2 vs li-3) + // expected: remove from old li-2, add from draft li-3, li-2 starting from the index. + for (int i = indexOfFirstDifference; i < oldLineItems.size(); i++) { + updateActions.add(RemoveLineItem.of(oldLineItems.get(i).getId())); } - private LineItemUpdateActionUtils() { + for (int i = indexOfFirstDifference; i < newlineItems.size(); i++) { + if (hasQuantity(newlineItems.get(i))) { + updateActions.add(AddLineItemWithSku.of(newlineItems.get(i))); + } } + + return updateActions; + } + + /** + * Compares all the fields of a {@link LineItem} and a {@link LineItemDraft} and returns a list of + * {@link UpdateAction}<{@link ShoppingList}> as a result. If both the {@link LineItem} and + * the {@link LineItemDraft} have identical fields, then no update action is needed and hence an + * empty {@link List} is returned. + * + * @param oldShoppingList shopping list resource, whose line item should be updated. + * @param newShoppingList new shopping list draft, which contains the line item to update. + * @param oldLineItem the line item which should be updated. + * @param newLineItem the line item draft where we get the new fields (i.e. quantity, custom + * fields and types). + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the update actions or an empty list if the line item fields are identical. + */ + @Nonnull + public static List> buildLineItemUpdateActions( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList, + @Nonnull final LineItem oldLineItem, + @Nonnull final LineItemDraft newLineItem, + @Nonnull final ShoppingListSyncOptions syncOptions) { + + final List> updateActions = + filterEmptyOptionals(buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem)); + + updateActions.addAll( + buildLineItemCustomUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions)); + + return updateActions; + } + + /** + * Compares the {@code quantity} values of a {@link LineItem} and a {@link LineItemDraft} and + * returns an {@link Optional} of update action, which would contain the {@code + * "changeLineItemQuantity"} {@link UpdateAction}. If both {@link LineItem} and {@link + * LineItemDraft} have the same {@code quantity} values, then no update action is needed and empty + * optional will be returned. + * + *

Note: If {@code quantity} from the {@code newLineItem} is {@code null}, the new {@code + * quantity} will be set to default value {@code 1L}. If {@code quantity} from the {@code + * newLineItem} is {@code 0}, then it means removing the line item. + * + * @param oldLineItem the line item which should be updated. + * @param newLineItem the line item draft where we get the new quantity. + * @return A filled optional with the update action or an empty optional if the quantities are + * identical. + */ + @Nonnull + public static Optional> buildChangeLineItemQuantityUpdateAction( + @Nonnull final LineItem oldLineItem, @Nonnull final LineItemDraft newLineItem) { + + final Long newLineItemQuantity = + newLineItem.getQuantity() == null ? NumberUtils.LONG_ONE : newLineItem.getQuantity(); + + return buildUpdateAction( + oldLineItem.getQuantity(), + newLineItemQuantity, + () -> ChangeLineItemQuantity.of(oldLineItem.getId(), newLineItemQuantity)); + } + + /** + * Compares the custom fields and custom types of a {@link LineItem} and a {@link LineItemDraft} + * and returns a list of {@link UpdateAction}<{@link ShoppingList}> as a result. If both the + * {@link LineItem} and the {@link LineItemDraft} have identical custom fields and types, then no + * update action is needed and hence an empty {@link List} is returned. + * + * @param oldShoppingList shopping list resource, whose line item should be updated. + * @param newShoppingList new shopping list draft, which contains the line item to update. + * @param oldLineItem the line item which should be updated. + * @param newLineItem the line item draft where we get the new custom fields and types. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the custom field/type update actions or an empty list if the custom + * fields/types are identical. + */ + @Nonnull + public static List> buildLineItemCustomUpdateActions( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList, + @Nonnull final LineItem oldLineItem, + @Nonnull final LineItemDraft newLineItem, + @Nonnull final ShoppingListSyncOptions syncOptions) { + + return CustomUpdateActionUtils.buildCustomUpdateActions( + oldShoppingList, + newShoppingList, + oldLineItem::getCustom, + newLineItem::getCustom, + new LineItemCustomActionBuilder(), + null, // not used by util. + t -> oldLineItem.getId(), + lineItem -> LineItem.resourceTypeId(), + t -> oldLineItem.getId(), + syncOptions); + } + + private LineItemUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListCustomActionBuilder.java b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListCustomActionBuilder.java index e8a4b701ac..674682d8b2 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListCustomActionBuilder.java @@ -6,52 +6,52 @@ import io.sphere.sdk.shoppinglists.ShoppingList; import io.sphere.sdk.shoppinglists.commands.updateactions.SetCustomField; import io.sphere.sdk.shoppinglists.commands.updateactions.SetCustomType; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; -public final class ShoppingListCustomActionBuilder implements GenericCustomActionBuilder { - - private static final ShoppingListCustomActionBuilder builder = new ShoppingListCustomActionBuilder(); - - private ShoppingListCustomActionBuilder() { - super(); - } - - @Nonnull - public static ShoppingListCustomActionBuilder of() { - return builder; - } - - @Nonnull - @Override - public UpdateAction buildRemoveCustomTypeAction( - @Nullable final Integer variantId, - @Nullable final String objectId) { - - return SetCustomType.ofRemoveType(); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomTypeAction( - @Nullable final Integer variantId, - @Nullable final String objectId, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - - return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomFieldAction( - @Nullable final Integer variantId, - @Nullable final String objectId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - - return SetCustomField.ofJson(customFieldName, customFieldValue); - } +public final class ShoppingListCustomActionBuilder + implements GenericCustomActionBuilder { + + private static final ShoppingListCustomActionBuilder builder = + new ShoppingListCustomActionBuilder(); + + private ShoppingListCustomActionBuilder() { + super(); + } + + @Nonnull + public static ShoppingListCustomActionBuilder of() { + return builder; + } + + @Nonnull + @Override + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String objectId) { + + return SetCustomType.ofRemoveType(); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + + return SetCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String objectId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + + return SetCustomField.ofJson(customFieldName, customFieldValue); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListReferenceResolutionUtils.java index cd86f92035..1a002f7a57 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListReferenceResolutionUtils.java @@ -1,5 +1,9 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; +import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; +import static java.util.stream.Collectors.toList; + import io.sphere.sdk.customers.Customer; import io.sphere.sdk.expansion.ExpansionPath; import io.sphere.sdk.models.Reference; @@ -16,219 +20,214 @@ import io.sphere.sdk.shoppinglists.expansion.ShoppingListExpansionModel; import io.sphere.sdk.shoppinglists.queries.ShoppingListQuery; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Objects; import java.util.Set; - -import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; -import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** - * Util class which provides utilities that can be used when syncing shopping lists from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing shopping lists from a source + * commercetools project to a target one. */ public final class ShoppingListReferenceResolutionUtils { - /** - * Returns a {@link List}<{@link ShoppingListDraft}> consisting of the results of applying the - * mapping from {@link ShoppingList} to {@link ShoppingListDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
customer{@link Reference}<{@link Customer}>{@link ResourceIdentifier}<{@link Customer}>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
lineItems.custom.type{@link Set}<{@link Reference}<{@link Type}>>{@link Set}<{@link ResourceIdentifier}<{@link Type}>>
textLineItems.custom.type{@link Set}<{@link Reference}<{@link Type}>>{@link Set}<{@link ResourceIdentifier}<{@link Type}>>
- * - *

Note: The aforementioned references should be expanded with a key. - * Any reference that is not expanded will have its id in place and not replaced by the key will be - * considered as existing resources on the target commercetools project and - * the library will issues an update/create API request without reference resolution. - * - * @param shoppingLists the shopping lists with expanded references. - * @return a {@link List} of {@link ShoppingListDraft} built from the supplied {@link List} of {@link ShoppingList}. - */ - @Nonnull - public static List mapToShoppingListDrafts( - @Nonnull final List shoppingLists) { - - return shoppingLists - .stream() - .filter(Objects::nonNull) - .map(ShoppingListReferenceResolutionUtils::mapToShoppingListDraft) - .collect(toList()); + /** + * Returns a {@link List}<{@link ShoppingListDraft}> consisting of the results of applying + * the mapping from {@link ShoppingList} to {@link ShoppingListDraft} with considering reference + * resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
customer{@link Reference}<{@link Customer}>{@link ResourceIdentifier}<{@link Customer}>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
lineItems.custom.type{@link Set}<{@link Reference}<{@link Type}>>{@link Set}<{@link ResourceIdentifier}<{@link Type}>>
textLineItems.custom.type{@link Set}<{@link Reference}<{@link Type}>>{@link Set}<{@link ResourceIdentifier}<{@link Type}>>
+ * + *

Note: The aforementioned references should be expanded with a key. Any reference that + * is not expanded will have its id in place and not replaced by the key will be considered as + * existing resources on the target commercetools project and the library will issues an + * update/create API request without reference resolution. + * + * @param shoppingLists the shopping lists with expanded references. + * @return a {@link List} of {@link ShoppingListDraft} built from the supplied {@link List} of + * {@link ShoppingList}. + */ + @Nonnull + public static List mapToShoppingListDrafts( + @Nonnull final List shoppingLists) { + + return shoppingLists.stream() + .filter(Objects::nonNull) + .map(ShoppingListReferenceResolutionUtils::mapToShoppingListDraft) + .collect(toList()); + } + + /** + * Returns a @link ShoppingListDraft} consisting of the result of applying the mapping from {@link + * ShoppingList} to {@link ShoppingListDraft} with considering reference resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
customer{@link Reference}<{@link Customer}>{@link ResourceIdentifier}<{@link Customer}>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
lineItems.custom.type{@link Set}<{@link Reference}<{@link Type}>>{@link Set}<{@link ResourceIdentifier}<{@link Type}>>
textLineItems.custom.type{@link Set}<{@link Reference}<{@link Type}>>{@link Set}<{@link ResourceIdentifier}<{@link Type}>>
+ * + *

Note: The aforementioned references should be expanded with a key. Any reference that + * is not expanded will have its id in place and not replaced by the key will be considered as + * existing resources on the target commercetools project and the library will issues an + * update/create API request without reference resolution. + * + * @param shoppingList the shopping list with expanded references. + * @return a {@link ShoppingListDraft} built from the supplied {@link ShoppingList}. + */ + @Nonnull + public static ShoppingListDraft mapToShoppingListDraft(@Nonnull final ShoppingList shoppingList) { + + return ShoppingListDraftBuilder.of(shoppingList.getName()) + .description(shoppingList.getDescription()) + .key(shoppingList.getKey()) + .customer(getResourceIdentifierWithKey(shoppingList.getCustomer())) + .slug(shoppingList.getSlug()) + .lineItems(mapToLineItemDrafts(shoppingList.getLineItems())) + .textLineItems(mapToTextLineItemDrafts(shoppingList.getTextLineItems())) + .custom(mapToCustomFieldsDraft(shoppingList)) + .deleteDaysAfterLastModification(shoppingList.getDeleteDaysAfterLastModification()) + .anonymousId(shoppingList.getAnonymousId()) + .build(); + } + + @Nullable + private static List mapToLineItemDrafts(@Nullable final List lineItems) { + + if (lineItems == null) { + return null; } - /** - * Returns a @link ShoppingListDraft} consisting of the result of applying the - * mapping from {@link ShoppingList} to {@link ShoppingListDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
customer{@link Reference}<{@link Customer}>{@link ResourceIdentifier}<{@link Customer}>
custom.type{@link Reference}<{@link Type}>{@link ResourceIdentifier}<{@link Type}>
lineItems.custom.type{@link Set}<{@link Reference}<{@link Type}>>{@link Set}<{@link ResourceIdentifier}<{@link Type}>>
textLineItems.custom.type{@link Set}<{@link Reference}<{@link Type}>>{@link Set}<{@link ResourceIdentifier}<{@link Type}>>
- * - *

Note: The aforementioned references should be expanded with a key. - * Any reference that is not expanded will have its id in place and not replaced by the key will be - * considered as existing resources on the target commercetools project and - * the library will issues an update/create API request without reference resolution. - * - * @param shoppingList the shopping list with expanded references. - * @return a {@link ShoppingListDraft} built from the supplied {@link ShoppingList}. - */ - @Nonnull - public static ShoppingListDraft mapToShoppingListDraft(@Nonnull final ShoppingList shoppingList) { - - return ShoppingListDraftBuilder - .of(shoppingList.getName()) - .description(shoppingList.getDescription()) - .key(shoppingList.getKey()) - .customer(getResourceIdentifierWithKey(shoppingList.getCustomer())) - .slug(shoppingList.getSlug()) - .lineItems(mapToLineItemDrafts(shoppingList.getLineItems())) - .textLineItems(mapToTextLineItemDrafts(shoppingList.getTextLineItems())) - .custom(mapToCustomFieldsDraft(shoppingList)) - .deleteDaysAfterLastModification(shoppingList.getDeleteDaysAfterLastModification()) - .anonymousId(shoppingList.getAnonymousId()) - .build(); + return lineItems.stream() + .filter(Objects::nonNull) + .map(ShoppingListReferenceResolutionUtils::mapToLineItemDraft) + .filter(Objects::nonNull) + .collect(toList()); + } + + @Nullable + private static LineItemDraft mapToLineItemDraft(@Nonnull final LineItem lineItem) { + + if (lineItem.getVariant() != null) { + return LineItemDraftBuilder.ofSku(lineItem.getVariant().getSku(), lineItem.getQuantity()) + .addedAt(lineItem.getAddedAt()) + .custom(mapToCustomFieldsDraft(lineItem.getCustom())) + .build(); } - @Nullable - private static List mapToLineItemDrafts( - @Nullable final List lineItems) { + return null; + } - if (lineItems == null) { - return null; - } + @Nullable + private static List mapToTextLineItemDrafts( + @Nullable final List textLineItems) { - return lineItems.stream() - .filter(Objects::nonNull) - .map(ShoppingListReferenceResolutionUtils::mapToLineItemDraft) - .filter(Objects::nonNull) - .collect(toList()); + if (textLineItems == null) { + return null; } - @Nullable - private static LineItemDraft mapToLineItemDraft(@Nonnull final LineItem lineItem) { - - if (lineItem.getVariant() != null) { - return LineItemDraftBuilder - .ofSku(lineItem.getVariant().getSku(), lineItem.getQuantity()) - .addedAt(lineItem.getAddedAt()) - .custom(mapToCustomFieldsDraft(lineItem.getCustom())) - .build(); - } - - return null; - } - - @Nullable - private static List mapToTextLineItemDrafts( - @Nullable final List textLineItems) { - - if (textLineItems == null) { - return null; - } - - return textLineItems.stream() - .filter(Objects::nonNull) - .map(ShoppingListReferenceResolutionUtils::mapToTextLineItemDraft) - .collect(toList()); - } - - @Nonnull - private static TextLineItemDraft mapToTextLineItemDraft(@Nonnull final TextLineItem textLineItem) { - - return TextLineItemDraftBuilder.of(textLineItem.getName(), textLineItem.getQuantity()) - .description(textLineItem.getDescription()) - .addedAt(textLineItem.getAddedAt()) - .custom(mapToCustomFieldsDraft(textLineItem.getCustom())) - .build(); - } - - /** - * Builds a {@link ShoppingListQuery} for fetching shopping lists from a source CTP project with all the - * needed references expanded for the sync: - *

    - *
  • Customer
  • - *
  • Custom Type of the Shopping List
  • - *
  • Variants of the LineItems
  • - *
  • Custom Types of the LineItems
  • - *
  • Custom Types of the TextLineItems
  • - *
- * - *

Note: Please only use this util if you desire to sync all the aforementioned references from - * a source commercetools project. Otherwise, it is more efficient to build the query without expansions, if they - * are not needed, to avoid unnecessarily bigger payloads fetched from the source project. - * - * @return the query for fetching shopping lists from the source CTP project with all the aforementioned references - * expanded. - */ - public static ShoppingListQuery buildShoppingListQuery() { - return ShoppingListQuery.of() - .withExpansionPaths(ShoppingListExpansionModel::customer) - .plusExpansionPaths(ExpansionPath.of("custom.type")) - .plusExpansionPaths(ExpansionPath.of("lineItems[*].variant")) - .plusExpansionPaths(ExpansionPath.of("lineItems[*].custom.type")) - .plusExpansionPaths(ExpansionPath.of("textLineItems[*].custom.type")); - } - - private ShoppingListReferenceResolutionUtils() { - } + return textLineItems.stream() + .filter(Objects::nonNull) + .map(ShoppingListReferenceResolutionUtils::mapToTextLineItemDraft) + .collect(toList()); + } + + @Nonnull + private static TextLineItemDraft mapToTextLineItemDraft( + @Nonnull final TextLineItem textLineItem) { + + return TextLineItemDraftBuilder.of(textLineItem.getName(), textLineItem.getQuantity()) + .description(textLineItem.getDescription()) + .addedAt(textLineItem.getAddedAt()) + .custom(mapToCustomFieldsDraft(textLineItem.getCustom())) + .build(); + } + + /** + * Builds a {@link ShoppingListQuery} for fetching shopping lists from a source CTP project with + * all the needed references expanded for the sync: + * + *

    + *
  • Customer + *
  • Custom Type of the Shopping List + *
  • Variants of the LineItems + *
  • Custom Types of the LineItems + *
  • Custom Types of the TextLineItems + *
+ * + *

Note: Please only use this util if you desire to sync all the aforementioned references from + * a source commercetools project. Otherwise, it is more efficient to build the query without + * expansions, if they are not needed, to avoid unnecessarily bigger payloads fetched from the + * source project. + * + * @return the query for fetching shopping lists from the source CTP project with all the + * aforementioned references expanded. + */ + public static ShoppingListQuery buildShoppingListQuery() { + return ShoppingListQuery.of() + .withExpansionPaths(ShoppingListExpansionModel::customer) + .plusExpansionPaths(ExpansionPath.of("custom.type")) + .plusExpansionPaths(ExpansionPath.of("lineItems[*].variant")) + .plusExpansionPaths(ExpansionPath.of("lineItems[*].custom.type")) + .plusExpansionPaths(ExpansionPath.of("textLineItems[*].custom.type")); + } + + private ShoppingListReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtils.java b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtils.java index 4edee4ceb8..7c9bec2b9a 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtils.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtils.java @@ -1,13 +1,5 @@ package com.commercetools.sync.shoppinglists.utils; -import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.shoppinglists.ShoppingList; -import io.sphere.sdk.shoppinglists.ShoppingListDraft; - -import javax.annotation.Nonnull; -import java.util.List; - import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildPrimaryResourceCustomUpdateActions; import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildLineItemsUpdateActions; @@ -19,52 +11,62 @@ import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetSlugUpdateAction; import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildTextLineItemsUpdateActions; +import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.shoppinglists.ShoppingList; +import io.sphere.sdk.shoppinglists.ShoppingListDraft; +import java.util.List; +import javax.annotation.Nonnull; + public final class ShoppingListSyncUtils { - private static final ShoppingListCustomActionBuilder shoppingListCustomActionBuilder = - ShoppingListCustomActionBuilder.of(); + private static final ShoppingListCustomActionBuilder shoppingListCustomActionBuilder = + ShoppingListCustomActionBuilder.of(); - /** - * Compares all the fields of a {@link ShoppingList} and a {@link ShoppingListDraft}. It returns a {@link List} of - * {@link UpdateAction}<{@link ShoppingList}> as a result. If no update action is needed, for example in - * case where both the {@link ShoppingListDraft} and the {@link ShoppingList} have the same fields, an empty - * {@link List} is returned. - * - * @param oldShoppingList the shopping list which should be updated. - * @param newShoppingList the shopping list draft where we get the new data. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied - * by the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link ShoppingListSyncOptions} - * for more info. - * @return A list of shopping list specific update actions. - */ - @Nonnull - public static List> buildActions( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList, - @Nonnull final ShoppingListSyncOptions syncOptions) { + /** + * Compares all the fields of a {@link ShoppingList} and a {@link ShoppingListDraft}. It returns a + * {@link List} of {@link UpdateAction}<{@link ShoppingList}> as a result. If no update + * action is needed, for example in case where both the {@link ShoppingListDraft} and the {@link + * ShoppingList} have the same fields, an empty {@link List} is returned. + * + * @param oldShoppingList the shopping list which should be updated. + * @param newShoppingList the shopping list draft where we get the new data. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * ShoppingListSyncOptions} for more info. + * @return A list of shopping list specific update actions. + */ + @Nonnull + public static List> buildActions( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList, + @Nonnull final ShoppingListSyncOptions syncOptions) { - final List> updateActions = filterEmptyOptionals( + final List> updateActions = + filterEmptyOptionals( buildSetSlugUpdateAction(oldShoppingList, newShoppingList), buildChangeNameUpdateAction(oldShoppingList, newShoppingList), buildSetDescriptionUpdateAction(oldShoppingList, newShoppingList), buildSetAnonymousIdUpdateAction(oldShoppingList, newShoppingList), buildSetCustomerUpdateAction(oldShoppingList, newShoppingList), - buildSetDeleteDaysAfterLastModificationUpdateAction(oldShoppingList, newShoppingList) - ); + buildSetDeleteDaysAfterLastModificationUpdateAction(oldShoppingList, newShoppingList)); - updateActions.addAll(buildPrimaryResourceCustomUpdateActions(oldShoppingList, + updateActions.addAll( + buildPrimaryResourceCustomUpdateActions( + oldShoppingList, newShoppingList::getCustom, shoppingListCustomActionBuilder, syncOptions)); - updateActions.addAll(buildLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions)); + updateActions.addAll( + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions)); - updateActions.addAll(buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions)); + updateActions.addAll( + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions)); - return updateActions; - } + return updateActions; + } - private ShoppingListSyncUtils() { - } + private ShoppingListSyncUtils() {} } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtils.java b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtils.java index b749bfd504..08cccf2990 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.customers.Customer; import io.sphere.sdk.models.LocalizedString; @@ -15,150 +18,168 @@ import io.sphere.sdk.shoppinglists.commands.updateactions.SetDeleteDaysAfterLastModification; import io.sphere.sdk.shoppinglists.commands.updateactions.SetDescription; import io.sphere.sdk.shoppinglists.commands.updateactions.SetSlug; - +import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; public final class ShoppingListUpdateActionUtils { - private ShoppingListUpdateActionUtils() { - } - - /** - * Compares the {@link LocalizedString} slugs of a {@link ShoppingList} and a {@link ShoppingListDraft} and returns - * an {@link UpdateAction}<{@link ShoppingList}> as a result in an {@link Optional}. If both the - * {@link ShoppingList} and the {@link ShoppingListDraft} have the same slug, then no update action is needed and - * hence an empty {@link Optional} is returned. - * - * @param oldShoppingList the shopping list which should be updated. - * @param newShoppingList the shopping list draft where we get the new slug. - * @return A filled optional with the update action or an empty optional if the slugs are identical. - */ - @Nonnull - public static Optional> buildSetSlugUpdateAction( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList) { - - return buildUpdateAction(oldShoppingList.getSlug(), - newShoppingList.getSlug(), () -> SetSlug.of(newShoppingList.getSlug())); + private ShoppingListUpdateActionUtils() {} + + /** + * Compares the {@link LocalizedString} slugs of a {@link ShoppingList} and a {@link + * ShoppingListDraft} and returns an {@link UpdateAction}<{@link ShoppingList}> as a result + * in an {@link Optional}. If both the {@link ShoppingList} and the {@link ShoppingListDraft} have + * the same slug, then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldShoppingList the shopping list which should be updated. + * @param newShoppingList the shopping list draft where we get the new slug. + * @return A filled optional with the update action or an empty optional if the slugs are + * identical. + */ + @Nonnull + public static Optional> buildSetSlugUpdateAction( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList) { + + return buildUpdateAction( + oldShoppingList.getSlug(), + newShoppingList.getSlug(), + () -> SetSlug.of(newShoppingList.getSlug())); + } + + /** + * Compares the {@link LocalizedString} names of a {@link ShoppingList} and a {@link + * ShoppingListDraft} and returns an {@link UpdateAction}<{@link ShoppingList}> as a result + * in an {@link Optional}. If both the {@link ShoppingList} and the {@link ShoppingListDraft} have + * the same name, then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldShoppingList the shopping list which should be updated. + * @param newShoppingList the shopping list draft where we get the new name. + * @return A filled optional with the update action or an empty optional if the names are + * identical. + */ + @Nonnull + public static Optional> buildChangeNameUpdateAction( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList) { + + return buildUpdateAction( + oldShoppingList.getName(), + newShoppingList.getName(), + () -> ChangeName.of(newShoppingList.getName())); + } + + /** + * Compares the {@link LocalizedString} descriptions of {@link ShoppingList} and a {@link + * ShoppingListDraft} and returns an {@link UpdateAction}<{@link ShoppingList}> as a result + * in an {@link Optional}. If both the {@link ShoppingList} and the {@link ShoppingListDraft} have + * the same description, then no update action is needed and hence an empty {@link Optional} is + * returned. + * + * @param oldShoppingList the shopping list which should be updated. + * @param newShoppingList the shopping list draft where we get the new description. + * @return A filled optional with the update action or an empty optional if the descriptions are + * identical. + */ + @Nonnull + public static Optional> buildSetDescriptionUpdateAction( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList) { + + return buildUpdateAction( + oldShoppingList.getDescription(), + newShoppingList.getDescription(), + () -> SetDescription.of(newShoppingList.getDescription())); + } + + /** + * Compares the customer references of a {@link ShoppingList} and a {@link ShoppingListDraft} and + * returns an {@link UpdateAction}<{@link ShoppingList}> as a result in an {@link Optional}. + * If both the {@link ShoppingList} and the {@link ShoppingListDraft} have the same customer, then + * no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldShoppingList the shopping list which should be updated. + * @param newShoppingList the shopping list draft which holds the new customer. + * @return A filled optional with the update action or an empty optional if the customers are + * identical. + */ + @Nonnull + public static Optional> buildSetCustomerUpdateAction( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList) { + + return buildUpdateActionForReferences( + oldShoppingList.getCustomer(), + newShoppingList.getCustomer(), + () -> SetCustomer.of(mapResourceIdentifierToReferenceable(newShoppingList.getCustomer()))); + } + + @Nullable + private static Referenceable mapResourceIdentifierToReferenceable( + @Nullable final ResourceIdentifier resourceIdentifier) { + + if (resourceIdentifier == null) { + return null; // unset } - /** - * Compares the {@link LocalizedString} names of a {@link ShoppingList} and a {@link ShoppingListDraft} and returns - * an {@link UpdateAction}<{@link ShoppingList}> as a result in an {@link Optional}. If both the - * {@link ShoppingList} and the {@link ShoppingListDraft} have the same name, then no update action is needed and - * hence an empty {@link Optional} is returned. - * - * @param oldShoppingList the shopping list which should be updated. - * @param newShoppingList the shopping list draft where we get the new name. - * @return A filled optional with the update action or an empty optional if the names are identical. - */ - @Nonnull - public static Optional> buildChangeNameUpdateAction( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList) { - - return buildUpdateAction(oldShoppingList.getName(), - newShoppingList.getName(), () -> ChangeName.of(newShoppingList.getName())); - } - - /** - * Compares the {@link LocalizedString} descriptions of {@link ShoppingList} and a {@link ShoppingListDraft} and - * returns an {@link UpdateAction}<{@link ShoppingList}> as a result in an {@link Optional}. If both the - * {@link ShoppingList} and the {@link ShoppingListDraft} have the same description, then no update action is needed - * and hence an empty {@link Optional} is returned. - * - * @param oldShoppingList the shopping list which should be updated. - * @param newShoppingList the shopping list draft where we get the new description. - * @return A filled optional with the update action or an empty optional if the descriptions are identical. - */ - @Nonnull - public static Optional> buildSetDescriptionUpdateAction( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList) { - - return buildUpdateAction(oldShoppingList.getDescription(), - newShoppingList.getDescription(), () -> SetDescription.of(newShoppingList.getDescription())); - } - - /** - * Compares the customer references of a {@link ShoppingList} and a {@link ShoppingListDraft} and - * returns an {@link UpdateAction}<{@link ShoppingList}> as a result in an {@link Optional}. If both the - * {@link ShoppingList} and the {@link ShoppingListDraft} have the same customer, then no update action is needed - * and hence an empty {@link Optional} is returned. - * - * @param oldShoppingList the shopping list which should be updated. - * @param newShoppingList the shopping list draft which holds the new customer. - * @return A filled optional with the update action or an empty optional if the customers are identical. - */ - @Nonnull - public static Optional> buildSetCustomerUpdateAction( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList) { - - return buildUpdateActionForReferences(oldShoppingList.getCustomer(), newShoppingList.getCustomer(), - () -> SetCustomer.of(mapResourceIdentifierToReferenceable(newShoppingList.getCustomer()))); - } - - @Nullable - private static Referenceable mapResourceIdentifierToReferenceable( - @Nullable final ResourceIdentifier resourceIdentifier) { - - if (resourceIdentifier == null) { - return null; // unset - } - - // TODO (JVM-SDK), see: SUPPORT-10261 SetCustomerGroup could be created with a ResourceIdentifier - // https://github.com/commercetools/commercetools-jvm-sdk/issues/2072 - return new ResourceImpl(null, null, null, null) { - @Override - public Reference toReference() { - return Reference.of(Customer.referenceTypeId(), resourceIdentifier.getId()); - } - }; - } - - /** - * Compares the anonymousIds of {@link ShoppingList} and a {@link ShoppingListDraft} and - * returns an {@link UpdateAction}<{@link ShoppingList}> as a result in an {@link Optional}. If both the - * {@link ShoppingList} and the {@link ShoppingListDraft} have the same anonymousId, then no update action is needed - * and hence an empty {@link Optional} is returned. - * - * @param oldShoppingList the shopping list which should be updated. - * @param newShoppingList the shopping list draft which holds the new anonymousId. - * @return A filled optional with the update action or an empty optional if the anonymousIds are identical. - */ - @Nonnull - public static Optional> buildSetAnonymousIdUpdateAction( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList) { - - return buildUpdateAction(oldShoppingList.getAnonymousId(), newShoppingList.getAnonymousId(), - () -> SetAnonymousId.of(newShoppingList.getAnonymousId())); - } - - /** - * Compares the deleteDaysAfterLastModification of {@link ShoppingList} and a {@link ShoppingListDraft} and - * returns an {@link UpdateAction}<{@link ShoppingList}> as a result in an {@link Optional}. If both the - * {@link ShoppingList} and the {@link ShoppingListDraft} have the same deleteDaysAfterLastModification, then no - * update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldShoppingList the shopping list which should be updated. - * @param newShoppingList the shopping list draft which holds the new deleteDaysAfterLastModification. - * @return A filled optional with the update action or an empty optional if the deleteDaysAfterLastModifications - * are identical. - */ - @Nonnull - public static Optional> buildSetDeleteDaysAfterLastModificationUpdateAction( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList) { - - return buildUpdateAction(oldShoppingList.getDeleteDaysAfterLastModification(), - newShoppingList.getDeleteDaysAfterLastModification(), () -> SetDeleteDaysAfterLastModification.of( + // TODO (JVM-SDK), see: SUPPORT-10261 SetCustomerGroup could be created with a + // ResourceIdentifier + // https://github.com/commercetools/commercetools-jvm-sdk/issues/2072 + return new ResourceImpl(null, null, null, null) { + @Override + public Reference toReference() { + return Reference.of(Customer.referenceTypeId(), resourceIdentifier.getId()); + } + }; + } + + /** + * Compares the anonymousIds of {@link ShoppingList} and a {@link ShoppingListDraft} and returns + * an {@link UpdateAction}<{@link ShoppingList}> as a result in an {@link Optional}. If both + * the {@link ShoppingList} and the {@link ShoppingListDraft} have the same anonymousId, then no + * update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldShoppingList the shopping list which should be updated. + * @param newShoppingList the shopping list draft which holds the new anonymousId. + * @return A filled optional with the update action or an empty optional if the anonymousIds are + * identical. + */ + @Nonnull + public static Optional> buildSetAnonymousIdUpdateAction( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList) { + + return buildUpdateAction( + oldShoppingList.getAnonymousId(), + newShoppingList.getAnonymousId(), + () -> SetAnonymousId.of(newShoppingList.getAnonymousId())); + } + + /** + * Compares the deleteDaysAfterLastModification of {@link ShoppingList} and a {@link + * ShoppingListDraft} and returns an {@link UpdateAction}<{@link ShoppingList}> as a result + * in an {@link Optional}. If both the {@link ShoppingList} and the {@link ShoppingListDraft} have + * the same deleteDaysAfterLastModification, then no update action is needed and hence an empty + * {@link Optional} is returned. + * + * @param oldShoppingList the shopping list which should be updated. + * @param newShoppingList the shopping list draft which holds the new + * deleteDaysAfterLastModification. + * @return A filled optional with the update action or an empty optional if the + * deleteDaysAfterLastModifications are identical. + */ + @Nonnull + public static Optional> + buildSetDeleteDaysAfterLastModificationUpdateAction( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList) { + + return buildUpdateAction( + oldShoppingList.getDeleteDaysAfterLastModification(), + newShoppingList.getDeleteDaysAfterLastModification(), + () -> + SetDeleteDaysAfterLastModification.of( newShoppingList.getDeleteDaysAfterLastModification())); - } + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/utils/TextLineItemCustomActionBuilder.java b/src/main/java/com/commercetools/sync/shoppinglists/utils/TextLineItemCustomActionBuilder.java index 2fa8d89ef2..509d035a0b 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/TextLineItemCustomActionBuilder.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/TextLineItemCustomActionBuilder.java @@ -6,41 +6,41 @@ import io.sphere.sdk.shoppinglists.ShoppingList; import io.sphere.sdk.shoppinglists.commands.updateactions.SetTextLineItemCustomField; import io.sphere.sdk.shoppinglists.commands.updateactions.SetTextLineItemCustomType; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; -public final class TextLineItemCustomActionBuilder implements GenericCustomActionBuilder { - - @Nonnull - @Override - public UpdateAction buildRemoveCustomTypeAction( - @Nullable final Integer variantId, - @Nullable final String textLineItemId) { - - return SetTextLineItemCustomType.ofRemoveType(textLineItemId); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomTypeAction( - @Nullable final Integer variantId, - @Nullable final String textLineItemId, - @Nonnull final String customTypeId, - @Nullable final Map customFieldsJsonMap) { - - return SetTextLineItemCustomType.ofTypeIdAndJson(customTypeId, customFieldsJsonMap, textLineItemId); - } - - @Nonnull - @Override - public UpdateAction buildSetCustomFieldAction( - @Nullable final Integer variantId, - @Nullable final String textLineItemId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - - return SetTextLineItemCustomField.ofJson(customFieldName, customFieldValue, textLineItemId); - } +public final class TextLineItemCustomActionBuilder + implements GenericCustomActionBuilder { + + @Nonnull + @Override + public UpdateAction buildRemoveCustomTypeAction( + @Nullable final Integer variantId, @Nullable final String textLineItemId) { + + return SetTextLineItemCustomType.ofRemoveType(textLineItemId); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomTypeAction( + @Nullable final Integer variantId, + @Nullable final String textLineItemId, + @Nonnull final String customTypeId, + @Nullable final Map customFieldsJsonMap) { + + return SetTextLineItemCustomType.ofTypeIdAndJson( + customTypeId, customFieldsJsonMap, textLineItemId); + } + + @Nonnull + @Override + public UpdateAction buildSetCustomFieldAction( + @Nullable final Integer variantId, + @Nullable final String textLineItemId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + + return SetTextLineItemCustomField.ofJson(customFieldName, customFieldValue, textLineItemId); + } } diff --git a/src/main/java/com/commercetools/sync/shoppinglists/utils/TextLineItemUpdateActionUtils.java b/src/main/java/com/commercetools/sync/shoppinglists/utils/TextLineItemUpdateActionUtils.java index 4e062911bb..d81eadff6e 100644 --- a/src/main/java/com/commercetools/sync/shoppinglists/utils/TextLineItemUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/shoppinglists/utils/TextLineItemUpdateActionUtils.java @@ -1,5 +1,11 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.CustomUpdateActionUtils; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; @@ -14,277 +20,295 @@ import io.sphere.sdk.shoppinglists.commands.updateactions.ChangeTextLineItemQuantity; import io.sphere.sdk.shoppinglists.commands.updateactions.RemoveTextLineItem; import io.sphere.sdk.shoppinglists.commands.updateactions.SetTextLineItemDescription; -import org.apache.commons.lang3.math.NumberUtils; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.math.NumberUtils; public final class TextLineItemUpdateActionUtils { - /** - * Compares a list of {@link TextLineItem}s with a list of {@link TextLineItemDraft}s. - * The method takes in functions for building the required update actions (AddTextLineItem, RemoveTextLineItem and - * 1-1 update actions on text line items (e.g. changeTextLineItemQuantity, setTextLineItemCustomType, etc..). - * - *

If the list of new {@link TextLineItemDraft}s is {@code null}, then remove actions are built for every - * existing text line item. - * - * @param oldShoppingList shopping list resource, whose text line items should be updated. - * @param newShoppingList new shopping list draft, which contains the text line items to update. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return a list of text line item update actions on the resource of shopping lists, if the list of text line items - * are not identical. Otherwise, if the text line items are identical, an empty list is returned. - */ - @Nonnull - public static List> buildTextLineItemsUpdateActions( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList, - @Nonnull final ShoppingListSyncOptions syncOptions) { - - final boolean hasOldTextLineItems = oldShoppingList.getTextLineItems() != null - && !oldShoppingList.getTextLineItems().isEmpty(); - final boolean hasNewTextLineItems = newShoppingList.getTextLineItems() != null + /** + * Compares a list of {@link TextLineItem}s with a list of {@link TextLineItemDraft}s. The method + * takes in functions for building the required update actions (AddTextLineItem, + * RemoveTextLineItem and 1-1 update actions on text line items (e.g. changeTextLineItemQuantity, + * setTextLineItemCustomType, etc..). + * + *

If the list of new {@link TextLineItemDraft}s is {@code null}, then remove actions are built + * for every existing text line item. + * + * @param oldShoppingList shopping list resource, whose text line items should be updated. + * @param newShoppingList new shopping list draft, which contains the text line items to update. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return a list of text line item update actions on the resource of shopping lists, if the list + * of text line items are not identical. Otherwise, if the text line items are identical, an + * empty list is returned. + */ + @Nonnull + public static List> buildTextLineItemsUpdateActions( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList, + @Nonnull final ShoppingListSyncOptions syncOptions) { + + final boolean hasOldTextLineItems = + oldShoppingList.getTextLineItems() != null && !oldShoppingList.getTextLineItems().isEmpty(); + final boolean hasNewTextLineItems = + newShoppingList.getTextLineItems() != null && !newShoppingList.getTextLineItems().isEmpty() && newShoppingList.getTextLineItems().stream().anyMatch(Objects::nonNull); - if (hasOldTextLineItems && !hasNewTextLineItems) { - - return oldShoppingList.getTextLineItems() - .stream() - .map(RemoveTextLineItem::of) - .collect(toList()); - - } else if (!hasOldTextLineItems) { + if (hasOldTextLineItems && !hasNewTextLineItems) { - if (!hasNewTextLineItems) { - return emptyList(); - } + return oldShoppingList.getTextLineItems().stream() + .map(RemoveTextLineItem::of) + .collect(toList()); - return newShoppingList.getTextLineItems() - .stream() - .filter(Objects::nonNull) - .filter(TextLineItemUpdateActionUtils::hasQuantity) - .map(AddTextLineItemWithAddedAt::of) - .collect(toList()); - } + } else if (!hasOldTextLineItems) { - final List oldTextLineItems = oldShoppingList.getTextLineItems(); - final List newTextLineItems = newShoppingList.getTextLineItems() - .stream() - .filter(Objects::nonNull) - .collect(toList()); + if (!hasNewTextLineItems) { + return emptyList(); + } - return buildUpdateActions(oldShoppingList, newShoppingList, oldTextLineItems, newTextLineItems, syncOptions); + return newShoppingList.getTextLineItems().stream() + .filter(Objects::nonNull) + .filter(TextLineItemUpdateActionUtils::hasQuantity) + .map(AddTextLineItemWithAddedAt::of) + .collect(toList()); } - private static boolean hasQuantity(@Nonnull final TextLineItemDraft textLineItemDraft) { - /* + final List oldTextLineItems = oldShoppingList.getTextLineItems(); + final List newTextLineItems = + newShoppingList.getTextLineItems().stream().filter(Objects::nonNull).collect(toList()); - with this check, it's avoided bad request case like below: + return buildUpdateActions( + oldShoppingList, newShoppingList, oldTextLineItems, newTextLineItems, syncOptions); + } - "code": "InvalidField", - "message": "The value '0' is not valid for field 'quantity'. Quantity 0 is not allowed.", + private static boolean hasQuantity(@Nonnull final TextLineItemDraft textLineItemDraft) { + /* - */ - return textLineItemDraft.getQuantity() != null && textLineItemDraft.getQuantity() > 0; - } + with this check, it's avoided bad request case like below: - /** - * The decisions in the calculating update actions are documented on the - * `docs/adr/0002-shopping-lists-lineitem-and-textlineitem-update-actions.md` - */ - @Nonnull - private static List> buildUpdateActions( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList, - @Nonnull final List oldTextLineItems, - @Nonnull final List newTextLineItems, - @Nonnull final ShoppingListSyncOptions syncOptions) { - - final List> updateActions = new ArrayList<>(); - - final int minSize = Math.min(oldTextLineItems.size(), newTextLineItems.size()); - for (int i = 0; i < minSize; i++) { - - final TextLineItem oldTextLineItem = oldTextLineItems.get(i); - final TextLineItemDraft newTextLineItem = newTextLineItems.get(i); - - if (newTextLineItem.getName() == null || newTextLineItem.getName().getLocales().isEmpty()) { - /* - checking the name of the oldTextLineItem is not needed, because it's required. - with this check below, it's avoided bad request case like: - - "detailedErrorMessage": "actions -> name: Missing required value" - */ - syncOptions.applyErrorCallback(new SyncException( - format("TextLineItemDraft at position '%d' of the ShoppingListDraft with key '%s' has no name " - + "set. Please make sure all text line items have names.", i, newShoppingList.getKey())), - oldShoppingList, newShoppingList, updateActions); - - return emptyList(); - } - - updateActions.addAll(buildTextLineItemUpdateActions( - oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, syncOptions)); - } - - for (int i = minSize; i < oldTextLineItems.size(); i++) { - updateActions.add(RemoveTextLineItem.of(oldTextLineItems.get(i).getId())); - } - - for (int i = minSize; i < newTextLineItems.size(); i++) { - if (hasQuantity(newTextLineItems.get(i))) { - updateActions.add(AddTextLineItemWithAddedAt.of(newTextLineItems.get(i))); - } - } - - return updateActions; - } + "code": "InvalidField", + "message": "The value '0' is not valid for field 'quantity'. Quantity 0 is not allowed.", - /** - * Compares all the fields of a {@link TextLineItem} and a {@link TextLineItemDraft} and returns a list of - * {@link UpdateAction}<{@link ShoppingList}> as a result. If both the {@link TextLineItem} and - * the {@link TextLineItemDraft} have identical fields, then no update action is needed and hence an empty - * {@link List} is returned. - * - * @param oldShoppingList shopping list resource, whose line item should be updated. - * @param newShoppingList new shopping list draft, which contains the line item to update. - * @param oldTextLineItem the text line item which should be updated. - * @param newTextLineItem the text line item draft where we get the new fields (i.e. quantity, custom fields). - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the update actions or an empty list if the text line item fields are identical. - */ - @Nonnull - public static List> buildTextLineItemUpdateActions( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList, - @Nonnull final TextLineItem oldTextLineItem, - @Nonnull final TextLineItemDraft newTextLineItem, - @Nonnull final ShoppingListSyncOptions syncOptions) { - - final List> updateActions = filterEmptyOptionals( - buildChangeTextLineItemNameUpdateAction(oldTextLineItem, newTextLineItem), - buildSetTextLineItemDescriptionUpdateAction(oldTextLineItem, newTextLineItem), - buildChangeTextLineItemQuantityUpdateAction(oldTextLineItem, newTextLineItem) - ); + */ + return textLineItemDraft.getQuantity() != null && textLineItemDraft.getQuantity() > 0; + } - updateActions.addAll(buildTextLineItemCustomUpdateActions( - oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, syncOptions)); + /** + * The decisions in the calculating update actions are documented on the + * `docs/adr/0002-shopping-lists-lineitem-and-textlineitem-update-actions.md` + */ + @Nonnull + private static List> buildUpdateActions( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList, + @Nonnull final List oldTextLineItems, + @Nonnull final List newTextLineItems, + @Nonnull final ShoppingListSyncOptions syncOptions) { - return updateActions; - } + final List> updateActions = new ArrayList<>(); - /** - * Compares the {@link LocalizedString} names of {@link TextLineItem} and a {@link TextLineItemDraft} and - * returns an {@link Optional} of update action, which would contain the {@code "changeTextLineItemName"} - * {@link UpdateAction}. If both the {@link TextLineItem} and the {@link TextLineItemDraft} have the same - * {@code description} values, then no update action is needed and hence an empty optional will be returned. - * - * @param oldTextLineItem the text line item which should be updated. - * @param newTextLineItem the text line item draft where we get the new name. - * @return A filled optional with the update action or an empty optional if the names are identical. - */ - @Nonnull - public static Optional> buildChangeTextLineItemNameUpdateAction( - @Nonnull final TextLineItem oldTextLineItem, - @Nonnull final TextLineItemDraft newTextLineItem) { - - return buildUpdateAction(oldTextLineItem.getName(), newTextLineItem.getName(), () -> - ChangeTextLineItemName.of(oldTextLineItem.getId(), newTextLineItem.getName())); - } + final int minSize = Math.min(oldTextLineItems.size(), newTextLineItems.size()); + for (int i = 0; i < minSize; i++) { - /** - * Compares the {@link LocalizedString} descriptions of {@link TextLineItem} and a {@link TextLineItemDraft} and - * returns an {@link Optional} of update action, which would contain the {@code "setTextLineItemDescription"} - * {@link UpdateAction}. If both the {@link TextLineItem} and the {@link TextLineItemDraft} have the same - * {@code description} values, then no update action is needed and hence an empty optional will be returned. - * - * @param oldTextLineItem the text line item which should be updated. - * @param newTextLineItem the text line item draft where we get the new description. - * @return A filled optional with the update action or an empty optional if the descriptions are identical. - */ - @Nonnull - public static Optional> buildSetTextLineItemDescriptionUpdateAction( - @Nonnull final TextLineItem oldTextLineItem, - @Nonnull final TextLineItemDraft newTextLineItem) { - - return buildUpdateAction(oldTextLineItem.getDescription(), newTextLineItem.getDescription(), () -> - SetTextLineItemDescription.of(oldTextLineItem).withDescription(newTextLineItem.getDescription())); - } + final TextLineItem oldTextLineItem = oldTextLineItems.get(i); + final TextLineItemDraft newTextLineItem = newTextLineItems.get(i); - /** - * Compares the {@code quantity} values of a {@link TextLineItem} and a {@link TextLineItemDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeTextLineItemQuantity"} - * {@link UpdateAction}. If both {@link TextLineItem} and {@link TextLineItemDraft} have the same - * {@code quantity} values, then no update action is needed and empty optional will be returned. - * - *

Note: If {@code quantity} from the {@code newTextLineItem} is {@code null}, the new {@code quantity} - * will be set to default value {@code 1L}. If {@code quantity} from the {@code newTextLineItem} is {@code 0}, - * then it means removing the text line item. - * - * @param oldTextLineItem the text line item which should be updated. - * @param newTextLineItem the text line item draft where we get the new quantity. - * @return A filled optional with the update action or an empty optional if the quantities are identical. - */ - @Nonnull - public static Optional> buildChangeTextLineItemQuantityUpdateAction( - @Nonnull final TextLineItem oldTextLineItem, - @Nonnull final TextLineItemDraft newTextLineItem) { - - final Long newTextLineItemQuantity = newTextLineItem.getQuantity() == null - ? NumberUtils.LONG_ONE : newTextLineItem.getQuantity(); - - return buildUpdateAction(oldTextLineItem.getQuantity(), newTextLineItemQuantity, - () -> ChangeTextLineItemQuantity.of(oldTextLineItem.getId(), newTextLineItemQuantity)); - } + if (newTextLineItem.getName() == null || newTextLineItem.getName().getLocales().isEmpty()) { + /* + checking the name of the oldTextLineItem is not needed, because it's required. + with this check below, it's avoided bad request case like: - /** - * Compares the custom fields and custom types of a {@link TextLineItem} and a {@link TextLineItemDraft} and returns - * a list of {@link UpdateAction}<{@link ShoppingList}> as a result. If both the {@link TextLineItem} and the - * {@link TextLineItemDraft} have identical custom fields and types, then no update action is needed and hence an - * empty {@link List} is returned. - * - * @param oldShoppingList shopping list resource, whose text line item should be updated. - * @param newShoppingList new shopping list draft, which contains the text line item to update. - * @param oldTextLineItem the text line item which should be updated. - * @param newTextLineItem the text line item draft where we get the new custom fields and types. - * @param syncOptions responsible for supplying the sync options to the sync utility method. It is used for - * triggering the error callback within the utility, in case of errors. - * @return A list with the custom field/type update actions or an empty list if the custom fields/types are - * identical. - */ - @Nonnull - public static List> buildTextLineItemCustomUpdateActions( - @Nonnull final ShoppingList oldShoppingList, - @Nonnull final ShoppingListDraft newShoppingList, - @Nonnull final TextLineItem oldTextLineItem, - @Nonnull final TextLineItemDraft newTextLineItem, - @Nonnull final ShoppingListSyncOptions syncOptions) { - - return CustomUpdateActionUtils.buildCustomUpdateActions( + "detailedErrorMessage": "actions -> name: Missing required value" + */ + syncOptions.applyErrorCallback( + new SyncException( + format( + "TextLineItemDraft at position '%d' of the ShoppingListDraft with key '%s' has no name " + + "set. Please make sure all text line items have names.", + i, newShoppingList.getKey())), oldShoppingList, newShoppingList, - oldTextLineItem::getCustom, - newTextLineItem::getCustom, - new TextLineItemCustomActionBuilder(), - null, // not used by util. - t -> oldTextLineItem.getId(), - textLineItem -> TextLineItem.resourceTypeId(), - t -> oldTextLineItem.getId(), - syncOptions); + updateActions); + + return emptyList(); + } + + updateActions.addAll( + buildTextLineItemUpdateActions( + oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, syncOptions)); } - private TextLineItemUpdateActionUtils() { + for (int i = minSize; i < oldTextLineItems.size(); i++) { + updateActions.add(RemoveTextLineItem.of(oldTextLineItems.get(i).getId())); } + + for (int i = minSize; i < newTextLineItems.size(); i++) { + if (hasQuantity(newTextLineItems.get(i))) { + updateActions.add(AddTextLineItemWithAddedAt.of(newTextLineItems.get(i))); + } + } + + return updateActions; + } + + /** + * Compares all the fields of a {@link TextLineItem} and a {@link TextLineItemDraft} and returns a + * list of {@link UpdateAction}<{@link ShoppingList}> as a result. If both the {@link + * TextLineItem} and the {@link TextLineItemDraft} have identical fields, then no update action is + * needed and hence an empty {@link List} is returned. + * + * @param oldShoppingList shopping list resource, whose line item should be updated. + * @param newShoppingList new shopping list draft, which contains the line item to update. + * @param oldTextLineItem the text line item which should be updated. + * @param newTextLineItem the text line item draft where we get the new fields (i.e. quantity, + * custom fields). + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the update actions or an empty list if the text line item fields are + * identical. + */ + @Nonnull + public static List> buildTextLineItemUpdateActions( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList, + @Nonnull final TextLineItem oldTextLineItem, + @Nonnull final TextLineItemDraft newTextLineItem, + @Nonnull final ShoppingListSyncOptions syncOptions) { + + final List> updateActions = + filterEmptyOptionals( + buildChangeTextLineItemNameUpdateAction(oldTextLineItem, newTextLineItem), + buildSetTextLineItemDescriptionUpdateAction(oldTextLineItem, newTextLineItem), + buildChangeTextLineItemQuantityUpdateAction(oldTextLineItem, newTextLineItem)); + + updateActions.addAll( + buildTextLineItemCustomUpdateActions( + oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, syncOptions)); + + return updateActions; + } + + /** + * Compares the {@link LocalizedString} names of {@link TextLineItem} and a {@link + * TextLineItemDraft} and returns an {@link Optional} of update action, which would contain the + * {@code "changeTextLineItemName"} {@link UpdateAction}. If both the {@link TextLineItem} and the + * {@link TextLineItemDraft} have the same {@code description} values, then no update action is + * needed and hence an empty optional will be returned. + * + * @param oldTextLineItem the text line item which should be updated. + * @param newTextLineItem the text line item draft where we get the new name. + * @return A filled optional with the update action or an empty optional if the names are + * identical. + */ + @Nonnull + public static Optional> buildChangeTextLineItemNameUpdateAction( + @Nonnull final TextLineItem oldTextLineItem, + @Nonnull final TextLineItemDraft newTextLineItem) { + + return buildUpdateAction( + oldTextLineItem.getName(), + newTextLineItem.getName(), + () -> ChangeTextLineItemName.of(oldTextLineItem.getId(), newTextLineItem.getName())); + } + + /** + * Compares the {@link LocalizedString} descriptions of {@link TextLineItem} and a {@link + * TextLineItemDraft} and returns an {@link Optional} of update action, which would contain the + * {@code "setTextLineItemDescription"} {@link UpdateAction}. If both the {@link TextLineItem} and + * the {@link TextLineItemDraft} have the same {@code description} values, then no update action + * is needed and hence an empty optional will be returned. + * + * @param oldTextLineItem the text line item which should be updated. + * @param newTextLineItem the text line item draft where we get the new description. + * @return A filled optional with the update action or an empty optional if the descriptions are + * identical. + */ + @Nonnull + public static Optional> buildSetTextLineItemDescriptionUpdateAction( + @Nonnull final TextLineItem oldTextLineItem, + @Nonnull final TextLineItemDraft newTextLineItem) { + + return buildUpdateAction( + oldTextLineItem.getDescription(), + newTextLineItem.getDescription(), + () -> + SetTextLineItemDescription.of(oldTextLineItem) + .withDescription(newTextLineItem.getDescription())); + } + + /** + * Compares the {@code quantity} values of a {@link TextLineItem} and a {@link TextLineItemDraft} + * and returns an {@link Optional} of update action, which would contain the {@code + * "changeTextLineItemQuantity"} {@link UpdateAction}. If both {@link TextLineItem} and {@link + * TextLineItemDraft} have the same {@code quantity} values, then no update action is needed and + * empty optional will be returned. + * + *

Note: If {@code quantity} from the {@code newTextLineItem} is {@code null}, the new {@code + * quantity} will be set to default value {@code 1L}. If {@code quantity} from the {@code + * newTextLineItem} is {@code 0}, then it means removing the text line item. + * + * @param oldTextLineItem the text line item which should be updated. + * @param newTextLineItem the text line item draft where we get the new quantity. + * @return A filled optional with the update action or an empty optional if the quantities are + * identical. + */ + @Nonnull + public static Optional> buildChangeTextLineItemQuantityUpdateAction( + @Nonnull final TextLineItem oldTextLineItem, + @Nonnull final TextLineItemDraft newTextLineItem) { + + final Long newTextLineItemQuantity = + newTextLineItem.getQuantity() == null + ? NumberUtils.LONG_ONE + : newTextLineItem.getQuantity(); + + return buildUpdateAction( + oldTextLineItem.getQuantity(), + newTextLineItemQuantity, + () -> ChangeTextLineItemQuantity.of(oldTextLineItem.getId(), newTextLineItemQuantity)); + } + + /** + * Compares the custom fields and custom types of a {@link TextLineItem} and a {@link + * TextLineItemDraft} and returns a list of {@link UpdateAction}<{@link ShoppingList}> as a + * result. If both the {@link TextLineItem} and the {@link TextLineItemDraft} have identical + * custom fields and types, then no update action is needed and hence an empty {@link List} is + * returned. + * + * @param oldShoppingList shopping list resource, whose text line item should be updated. + * @param newShoppingList new shopping list draft, which contains the text line item to update. + * @param oldTextLineItem the text line item which should be updated. + * @param newTextLineItem the text line item draft where we get the new custom fields and types. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the custom field/type update actions or an empty list if the custom + * fields/types are identical. + */ + @Nonnull + public static List> buildTextLineItemCustomUpdateActions( + @Nonnull final ShoppingList oldShoppingList, + @Nonnull final ShoppingListDraft newShoppingList, + @Nonnull final TextLineItem oldTextLineItem, + @Nonnull final TextLineItemDraft newTextLineItem, + @Nonnull final ShoppingListSyncOptions syncOptions) { + + return CustomUpdateActionUtils.buildCustomUpdateActions( + oldShoppingList, + newShoppingList, + oldTextLineItem::getCustom, + newTextLineItem::getCustom, + new TextLineItemCustomActionBuilder(), + null, // not used by util. + t -> oldTextLineItem.getId(), + textLineItem -> TextLineItem.resourceTypeId(), + t -> oldTextLineItem.getId(), + syncOptions); + } + + private TextLineItemUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/states/StateSync.java b/src/main/java/com/commercetools/sync/states/StateSync.java index 85239d9cc0..7874c62343 100644 --- a/src/main/java/com/commercetools/sync/states/StateSync.java +++ b/src/main/java/com/commercetools/sync/states/StateSync.java @@ -1,5 +1,14 @@ package com.commercetools.sync.states; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static com.commercetools.sync.states.utils.StateSyncUtils.buildActions; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.allOf; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.models.WaitingToBeResolvedTransitions; @@ -14,10 +23,6 @@ import io.sphere.sdk.models.Reference; import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -29,422 +34,460 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static com.commercetools.sync.states.utils.StateSyncUtils.buildActions; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.allOf; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class StateSync extends BaseSync { - private static final String CTP_STATE_FETCH_FAILED = "Failed to fetch existing states with keys: '%s'."; - private static final String CTP_STATE_UPDATE_FAILED = "Failed to update state with key: '%s'. Reason: %s"; - - private static final String FAILED_TO_PROCESS = "Failed to process the StateDraft with key: '%s'. Reason: %s"; - private static final String UNRESOLVED_TRANSITIONS_STORE_FETCH_FAILED = "Failed to fetch StateDrafts waiting to " - + "be resolved with keys '%s'."; - - private final StateService stateService; - private final StateReferenceResolver stateReferenceResolver; - private final UnresolvedTransitionsService unresolvedTransitionsService; - private final StateBatchValidator batchValidator; - - private ConcurrentHashMap.KeySetView readyToResolve; - - public StateSync(@Nonnull final StateSyncOptions stateSyncOptions) { - this(stateSyncOptions, new StateServiceImpl(stateSyncOptions)); + private static final String CTP_STATE_FETCH_FAILED = + "Failed to fetch existing states with keys: '%s'."; + private static final String CTP_STATE_UPDATE_FAILED = + "Failed to update state with key: '%s'. Reason: %s"; + + private static final String FAILED_TO_PROCESS = + "Failed to process the StateDraft with key: '%s'. Reason: %s"; + private static final String UNRESOLVED_TRANSITIONS_STORE_FETCH_FAILED = + "Failed to fetch StateDrafts waiting to " + "be resolved with keys '%s'."; + + private final StateService stateService; + private final StateReferenceResolver stateReferenceResolver; + private final UnresolvedTransitionsService unresolvedTransitionsService; + private final StateBatchValidator batchValidator; + + private ConcurrentHashMap.KeySetView readyToResolve; + + public StateSync(@Nonnull final StateSyncOptions stateSyncOptions) { + this(stateSyncOptions, new StateServiceImpl(stateSyncOptions)); + } + + /** + * Takes a {@link StateSyncOptions} and a {@link StateSync} instances to instantiate a new {@link + * StateSync} instance that could be used to sync state drafts in the CTP project specified in the + * injected {@link StateSyncOptions} instance. + * + *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and + * passed to. + * + * @param stateSyncOptions the container of all the options of the sync process including the CTP + * project client and/or configuration and other sync-specific options. + * @param stateService the type service which is responsible for fetching/caching the Types from + * the CTP project. + */ + StateSync( + @Nonnull final StateSyncOptions stateSyncOptions, @Nonnull final StateService stateService) { + super(new StateSyncStatistics(), stateSyncOptions); + this.stateService = stateService; + this.stateReferenceResolver = new StateReferenceResolver(getSyncOptions(), stateService); + this.unresolvedTransitionsService = new UnresolvedTransitionsServiceImpl(getSyncOptions()); + this.batchValidator = new StateBatchValidator(getSyncOptions(), getStatistics()); + } + + @Override + protected CompletionStage process( + @Nonnull final List resourceDrafts) { + List> batches = batchElements(resourceDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, completedFuture(statistics)); + } + + @Override + protected CompletionStage processBatch( + @Nonnull final List batch) { + readyToResolve = ConcurrentHashMap.newKeySet(); + + final ImmutablePair, Set> result = + batchValidator.validateAndCollectReferencedKeys(batch); + + final Set validDrafts = result.getLeft(); + if (validDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return CompletableFuture.completedFuture(statistics); } - /** - * Takes a {@link StateSyncOptions} and a {@link StateSync} instances to instantiate - * a new {@link StateSync} instance that could be used to sync state drafts in the CTP project specified - * in the injected {@link StateSyncOptions} instance. - * - *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and passed to. - * - * @param stateSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param stateService the type service which is responsible for fetching/caching the Types from the CTP - * project. - */ - StateSync(@Nonnull final StateSyncOptions stateSyncOptions, - @Nonnull final StateService stateService) { - super(new StateSyncStatistics(), stateSyncOptions); - this.stateService = stateService; - this.stateReferenceResolver = new StateReferenceResolver(getSyncOptions(), stateService); - this.unresolvedTransitionsService = new UnresolvedTransitionsServiceImpl(getSyncOptions()); - this.batchValidator = new StateBatchValidator(getSyncOptions(), getStatistics()); - } - - @Override - protected CompletionStage process(@Nonnull final List resourceDrafts) { - List> batches = batchElements(resourceDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, completedFuture(statistics)); - } - - @Override - protected CompletionStage processBatch(@Nonnull final List batch) { - readyToResolve = ConcurrentHashMap.newKeySet(); - - final ImmutablePair, Set> result = - batchValidator.validateAndCollectReferencedKeys(batch); - - final Set validDrafts = result.getLeft(); - if (validDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return CompletableFuture.completedFuture(statistics); - } - - final Set stateTransitionKeys = result.getRight(); - - return stateService - .cacheKeysToIds(stateTransitionKeys) - .handle(ImmutablePair::new) - .thenCompose(cachingResponse -> { - final Throwable cachingException = cachingResponse.getValue(); - if (cachingException != null) { - handleError(new SyncException("Failed to build a cache of keys to ids.", cachingException), - null,null,null , validDrafts.size()); - return CompletableFuture.completedFuture(null); - } - - final Map keyToIdCache = cachingResponse.getKey(); - return syncBatch(validDrafts, keyToIdCache); + final Set stateTransitionKeys = result.getRight(); + + return stateService + .cacheKeysToIds(stateTransitionKeys) + .handle(ImmutablePair::new) + .thenCompose( + cachingResponse -> { + final Throwable cachingException = cachingResponse.getValue(); + if (cachingException != null) { + handleError( + new SyncException("Failed to build a cache of keys to ids.", cachingException), + null, + null, + null, + validDrafts.size()); + return CompletableFuture.completedFuture(null); + } + + final Map keyToIdCache = cachingResponse.getKey(); + return syncBatch(validDrafts, keyToIdCache); }) - .thenApply(ignored -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignored -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed states to sync. + * + * @param syncException The exception that caused the failure. + * @param failedTimes The number of times that the failed states counter is incremented. + */ + private void handleError( + @Nonnull final SyncException syncException, + @Nullable final State entry, + @Nullable final StateDraft draft, + @Nullable final List> updateActions, + final int failedTimes) { + syncOptions.applyErrorCallback(syncException, entry, draft, updateActions); + ; + statistics.incrementFailed(failedTimes); + } + + @Nonnull + private CompletionStage syncBatch( + @Nonnull final Set stateDrafts, @Nonnull final Map keyToIdCache) { + + if (stateDrafts.isEmpty()) { + return CompletableFuture.completedFuture(null); } - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed states to sync. - * - * @param syncException The exception that caused the failure. - * @param failedTimes The number of times that the failed states counter is incremented. - */ - private void handleError(@Nonnull final SyncException syncException,@Nullable final State entry, - @Nullable final StateDraft draft, @Nullable final List> updateActions, - final int failedTimes) { - syncOptions.applyErrorCallback(syncException, entry, draft, updateActions);; - statistics.incrementFailed(failedTimes); - } - - @Nonnull - private CompletionStage syncBatch(@Nonnull final Set stateDrafts, - @Nonnull final Map keyToIdCache) { - - if (stateDrafts.isEmpty()) { - return CompletableFuture.completedFuture(null); - } - - final Set stateDraftKeys = stateDrafts - .stream() - .map(StateDraft::getKey) - .collect(Collectors.toSet()); - - return stateService - .fetchMatchingStatesByKeysWithTransitions(stateDraftKeys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Throwable fetchException = fetchResponse.getValue(); - if (fetchException != null) { - final String errorMessage = format(CTP_STATE_FETCH_FAILED, stateDraftKeys); - handleError(new SyncException(errorMessage, fetchException),null, null,null, - stateDraftKeys.size()); - return CompletableFuture.completedFuture(null); - } else { - final Set matchingStates = fetchResponse.getKey(); - return syncOrKeepTrack(stateDrafts, matchingStates, keyToIdCache) - .thenCompose(aVoid -> resolveNowReadyReferences(keyToIdCache)); - } + final Set stateDraftKeys = + stateDrafts.stream().map(StateDraft::getKey).collect(Collectors.toSet()); + + return stateService + .fetchMatchingStatesByKeysWithTransitions(stateDraftKeys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Throwable fetchException = fetchResponse.getValue(); + if (fetchException != null) { + final String errorMessage = format(CTP_STATE_FETCH_FAILED, stateDraftKeys); + handleError( + new SyncException(errorMessage, fetchException), + null, + null, + null, + stateDraftKeys.size()); + return CompletableFuture.completedFuture(null); + } else { + final Set matchingStates = fetchResponse.getKey(); + return syncOrKeepTrack(stateDrafts, matchingStates, keyToIdCache) + .thenCompose(aVoid -> resolveNowReadyReferences(keyToIdCache)); + } }); - } - - /** - * Given a set of state drafts, for each new draft: if it doesn't have any state references which are missing, - * it syncs the new draft. However, if it does have missing references, it keeps track of it by persisting it. - * - * @param newStates drafts that need to be synced. - * @param oldStates old states. - * @param keyToIdCache the cache containing the mapping of all existing state keys to ids. - * @return a {@link CompletionStage} which contains an empty result after execution of the update - */ - @Nonnull - private CompletionStage syncOrKeepTrack( - @Nonnull final Set newStates, - @Nonnull final Set oldStates, - @Nonnull final Map keyToIdCache) { - - return allOf(newStates - .stream() - .map(newDraft -> { - final Set missingTransitionStateKeys = - getMissingTransitionStateKeys(newDraft, keyToIdCache); - - if (!missingTransitionStateKeys.isEmpty()) { + } + + /** + * Given a set of state drafts, for each new draft: if it doesn't have any state references which + * are missing, it syncs the new draft. However, if it does have missing references, it keeps + * track of it by persisting it. + * + * @param newStates drafts that need to be synced. + * @param oldStates old states. + * @param keyToIdCache the cache containing the mapping of all existing state keys to ids. + * @return a {@link CompletionStage} which contains an empty result after execution of the update + */ + @Nonnull + private CompletionStage syncOrKeepTrack( + @Nonnull final Set newStates, + @Nonnull final Set oldStates, + @Nonnull final Map keyToIdCache) { + + return allOf( + newStates.stream() + .map( + newDraft -> { + final Set missingTransitionStateKeys = + getMissingTransitionStateKeys(newDraft, keyToIdCache); + + if (!missingTransitionStateKeys.isEmpty()) { return keepTrackOfMissingTransitionStates(newDraft, missingTransitionStateKeys); - } else { + } else { return syncDraft(oldStates, newDraft); - } - }) + } + }) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); - } + } - private Set getMissingTransitionStateKeys( - @Nonnull final StateDraft newState, - @Nonnull final Map keyToIdCache) { + private Set getMissingTransitionStateKeys( + @Nonnull final StateDraft newState, @Nonnull final Map keyToIdCache) { - if (newState.getTransitions() == null || newState.getTransitions().isEmpty()) { - return Collections.emptySet(); - } - - return newState.getTransitions() - .stream() - .map(Reference::getId) - .filter(key -> !keyToIdCache.containsKey(key)) - .collect(Collectors.toSet()); + if (newState.getTransitions() == null || newState.getTransitions().isEmpty()) { + return Collections.emptySet(); } - private CompletionStage> keepTrackOfMissingTransitionStates( - @Nonnull final StateDraft newState, - @Nonnull final Set missingTransitionParentStateKeys) { - - missingTransitionParentStateKeys.forEach(missingParentKey -> - statistics.addMissingDependency(missingParentKey, newState.getKey())); + return newState.getTransitions().stream() + .map(Reference::getId) + .filter(key -> !keyToIdCache.containsKey(key)) + .collect(Collectors.toSet()); + } - return unresolvedTransitionsService.save( - new WaitingToBeResolvedTransitions(newState, missingTransitionParentStateKeys)); - } + private CompletionStage> + keepTrackOfMissingTransitionStates( + @Nonnull final StateDraft newState, + @Nonnull final Set missingTransitionParentStateKeys) { - @Nonnull - private CompletionStage syncDraft( - @Nonnull final Set oldStates, - @Nonnull final StateDraft newStateDraft) { + missingTransitionParentStateKeys.forEach( + missingParentKey -> statistics.addMissingDependency(missingParentKey, newState.getKey())); - final Map oldStateMap = - oldStates.stream().collect(toMap(State::getKey, identity())); + return unresolvedTransitionsService.save( + new WaitingToBeResolvedTransitions(newState, missingTransitionParentStateKeys)); + } - return stateReferenceResolver - .resolveReferences(newStateDraft) - .thenCompose(resolvedDraft -> { + @Nonnull + private CompletionStage syncDraft( + @Nonnull final Set oldStates, @Nonnull final StateDraft newStateDraft) { - final State oldState = oldStateMap.get(newStateDraft.getKey()); + final Map oldStateMap = + oldStates.stream().collect(toMap(State::getKey, identity())); - return ofNullable(oldState) - .map(state -> buildActionsAndUpdate(oldState, resolvedDraft)) - .orElseGet(() -> applyCallbackAndCreate(resolvedDraft)); + return stateReferenceResolver + .resolveReferences(newStateDraft) + .thenCompose( + resolvedDraft -> { + final State oldState = oldStateMap.get(newStateDraft.getKey()); + return ofNullable(oldState) + .map(state -> buildActionsAndUpdate(oldState, resolvedDraft)) + .orElseGet(() -> applyCallbackAndCreate(resolvedDraft)); }) - .exceptionally(completionException -> { - final String errorMessage = format(FAILED_TO_PROCESS, newStateDraft.getKey(), - completionException.getMessage()); - handleError(new SyncException(errorMessage, completionException), - null,newStateDraft,null , 1); - return null; + .exceptionally( + completionException -> { + final String errorMessage = + format( + FAILED_TO_PROCESS, newStateDraft.getKey(), completionException.getMessage()); + handleError( + new SyncException(errorMessage, completionException), + null, + newStateDraft, + null, + 1); + return null; }); + } + + /** + * Given a state draft, this method applies the beforeCreateCallback and then issues a create + * request to the CTP project to create the corresponding State. + * + * @param stateDraft the state draft to create the state from. + * @return a {@link CompletionStage} which contains an empty result after execution of the create. + */ + @Nonnull + private CompletionStage applyCallbackAndCreate(@Nonnull final StateDraft stateDraft) { + return syncOptions + .applyBeforeCreateCallback(stateDraft) + .map( + draft -> + stateService + .createState(draft) + .thenAccept( + stateOptional -> { + if (stateOptional.isPresent()) { + readyToResolve.add(stateDraft.getKey()); + statistics.incrementCreated(); + } else { + statistics.incrementFailed(); + } + })) + .orElse(completedFuture(null)); + } + + @Nonnull + private CompletionStage buildActionsAndUpdate( + @Nonnull final State oldState, @Nonnull final StateDraft newState) { + + final List> updateActions = buildActions(oldState, newState); + + List> updateActionsAfterCallback = + syncOptions.applyBeforeUpdateCallback(updateActions, newState, oldState); + + if (!updateActionsAfterCallback.isEmpty()) { + return updateState(oldState, newState, updateActionsAfterCallback); } - /** - * Given a state draft, this method applies the beforeCreateCallback and then issues a create request to the - * CTP project to create the corresponding State. - * - * @param stateDraft the state draft to create the state from. - * @return a {@link CompletionStage} which contains an empty result after execution of the create. - */ - @Nonnull - private CompletionStage applyCallbackAndCreate(@Nonnull final StateDraft stateDraft) { - return syncOptions - .applyBeforeCreateCallback(stateDraft) - .map(draft -> stateService - .createState(draft) - .thenAccept(stateOptional -> { - if (stateOptional.isPresent()) { - readyToResolve.add(stateDraft.getKey()); - statistics.incrementCreated(); - } else { - statistics.incrementFailed(); - } - }) - ) - .orElse(completedFuture(null)); - } - - @Nonnull - private CompletionStage buildActionsAndUpdate( - @Nonnull final State oldState, - @Nonnull final StateDraft newState) { - - final List> updateActions = buildActions(oldState, newState); - - List> updateActionsAfterCallback = - syncOptions.applyBeforeUpdateCallback(updateActions, newState, oldState); - - if (!updateActionsAfterCallback.isEmpty()) { - return updateState(oldState, newState, updateActionsAfterCallback); - } - - return completedFuture(null); - } - - /** - * Given an existing {@link State} and a new {@link StateDraft}, the method calculates all the - * update actions required to synchronize the existing state to be the same as the new one. If there are - * update actions found, a request is made to CTP to update the existing state, otherwise it doesn't issue a - * request. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP, the error handling method - * is called. - * - * @param oldState existing state that could be updated. - * @param newState draft containing data that could differ from data in {@code oldState}. - * @return a {@link CompletionStage} which contains an empty result after execution of the update. - */ - @Nonnull - private CompletionStage updateState( - @Nonnull final State oldState, - @Nonnull final StateDraft newState, - @Nonnull final List> updateActions) { - - return stateService - .updateState(oldState, updateActions) - .handle(ImmutablePair::new) - .thenCompose(updateResponse -> { - final Throwable sphereException = updateResponse.getValue(); - - if (sphereException != null) { - return executeSupplierIfConcurrentModificationException( - sphereException, - () -> fetchAndUpdate(oldState, newState), - () -> { - final String errorMessage = - format(CTP_STATE_UPDATE_FAILED, newState.getKey(), - sphereException.getMessage()); - handleError(new SyncException(errorMessage, sphereException), oldState, newState, - updateActions, 1); - return completedFuture(null); - }); - } else { - statistics.incrementUpdated(); - return completedFuture(null); - } + return completedFuture(null); + } + + /** + * Given an existing {@link State} and a new {@link StateDraft}, the method calculates all the + * update actions required to synchronize the existing state to be the same as the new one. If + * there are update actions found, a request is made to CTP to update the existing state, + * otherwise it doesn't issue a request. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP, + * the error handling method is called. + * + * @param oldState existing state that could be updated. + * @param newState draft containing data that could differ from data in {@code oldState}. + * @return a {@link CompletionStage} which contains an empty result after execution of the update. + */ + @Nonnull + private CompletionStage updateState( + @Nonnull final State oldState, + @Nonnull final StateDraft newState, + @Nonnull final List> updateActions) { + + return stateService + .updateState(oldState, updateActions) + .handle(ImmutablePair::new) + .thenCompose( + updateResponse -> { + final Throwable sphereException = updateResponse.getValue(); + + if (sphereException != null) { + return executeSupplierIfConcurrentModificationException( + sphereException, + () -> fetchAndUpdate(oldState, newState), + () -> { + final String errorMessage = + format( + CTP_STATE_UPDATE_FAILED, + newState.getKey(), + sphereException.getMessage()); + handleError( + new SyncException(errorMessage, sphereException), + oldState, + newState, + updateActions, + 1); + return completedFuture(null); + }); + } else { + statistics.incrementUpdated(); + return completedFuture(null); + } }); - } - - @Nonnull - private CompletionStage fetchAndUpdate( - @Nonnull final State oldState, - @Nonnull final StateDraft newState) { - String key = oldState.getKey(); - return stateService - .fetchState(key) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - Optional fetchedStateOptional = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(CTP_STATE_UPDATE_FAILED, key, + } + + @Nonnull + private CompletionStage fetchAndUpdate( + @Nonnull final State oldState, @Nonnull final StateDraft newState) { + String key = oldState.getKey(); + return stateService + .fetchState(key) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + Optional fetchedStateOptional = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + CTP_STATE_UPDATE_FAILED, + key, "Failed to fetch from CTP while retrying after concurrency modification."); - handleError(new SyncException(errorMessage, exception), oldState, newState, - null, 1); - return completedFuture(null); - } - - return fetchedStateOptional - .map(fetchedState -> buildActionsAndUpdate(fetchedState, newState)) - .orElseGet(() -> { - final String errorMessage = format(CTP_STATE_UPDATE_FAILED, key, - "Not found when attempting to fetch while retrying " - + "after concurrency modification."); - handleError(new SyncException(errorMessage), oldState, newState, - null, 1); + handleError( + new SyncException(errorMessage, exception), oldState, newState, null, 1); + return completedFuture(null); + } + + return fetchedStateOptional + .map(fetchedState -> buildActionsAndUpdate(fetchedState, newState)) + .orElseGet( + () -> { + final String errorMessage = + format( + CTP_STATE_UPDATE_FAILED, + key, + "Not found when attempting to fetch while retrying " + + "after concurrency modification."); + handleError(new SyncException(errorMessage), oldState, newState, null, 1); return completedFuture(null); - }); + }); }); - } + } - @Nonnull - private CompletionStage resolveNowReadyReferences(@Nonnull final Map keyToIdCache) { + @Nonnull + private CompletionStage resolveNowReadyReferences( + @Nonnull final Map keyToIdCache) { - // We delete anyways the keys from the statistics before we attempt resolution, because even if resolution fails - // the states that failed to be synced would be counted as failed. + // We delete anyways the keys from the statistics before we attempt resolution, because even if + // resolution fails + // the states that failed to be synced would be counted as failed. - final Set referencingDraftKeys = readyToResolve - .stream() + final Set referencingDraftKeys = + readyToResolve.stream() .map(statistics::removeAndGetReferencingKeys) .filter(Objects::nonNull) .flatMap(Set::stream) .collect(Collectors.toSet()); - if (referencingDraftKeys.isEmpty()) { - return CompletableFuture.completedFuture(null); - } - - - final Set readyToSync = new HashSet<>(); - final Set waitingDraftsToBeUpdated = new HashSet<>(); - - return unresolvedTransitionsService - .fetch(referencingDraftKeys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Set waitingDrafts = fetchResponse.getKey(); - final Throwable fetchException = fetchResponse.getValue(); - - if (fetchException != null) { - final String errorMessage = format(UNRESOLVED_TRANSITIONS_STORE_FETCH_FAILED, referencingDraftKeys); - handleError(new SyncException(errorMessage, fetchException), null, null, - null, referencingDraftKeys.size()); - return CompletableFuture.completedFuture(null); - } - - waitingDrafts - .forEach(waitingDraft -> { - final Set missingTransitionStateKeys = waitingDraft.getMissingTransitionStateKeys(); - missingTransitionStateKeys.removeAll(readyToResolve); - - if (missingTransitionStateKeys.isEmpty()) { - readyToSync.add(waitingDraft.getStateDraft()); - } else { - waitingDraftsToBeUpdated.add(waitingDraft); - } - }); + if (referencingDraftKeys.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + final Set readyToSync = new HashSet<>(); + final Set waitingDraftsToBeUpdated = new HashSet<>(); + + return unresolvedTransitionsService + .fetch(referencingDraftKeys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Set waitingDrafts = fetchResponse.getKey(); + final Throwable fetchException = fetchResponse.getValue(); + + if (fetchException != null) { + final String errorMessage = + format(UNRESOLVED_TRANSITIONS_STORE_FETCH_FAILED, referencingDraftKeys); + handleError( + new SyncException(errorMessage, fetchException), + null, + null, + null, + referencingDraftKeys.size()); + return CompletableFuture.completedFuture(null); + } + + waitingDrafts.forEach( + waitingDraft -> { + final Set missingTransitionStateKeys = + waitingDraft.getMissingTransitionStateKeys(); + missingTransitionStateKeys.removeAll(readyToResolve); + + if (missingTransitionStateKeys.isEmpty()) { + readyToSync.add(waitingDraft.getStateDraft()); + } else { + waitingDraftsToBeUpdated.add(waitingDraft); + } + }); - return updateWaitingDrafts(waitingDraftsToBeUpdated) - .thenCompose(aVoid -> syncBatch(readyToSync, keyToIdCache)) - .thenCompose(aVoid -> removeFromWaiting(readyToSync)); + return updateWaitingDrafts(waitingDraftsToBeUpdated) + .thenCompose(aVoid -> syncBatch(readyToSync, keyToIdCache)) + .thenCompose(aVoid -> removeFromWaiting(readyToSync)); }); - } + } - @Nonnull - private CompletableFuture updateWaitingDrafts( - @Nonnull final Set waitingDraftsToBeUpdated) { + @Nonnull + private CompletableFuture updateWaitingDrafts( + @Nonnull final Set waitingDraftsToBeUpdated) { - return allOf(waitingDraftsToBeUpdated - .stream() + return allOf( + waitingDraftsToBeUpdated.stream() .map(unresolvedTransitionsService::save) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); - } + } - @Nonnull - private CompletableFuture removeFromWaiting( - @Nonnull final Set drafts) { - return allOf(drafts - .stream() + @Nonnull + private CompletableFuture removeFromWaiting(@Nonnull final Set drafts) { + return allOf( + drafts.stream() .map(StateDraft::getKey) .map(unresolvedTransitionsService::delete) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); - } + } } diff --git a/src/main/java/com/commercetools/sync/states/StateSyncOptions.java b/src/main/java/com/commercetools/sync/states/StateSyncOptions.java index 93b20464ac..0dd30e0feb 100644 --- a/src/main/java/com/commercetools/sync/states/StateSyncOptions.java +++ b/src/main/java/com/commercetools/sync/states/StateSyncOptions.java @@ -9,33 +9,35 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class StateSyncOptions extends BaseSyncOptions { - StateSyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallBack, - @Nullable final TriConsumer, Optional> - warningCallBack, - final int batchSize, - @Nullable final TriFunction>, StateDraft, State, - List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, final long cacheSize) { - super( - ctpClient, - errorCallBack, - warningCallBack, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize); - } - + StateSyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallBack, + @Nullable + final TriConsumer, Optional> warningCallBack, + final int batchSize, + @Nullable + final TriFunction>, StateDraft, State, List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { + super( + ctpClient, + errorCallBack, + warningCallBack, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } } diff --git a/src/main/java/com/commercetools/sync/states/StateSyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/states/StateSyncOptionsBuilder.java index 6b740c8b37..0d6a8fca2c 100644 --- a/src/main/java/com/commercetools/sync/states/StateSyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/states/StateSyncOptionsBuilder.java @@ -4,58 +4,57 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; - import javax.annotation.Nonnull; -public final class StateSyncOptionsBuilder extends BaseSyncOptionsBuilder { - - public static final int BATCH_SIZE_DEFAULT = 50; - - private StateSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } - - /** - * Creates a new instance of {@link StateSyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 500). - * - * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link StateSyncOptionsBuilder} - */ - public static StateSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - - return new StateSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); - } - - /** - * Creates new instance of {@link StateSyncOptions} enriched with all attributes provided to {@code this} - * builder. - * - * @return new instance of {@link StateSyncOptions} - */ - @Override - public StateSyncOptions build() { - return new StateSyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } - - /** - * Returns an instance of this class to be used in the superclass's generic methods. Please see the JavaDoc in the - * overridden method for further details. - * - * @return an instance of this class. - */ - @Override - protected StateSyncOptionsBuilder getThis() { - return this; - } - +public final class StateSyncOptionsBuilder + extends BaseSyncOptionsBuilder { + + public static final int BATCH_SIZE_DEFAULT = 50; + + private StateSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } + + /** + * Creates a new instance of {@link StateSyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 500). + * + * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the + * target CTP project. + * @return new instance of {@link StateSyncOptionsBuilder} + */ + public static StateSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + + return new StateSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } + + /** + * Creates new instance of {@link StateSyncOptions} enriched with all attributes provided to + * {@code this} builder. + * + * @return new instance of {@link StateSyncOptions} + */ + @Override + public StateSyncOptions build() { + return new StateSyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } + + /** + * Returns an instance of this class to be used in the superclass's generic methods. Please see + * the JavaDoc in the overridden method for further details. + * + * @return an instance of this class. + */ + @Override + protected StateSyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/states/helpers/StateBatchValidator.java b/src/main/java/com/commercetools/sync/states/helpers/StateBatchValidator.java index 1e8174bfd7..1bdbde9641 100644 --- a/src/main/java/com/commercetools/sync/states/helpers/StateBatchValidator.java +++ b/src/main/java/com/commercetools/sync/states/helpers/StateBatchValidator.java @@ -1,5 +1,10 @@ package com.commercetools.sync.states.helpers; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; +import static java.lang.String.format; +import static java.util.Collections.emptySet; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.exceptions.InvalidReferenceException; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.helpers.BaseBatchValidator; @@ -8,128 +13,122 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; -import static java.lang.String.format; -import static java.util.Collections.emptySet; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class StateBatchValidator extends BaseBatchValidator { - static final String STATE_DRAFT_KEY_NOT_SET = "StateDraft with name: %s doesn't have a key. " - + "Please make sure all state drafts have keys."; - static final String STATE_DRAFT_IS_NULL = "StateDraft is null."; - static final String STATE_HAS_INVALID_REFERENCES = "StateDraft with key: '%s' has invalid state transitions"; - - public StateBatchValidator(@Nonnull final StateSyncOptions syncOptions, - @Nonnull final StateSyncStatistics syncStatistics) { - super(syncOptions, syncStatistics); - } - - - /** - * Given the {@link List}<{@link StateDraft}> of drafts this method attempts to validate - * drafts and collect referenced keys from the draft - * and return an {@link ImmutablePair}<{@link Set}<{@link StateDraft}> - * ,{@link Set}<{@link String}>> - * which contains the {@link Set} of valid drafts and referenced state transition keys. - * - *

A valid state draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a key which is not blank (null/empty)
  4. - *
  5. It has no invalid state reference on an transition - * A valid reference is simply one which has its id field's value not blank (null/empty)
  6. - *
- * - * @param stateDrafts the state drafts to validate and collect referenced state transition keys. - * @return {@link ImmutablePair}<{@link Set}<{@link ProductTypeDraft}>, - * {@link Set}<{@link String}>> which contains the {@link Set} of valid drafts and - * referenced product type keys. - */ - @Override - public ImmutablePair, Set> validateAndCollectReferencedKeys( - @Nonnull final List stateDrafts) { - - final Set stateTransitionKeys = new HashSet<>(); - - final Set validDrafts = stateDrafts - .stream() + static final String STATE_DRAFT_KEY_NOT_SET = + "StateDraft with name: %s doesn't have a key. " + + "Please make sure all state drafts have keys."; + static final String STATE_DRAFT_IS_NULL = "StateDraft is null."; + static final String STATE_HAS_INVALID_REFERENCES = + "StateDraft with key: '%s' has invalid state transitions"; + + public StateBatchValidator( + @Nonnull final StateSyncOptions syncOptions, + @Nonnull final StateSyncStatistics syncStatistics) { + super(syncOptions, syncStatistics); + } + + /** + * Given the {@link List}<{@link StateDraft}> of drafts this method attempts to validate + * drafts and collect referenced keys from the draft and return an {@link ImmutablePair}<{@link + * Set}<{@link StateDraft}> ,{@link Set}<{@link String}>> which contains the {@link + * Set} of valid drafts and referenced state transition keys. + * + *

A valid state draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a key which is not blank (null/empty) + *
  3. It has no invalid state reference on an transition A valid reference is simply one which + * has its id field's value not blank (null/empty) + *
+ * + * @param stateDrafts the state drafts to validate and collect referenced state transition keys. + * @return {@link ImmutablePair}<{@link Set}<{@link ProductTypeDraft}>, {@link + * Set}<{@link String}>> which contains the {@link Set} of valid drafts and + * referenced product type keys. + */ + @Override + public ImmutablePair, Set> validateAndCollectReferencedKeys( + @Nonnull final List stateDrafts) { + + final Set stateTransitionKeys = new HashSet<>(); + + final Set validDrafts = + stateDrafts.stream() .filter(stateDraft -> isValidStateDraft(stateDraft, stateTransitionKeys)) .collect(Collectors.toSet()); - return ImmutablePair.of(validDrafts, stateTransitionKeys); + return ImmutablePair.of(validDrafts, stateTransitionKeys); + } + + private boolean isValidStateDraft( + @Nullable final StateDraft stateDraft, @Nonnull final Set stateTransitionKeys) { + + if (stateDraft == null) { + handleError(STATE_DRAFT_IS_NULL); + } else if (isBlank(stateDraft.getKey())) { + handleError(format(STATE_DRAFT_KEY_NOT_SET, stateDraft.getName())); + } else { + try { + final Set referencesStateKeys = getTransitionKeys(stateDraft); + stateTransitionKeys.addAll(referencesStateKeys); + return true; + } catch (SyncException exception) { + handleError(exception); + } } - private boolean isValidStateDraft( - @Nullable final StateDraft stateDraft, - @Nonnull final Set stateTransitionKeys) { - - if (stateDraft == null) { - handleError(STATE_DRAFT_IS_NULL); - } else if (isBlank(stateDraft.getKey())) { - handleError(format(STATE_DRAFT_KEY_NOT_SET, stateDraft.getName())); - } else { - try { - final Set referencesStateKeys = getTransitionKeys(stateDraft); - stateTransitionKeys.addAll(referencesStateKeys); - return true; - } catch (SyncException exception) { - handleError(exception); - } - } + return false; + } - return false; - } + @Nonnull + private static Set getTransitionKeys(@Nonnull final StateDraft stateDraft) + throws SyncException { - @Nonnull - private static Set getTransitionKeys(@Nonnull final StateDraft stateDraft) - throws SyncException { - - final Set> transitions = stateDraft.getTransitions(); - if (transitions == null || transitions.isEmpty()) { - return emptySet(); - } + final Set> transitions = stateDraft.getTransitions(); + if (transitions == null || transitions.isEmpty()) { + return emptySet(); + } - final Set referencedStateKeys = new HashSet<>(); - final List> invalidStates = new ArrayList<>(); - for (Reference transition : transitions) { - if (transition != null) { - try { - referencedStateKeys.add(getStateKey(transition)); - } catch (InvalidReferenceException invalidReferenceException) { - invalidStates.add(transition); - } - } + final Set referencedStateKeys = new HashSet<>(); + final List> invalidStates = new ArrayList<>(); + for (Reference transition : transitions) { + if (transition != null) { + try { + referencedStateKeys.add(getStateKey(transition)); + } catch (InvalidReferenceException invalidReferenceException) { + invalidStates.add(transition); } + } + } - if (!invalidStates.isEmpty()) { - final String errorMessage = format(STATE_HAS_INVALID_REFERENCES, stateDraft.getKey()); - throw new SyncException(errorMessage, - new InvalidReferenceException(BLANK_ID_VALUE_ON_REFERENCE)); - } - return referencedStateKeys; + if (!invalidStates.isEmpty()) { + final String errorMessage = format(STATE_HAS_INVALID_REFERENCES, stateDraft.getKey()); + throw new SyncException( + errorMessage, new InvalidReferenceException(BLANK_ID_VALUE_ON_REFERENCE)); } + return referencedStateKeys; + } - @Nonnull - private static String getStateKey(@Nonnull final Reference stateReference) - throws InvalidReferenceException { + @Nonnull + private static String getStateKey(@Nonnull final Reference stateReference) + throws InvalidReferenceException { - final String key = stateReference.getId(); - if (isBlank(key)) { - throw new InvalidReferenceException(BLANK_ID_VALUE_ON_REFERENCE); - } - return key; + final String key = stateReference.getId(); + if (isBlank(key)) { + throw new InvalidReferenceException(BLANK_ID_VALUE_ON_REFERENCE); } - + return key; + } } diff --git a/src/main/java/com/commercetools/sync/states/helpers/StateReferenceResolver.java b/src/main/java/com/commercetools/sync/states/helpers/StateReferenceResolver.java index bf8f0d5cd6..8fb4fe1f17 100644 --- a/src/main/java/com/commercetools/sync/states/helpers/StateReferenceResolver.java +++ b/src/main/java/com/commercetools/sync/states/helpers/StateReferenceResolver.java @@ -1,5 +1,8 @@ package com.commercetools.sync.states.helpers; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.stream.Collectors.toSet; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.BaseReferenceResolver; @@ -9,112 +12,111 @@ import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; import io.sphere.sdk.states.StateDraftBuilder; - -import javax.annotation.Nonnull; import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.stream.Collectors.toSet; - -public final class StateReferenceResolver extends BaseReferenceResolver { - private final StateService stateService; - private static final String FAILED_TO_RESOLVE_REFERENCE = "Failed to resolve 'transition' reference on " - + "StateDraft with key:'%s'. Reason: %s"; +public final class StateReferenceResolver + extends BaseReferenceResolver { + private final StateService stateService; + private static final String FAILED_TO_RESOLVE_REFERENCE = + "Failed to resolve 'transition' reference on StateDraft with key:'%s'. Reason: %s"; - /** - * Takes a {@link StateSyncOptions} instance, a {@link StateService} to instantiate a - * {@link StateReferenceResolver} instance that could be used to resolve the category drafts in the - * CTP project specified in the injected {@link StateSyncOptions} instance. - * - * @param options the container of all the options of the sync process including the CTP project client - * and/or configuration and other sync-specific options. - * @param stateService the service to fetch the states for reference resolution. - */ - public StateReferenceResolver(@Nonnull final StateSyncOptions options, - @Nonnull final StateService stateService) { - super(options); - this.stateService = stateService; - } + /** + * Takes a {@link StateSyncOptions} instance, a {@link StateService} to instantiate a {@link + * StateReferenceResolver} instance that could be used to resolve the category drafts in the CTP + * project specified in the injected {@link StateSyncOptions} instance. + * + * @param options the container of all the options of the sync process including the CTP project + * client and/or configuration and other sync-specific options. + * @param stateService the service to fetch the states for reference resolution. + */ + public StateReferenceResolver( + @Nonnull final StateSyncOptions options, @Nonnull final StateService stateService) { + super(options); + this.stateService = stateService; + } - /** - * Given a {@link StateDraft} this method attempts to resolve the transition state references to - * return a {@link CompletionStage} which contains a new instance of the draft with the resolved - * references. The keys of the references are taken from the id field of the references. - * - * @param stateDraft the stateDraft to resolve its references. - * @return a {@link CompletionStage} that contains as a result a new Statedraft instance with resolved - * category references or, in case an error occurs during reference resolution, - * a {@link ReferenceResolutionException}. - */ - @Override - @Nonnull - public CompletionStage resolveReferences(@Nonnull final StateDraft stateDraft) { - return resolveTransitionReferences(StateDraftBuilder.of(stateDraft)) - .thenApply(StateDraftBuilder::build); - } + /** + * Given a {@link StateDraft} this method attempts to resolve the transition state references to + * return a {@link CompletionStage} which contains a new instance of the draft with the resolved + * references. The keys of the references are taken from the id field of the references. + * + * @param stateDraft the stateDraft to resolve its references. + * @return a {@link CompletionStage} that contains as a result a new Statedraft instance with + * resolved category references or, in case an error occurs during reference resolution, a + * {@link ReferenceResolutionException}. + */ + @Override + @Nonnull + public CompletionStage resolveReferences(@Nonnull final StateDraft stateDraft) { + return resolveTransitionReferences(StateDraftBuilder.of(stateDraft)) + .thenApply(StateDraftBuilder::build); + } - /** - * Given a {@link StateDraftBuilder} this method attempts to resolve the transitions to - * return a {@link CompletionStage} which contains a new instance of the builder with the resolved references. - * The key of the state references is taken from the value of the id fields. - * - * @param draftBuilder the state to resolve its transition references. - * @return a {@link CompletionStage} that contains as a result a new builder instance with resolved references or, - * in case an error occurs during reference resolution, a {@link ReferenceResolutionException}. - */ - @Nonnull - private CompletionStage resolveTransitionReferences( - @Nonnull final StateDraftBuilder draftBuilder) { + /** + * Given a {@link StateDraftBuilder} this method attempts to resolve the transitions to return a + * {@link CompletionStage} which contains a new instance of the builder with the resolved + * references. The key of the state references is taken from the value of the id fields. + * + * @param draftBuilder the state to resolve its transition references. + * @return a {@link CompletionStage} that contains as a result a new builder instance with + * resolved references or, in case an error occurs during reference resolution, a {@link + * ReferenceResolutionException}. + */ + @Nonnull + private CompletionStage resolveTransitionReferences( + @Nonnull final StateDraftBuilder draftBuilder) { - final Set> stateReferences = draftBuilder.getTransitions(); - final Set stateKeys = new HashSet<>(); - if (stateReferences != null) { - for (Reference stateReference : stateReferences) { - if (stateReference != null) { - try { - final String stateKey = getIdFromReference(stateReference); - stateKeys.add(stateKey); - } catch (ReferenceResolutionException referenceResolutionException) { - return exceptionallyCompletedFuture( - new ReferenceResolutionException( - format(FAILED_TO_RESOLVE_REFERENCE, - draftBuilder.getKey(), referenceResolutionException.getMessage()))); - } - } - } + final Set> stateReferences = draftBuilder.getTransitions(); + final Set stateKeys = new HashSet<>(); + if (stateReferences != null) { + for (Reference stateReference : stateReferences) { + if (stateReference != null) { + try { + final String stateKey = getIdFromReference(stateReference); + stateKeys.add(stateKey); + } catch (ReferenceResolutionException referenceResolutionException) { + return exceptionallyCompletedFuture( + new ReferenceResolutionException( + format( + FAILED_TO_RESOLVE_REFERENCE, + draftBuilder.getKey(), + referenceResolutionException.getMessage()))); + } } - return fetchAndResolveStateTransitions(draftBuilder, stateKeys); + } } + return fetchAndResolveStateTransitions(draftBuilder, stateKeys); + } - /** - * Given a {@link StateDraftBuilder} and a {@link Set} of {@code stateKeys} this method fetches the states - * corresponding to these keys. Then it sets the state references on the {@code draftBuilder}. - * - * @param draftBuilder the state draft builder to resolve it's state references. - * @param stateKeys the state keys of to resolve their actual id on the draft. - * @return a {@link CompletionStage} that contains as a result a new stateDraft instance with resolved state - * references or an exception. - */ - @Nonnull - private CompletionStage fetchAndResolveStateTransitions( - @Nonnull final StateDraftBuilder draftBuilder, - @Nonnull final Set stateKeys) { - - return stateService.fetchMatchingStatesByKeysWithTransitions(stateKeys) - .thenApply(states -> states.stream() - .map(State::toReference) - .filter(Objects::nonNull) - .collect(toSet())) - .thenApply(references -> { - if (!references.isEmpty()) { - draftBuilder.transitions(references); - } - return draftBuilder; - }); - } + /** + * Given a {@link StateDraftBuilder} and a {@link Set} of {@code stateKeys} this method fetches + * the states corresponding to these keys. Then it sets the state references on the {@code + * draftBuilder}. + * + * @param draftBuilder the state draft builder to resolve it's state references. + * @param stateKeys the state keys of to resolve their actual id on the draft. + * @return a {@link CompletionStage} that contains as a result a new stateDraft instance with + * resolved state references or an exception. + */ + @Nonnull + private CompletionStage fetchAndResolveStateTransitions( + @Nonnull final StateDraftBuilder draftBuilder, @Nonnull final Set stateKeys) { + return stateService + .fetchMatchingStatesByKeysWithTransitions(stateKeys) + .thenApply( + states -> + states.stream().map(State::toReference).filter(Objects::nonNull).collect(toSet())) + .thenApply( + references -> { + if (!references.isEmpty()) { + draftBuilder.transitions(references); + } + return draftBuilder; + }); + } } diff --git a/src/main/java/com/commercetools/sync/states/helpers/StateSyncStatistics.java b/src/main/java/com/commercetools/sync/states/helpers/StateSyncStatistics.java index 64df1ed3af..ee3c32927d 100644 --- a/src/main/java/com/commercetools/sync/states/helpers/StateSyncStatistics.java +++ b/src/main/java/com/commercetools/sync/states/helpers/StateSyncStatistics.java @@ -1,80 +1,87 @@ package com.commercetools.sync.states.helpers; -import com.commercetools.sync.commons.helpers.BaseSyncStatistics; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.lang.String.format; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import com.commercetools.sync.commons.helpers.BaseSyncStatistics; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; - -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.lang.String.format; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class StateSyncStatistics extends BaseSyncStatistics { - /** - * The following {@link Map} ({@code stateKeysWithMissingParents}) represents products with - * missing states (other referenced states). - * - *
    - *
  • key: key of the missing parent state
  • - *
  • value: a set of the parent's children state keys
  • - *
- * - *

The map is thread-safe (by instantiating it with {@link ConcurrentHashMap}). - * - */ - private ConcurrentHashMap> stateKeysWithMissingParents = new ConcurrentHashMap<>(); + /** + * The following {@link Map} ({@code stateKeysWithMissingParents}) represents products with + * missing states (other referenced states). + * + *

    + *
  • key: key of the missing parent state + *
  • value: a set of the parent's children state keys + *
+ * + *

The map is thread-safe (by instantiating it with {@link ConcurrentHashMap}). + */ + private ConcurrentHashMap> stateKeysWithMissingParents = + new ConcurrentHashMap<>(); - /** - * Builds a summary of the state sync statistics instance that looks like the following example: - * - *

"Summary: 4 state(s) were processed in total (1 created, 1 updated, 1 failed to sync - * and 1 product(s) with a missing transition(s))." - * - * @return a summary message of the states sync statistics instance. - */ - @Override - public String getReportMessage() { - return format("Summary: %s state(s) were processed in total " - + "(%s created, %s updated, %s failed to sync and %s state(s) with missing transition(s)).", - getProcessed(), getCreated(), getUpdated(), getFailed(), getNumberOfStatesWithMissingParents()); - } + /** + * Builds a summary of the state sync statistics instance that looks like the following example: + * + *

"Summary: 4 state(s) were processed in total (1 created, 1 updated, 1 failed to sync and 1 + * product(s) with a missing transition(s))." + * + * @return a summary message of the states sync statistics instance. + */ + @Override + public String getReportMessage() { + return format( + "Summary: %s state(s) were processed in total " + + "(%s created, %s updated, %s failed to sync and %s state(s) with missing transition(s)).", + getProcessed(), + getCreated(), + getUpdated(), + getFailed(), + getNumberOfStatesWithMissingParents()); + } - /** - * Returns the total number of states with missing parents. - * - * @return the total number of states with missing parents. - */ - public int getNumberOfStatesWithMissingParents() { - return (int) stateKeysWithMissingParents - .values() - .stream() + /** + * Returns the total number of states with missing parents. + * + * @return the total number of states with missing parents. + */ + public int getNumberOfStatesWithMissingParents() { + return (int) + stateKeysWithMissingParents.values().stream() .flatMap(Collection::stream) .distinct() .count(); - } + } - /** - * This method checks if there is an entry with the key of the {@code missingParentStateKey} in the - * {@code stateKeysWithMissingParents}, if there isn't it creates a new entry with this parent key and as a value - * a new set containing the {@code childKey}. Otherwise, if there is already, it just adds the - * {@code childKey} to the existing set. - * - * @param parentKey the key of the missing parent. - * @param childKey the key of the state with a missing parent. - */ - public void addMissingDependency(@Nonnull final String parentKey, @Nonnull final String childKey) { - stateKeysWithMissingParents.merge(parentKey, asSet(childKey), (existingSet, newChildAsSet) -> { - existingSet.addAll(newChildAsSet); - return existingSet; + /** + * This method checks if there is an entry with the key of the {@code missingParentStateKey} in + * the {@code stateKeysWithMissingParents}, if there isn't it creates a new entry with this parent + * key and as a value a new set containing the {@code childKey}. Otherwise, if there is already, + * it just adds the {@code childKey} to the existing set. + * + * @param parentKey the key of the missing parent. + * @param childKey the key of the state with a missing parent. + */ + public void addMissingDependency( + @Nonnull final String parentKey, @Nonnull final String childKey) { + stateKeysWithMissingParents.merge( + parentKey, + asSet(childKey), + (existingSet, newChildAsSet) -> { + existingSet.addAll(newChildAsSet); + return existingSet; }); - } + } - @Nullable - public Set removeAndGetReferencingKeys(@Nonnull final String key) { - return stateKeysWithMissingParents.remove(key); - } + @Nullable + public Set removeAndGetReferencingKeys(@Nonnull final String key) { + return stateKeysWithMissingParents.remove(key); + } } diff --git a/src/main/java/com/commercetools/sync/states/utils/StateReferenceResolutionUtils.java b/src/main/java/com/commercetools/sync/states/utils/StateReferenceResolutionUtils.java index 8bb2688ac1..5138b906ed 100644 --- a/src/main/java/com/commercetools/sync/states/utils/StateReferenceResolutionUtils.java +++ b/src/main/java/com/commercetools/sync/states/utils/StateReferenceResolutionUtils.java @@ -1,107 +1,107 @@ package com.commercetools.sync.states.utils; +import static com.commercetools.sync.commons.utils.SyncUtils.getReferenceWithKeyReplaced; + import io.sphere.sdk.models.Reference; import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; import io.sphere.sdk.states.StateDraftBuilder; import io.sphere.sdk.states.expansion.StateExpansionModel; import io.sphere.sdk.states.queries.StateQuery; - -import javax.annotation.Nonnull; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.SyncUtils.getReferenceWithKeyReplaced; +import javax.annotation.Nonnull; /** - * Util class which provides utilities that can be used when syncing resources from a source commercetools project - * to a target one. + * Util class which provides utilities that can be used when syncing resources from a source + * commercetools project to a target one. */ public final class StateReferenceResolutionUtils { - /** - * Returns an {@link List}<{@link StateDraft}> consisting of the results of applying the - * mapping from {@link State} to {@link StateDraft} with considering reference resolution. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Reference fieldfromto
transitions{@link Set}<{@link Reference}<{@link State}>>{@link Set}<{@link Reference}<{@link State}>> (with key replaced with id field)
- * - *

Note: The transition references should be expanded with a key. - * Any reference that is not expanded will have its id in place and not replaced by the key will be - * considered as existing resources on the target commercetools project and - * the library will issues an update/create API request without reference resolution. - * - * @param states the states with expanded references. - * @return a {@link List} of {@link StateDraft} built from the - * supplied {@link List} of {@link State}. - */ - @Nonnull - public static List mapToStateDrafts(@Nonnull final List states) { - return states - .stream() - .filter(Objects::nonNull) - .map(state -> { - final Set> newTransitions = replaceTransitionIdsWithKeys(state); - return StateDraftBuilder - .of(state.getKey(), state.getType()) - .name(state.getName()) - .description(state.getDescription()) - .initial(state.isInitial()) - .roles(state.getRoles()) - .transitions(newTransitions) - .build(); + /** + * Returns an {@link List}<{@link StateDraft}> consisting of the results of applying the + * mapping from {@link State} to {@link StateDraft} with considering reference resolution. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Reference fieldfromto
transitions{@link Set}<{@link Reference}<{@link State}>>{@link Set}<{@link Reference}<{@link State}>> (with key replaced with id field)
+ * + *

Note: The transition references should be expanded with a key. Any reference that is + * not expanded will have its id in place and not replaced by the key will be considered as + * existing resources on the target commercetools project and the library will issues an + * update/create API request without reference resolution. + * + * @param states the states with expanded references. + * @return a {@link List} of {@link StateDraft} built from the supplied {@link List} of {@link + * State}. + */ + @Nonnull + public static List mapToStateDrafts(@Nonnull final List states) { + return states.stream() + .filter(Objects::nonNull) + .map( + state -> { + final Set> newTransitions = replaceTransitionIdsWithKeys(state); + return StateDraftBuilder.of(state.getKey(), state.getType()) + .name(state.getName()) + .description(state.getDescription()) + .initial(state.isInitial()) + .roles(state.getRoles()) + .transitions(newTransitions) + .build(); }) - .collect(Collectors.toList()); - } + .collect(Collectors.toList()); + } - private static Set> replaceTransitionIdsWithKeys(@Nonnull final State state) { - final Set> transitions = state.getTransitions(); - final Set> newTransitions = new HashSet<>(); - if (transitions != null && !transitions.isEmpty()) { - transitions.forEach(transition -> { - newTransitions.add(getReferenceWithKeyReplaced(transition, - () -> State.referenceOfId(transition.getObj().getKey()))); - }); - } - return newTransitions; + private static Set> replaceTransitionIdsWithKeys(@Nonnull final State state) { + final Set> transitions = state.getTransitions(); + final Set> newTransitions = new HashSet<>(); + if (transitions != null && !transitions.isEmpty()) { + transitions.forEach( + transition -> { + newTransitions.add( + getReferenceWithKeyReplaced( + transition, () -> State.referenceOfId(transition.getObj().getKey()))); + }); } + return newTransitions; + } - /** - * Builds a {@link StateQuery} for fetching states from a source CTP project with all the needed - * references expanded for the sync: - *

    - *
  • Transition States
  • - *
- * - *

Note: Please only use this util if you desire to sync all the aforementioned references from - * a source commercetools project. Otherwise, it is more efficient to build the query without expansions, if they - * are not needed, to avoid unnecessarily bigger payloads fetched from the source project. - * - * @return the query for fetching states from the source CTP project with all the aforementioned references - * expanded. - */ - public static StateQuery buildStateQuery() { - return StateQuery.of() - .withExpansionPaths(StateExpansionModel::transitions); - } + /** + * Builds a {@link StateQuery} for fetching states from a source CTP project with all the needed + * references expanded for the sync: + * + *

    + *
  • Transition States + *
+ * + *

Note: Please only use this util if you desire to sync all the aforementioned references from + * a source commercetools project. Otherwise, it is more efficient to build the query without + * expansions, if they are not needed, to avoid unnecessarily bigger payloads fetched from the + * source project. + * + * @return the query for fetching states from the source CTP project with all the aforementioned + * references expanded. + */ + public static StateQuery buildStateQuery() { + return StateQuery.of().withExpansionPaths(StateExpansionModel::transitions); + } - private StateReferenceResolutionUtils() { - } + private StateReferenceResolutionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/states/utils/StateSyncUtils.java b/src/main/java/com/commercetools/sync/states/utils/StateSyncUtils.java index df1571cc10..93b9b0e2de 100644 --- a/src/main/java/com/commercetools/sync/states/utils/StateSyncUtils.java +++ b/src/main/java/com/commercetools/sync/states/utils/StateSyncUtils.java @@ -1,53 +1,48 @@ package com.commercetools.sync.states.utils; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.states.State; -import io.sphere.sdk.states.StateDraft; - -import javax.annotation.Nonnull; -import java.util.List; - +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildChangeInitialAction; import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildChangeTypeAction; import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildRolesUpdateActions; import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildSetDescriptionAction; import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildSetNameAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildSetTransitionsAction; -public final class StateSyncUtils { +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.states.State; +import io.sphere.sdk.states.StateDraft; +import java.util.List; +import javax.annotation.Nonnull; - private StateSyncUtils() { - } - - /** - * Compares all the fields (including the roles see - * {@link StateUpdateActionUtils#buildRolesUpdateActions}) of a {@link State} and a - * {@link StateDraft}. It returns a {@link List} of {@link UpdateAction}<{@link State}> as a - * result. If no update action is needed, for example in case where both the {@link State} and the - * {@link StateDraft} have the same fields, an empty {@link List} is returned. - * - * @param oldState the {@link State} which should be updated. - * @param newState the {@link StateDraft} where we get the new data. - * @return A list of state-specific update actions. - */ - @Nonnull - public static List> buildActions( - @Nonnull final State oldState, - @Nonnull final StateDraft newState) { - - final List> updateActions = - filterEmptyOptionals( - buildChangeTypeAction(oldState, newState), - buildSetNameAction(oldState, newState), - buildSetDescriptionAction(oldState, newState), - buildChangeInitialAction(oldState, newState) - ); - - updateActions.addAll(buildRolesUpdateActions(oldState, newState)); - buildSetTransitionsAction(oldState, newState).ifPresent(updateActions::add); - - return updateActions; - } +public final class StateSyncUtils { + private StateSyncUtils() {} + + /** + * Compares all the fields (including the roles see {@link + * StateUpdateActionUtils#buildRolesUpdateActions}) of a {@link State} and a {@link StateDraft}. + * It returns a {@link List} of {@link UpdateAction}<{@link State}> as a result. If no + * update action is needed, for example in case where both the {@link State} and the {@link + * StateDraft} have the same fields, an empty {@link List} is returned. + * + * @param oldState the {@link State} which should be updated. + * @param newState the {@link StateDraft} where we get the new data. + * @return A list of state-specific update actions. + */ + @Nonnull + public static List> buildActions( + @Nonnull final State oldState, @Nonnull final StateDraft newState) { + + final List> updateActions = + filterEmptyOptionals( + buildChangeTypeAction(oldState, newState), + buildSetNameAction(oldState, newState), + buildSetDescriptionAction(oldState, newState), + buildChangeInitialAction(oldState, newState)); + + updateActions.addAll(buildRolesUpdateActions(oldState, newState)); + buildSetTransitionsAction(oldState, newState).ifPresent(updateActions::add); + + return updateActions; + } } diff --git a/src/main/java/com/commercetools/sync/states/utils/StateUpdateActionUtils.java b/src/main/java/com/commercetools/sync/states/utils/StateUpdateActionUtils.java index 66721608d9..4bc6aff79a 100644 --- a/src/main/java/com/commercetools/sync/states/utils/StateUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/states/utils/StateUpdateActionUtils.java @@ -1,5 +1,10 @@ package com.commercetools.sync.states.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static org.apache.commons.lang3.BooleanUtils.toBoolean; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.Reference; import io.sphere.sdk.states.State; @@ -12,210 +17,197 @@ import io.sphere.sdk.states.commands.updateactions.SetDescription; import io.sphere.sdk.states.commands.updateactions.SetName; import io.sphere.sdk.states.commands.updateactions.SetTransitions; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.HashSet; import java.util.List; - import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static org.apache.commons.lang3.BooleanUtils.toBoolean; +import javax.annotation.Nonnull; public final class StateUpdateActionUtils { - private StateUpdateActionUtils() { + private StateUpdateActionUtils() {} + + /** + * Compares the {@code type} values of a {@link State} and a {@link StateDraft} and returns an + * {@link Optional} of update action, which would contain the {@code "changeType"} {@link + * UpdateAction}. If both {@link State} and {@link StateDraft} have the same {@code type} values, + * then no update action is needed and empty optional will be returned. + * + * @param oldState the state that should be updated. + * @param newState the state draft which contains the new type. + * @return optional containing update action or empty optional if types are identical. + */ + @Nonnull + public static Optional> buildChangeTypeAction( + @Nonnull final State oldState, @Nonnull final StateDraft newState) { + + return buildUpdateAction( + oldState.getType(), newState.getType(), () -> ChangeType.of(newState.getType())); + } + + /** + * Compares the {@code name} values of a {@link State} and a {@link StateDraft} and returns an + * {@link Optional} of update action, which would contain the {@code "setName"} {@link + * UpdateAction}. If both {@link State} and {@link StateDraft} have the same {@code name} values, + * then no update action is needed and empty optional will be returned. + * + * @param oldState the state that should be updated. + * @param newState the state draft which contains the new name. + * @return optional containing update action or empty optional if names are identical. + */ + @Nonnull + public static Optional> buildSetNameAction( + @Nonnull final State oldState, @Nonnull final StateDraft newState) { + + return buildUpdateAction( + oldState.getName(), newState.getName(), () -> SetName.of(newState.getName())); + } + + /** + * Compares the {@code description} values of a {@link State} and a {@link StateDraft} and returns + * an {@link Optional} of update action, which would contain the {@code "setDescription"} {@link + * UpdateAction}. If both {@link State} and {@link StateDraft} have the same {@code description} + * values, then no update action is needed and empty optional will be returned. + * + * @param oldState the state that should be updated. + * @param newState the state draft which contains the new description. + * @return optional containing update action or empty optional if descriptions are identical. + */ + @Nonnull + public static Optional> buildSetDescriptionAction( + @Nonnull final State oldState, @Nonnull final StateDraft newState) { + + return buildUpdateAction( + oldState.getDescription(), + newState.getDescription(), + () -> SetDescription.of(newState.getDescription())); + } + + /** + * Compares the {@code initial} values of a {@link State} and a {@link StateDraft} and returns an + * {@link Optional} of update action, which would contain the {@code "changeInitial"} {@link + * UpdateAction}. If both {@link State} and {@link StateDraft} have the same {@code initial} + * values, then no update action is needed and empty optional will be returned. + * + * @param oldState the state that should be updated. + * @param newState the state draft which contains the new initial. + * @return optional containing update action or empty optional if initial are identical. + */ + @Nonnull + public static Optional> buildChangeInitialAction( + @Nonnull final State oldState, @Nonnull final StateDraft newState) { + + final boolean isNewStateInitial = toBoolean(newState.isInitial()); + final boolean isOldStateInitial = toBoolean(oldState.isInitial()); + + return buildUpdateAction( + isOldStateInitial, isNewStateInitial, () -> ChangeInitial.of(isNewStateInitial)); + } + + /** + * Compares the roles of a {@link State} and a {@link StateDraft} and returns a list of {@link + * UpdateAction}<{@link State}> as a result. If both the {@link State} and the {@link + * StateDraft} have identical roles, then no update action is needed and hence an empty {@link + * List} is returned. + * + * @param oldState the state which should be updated. + * @param newState the state draft where we get the key. + * @return A list with the update actions or an empty list if the roles are identical. + */ + @Nonnull + public static List> buildRolesUpdateActions( + @Nonnull final State oldState, @Nonnull final StateDraft newState) { + + boolean emptyNew = newState.getRoles() == null || newState.getRoles().isEmpty(); + boolean emptyOld = oldState.getRoles() == null || oldState.getRoles().isEmpty(); + + if (emptyNew && emptyOld) { + return emptyList(); } - /** - * Compares the {@code type} values of a {@link State} and a {@link StateDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeType"} - * {@link UpdateAction}. If both {@link State} and {@link StateDraft} have the same - * {@code type} values, then no update action is needed and empty optional will be returned. - * - * @param oldState the state that should be updated. - * @param newState the state draft which contains the new type. - * @return optional containing update action or empty optional if types are identical. - */ - @Nonnull - public static Optional> buildChangeTypeAction( - @Nonnull final State oldState, - @Nonnull final StateDraft newState) { - - return buildUpdateAction(oldState.getType(), newState.getType(), - () -> ChangeType.of(newState.getType())); - } + Set newRoles = emptyNew ? new HashSet<>() : newState.getRoles(); + Set oldRoles = emptyOld ? new HashSet<>() : oldState.getRoles(); - /** - * Compares the {@code name} values of a {@link State} and a {@link StateDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setName"} - * {@link UpdateAction}. If both {@link State} and {@link StateDraft} have the same - * {@code name} values, then no update action is needed and empty optional will be returned. - * - * @param oldState the state that should be updated. - * @param newState the state draft which contains the new name. - * @return optional containing update action or empty optional if names are identical. - */ - @Nonnull - public static Optional> buildSetNameAction( - @Nonnull final State oldState, - @Nonnull final StateDraft newState) { - - return buildUpdateAction(oldState.getName(), newState.getName(), - () -> SetName.of(newState.getName())); - } + List> actions = new ArrayList<>(); - /** - * Compares the {@code description} values of a {@link State} and a {@link StateDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setDescription"} - * {@link UpdateAction}. If both {@link State} and {@link StateDraft} have the same - * {@code description} values, then no update action is needed and empty optional will be returned. - * - * @param oldState the state that should be updated. - * @param newState the state draft which contains the new description. - * @return optional containing update action or empty optional if descriptions are identical. - */ - @Nonnull - public static Optional> buildSetDescriptionAction( - @Nonnull final State oldState, - @Nonnull final StateDraft newState) { - - return buildUpdateAction(oldState.getDescription(), newState.getDescription(), - () -> SetDescription.of(newState.getDescription())); - } + Set remove = diffRoles(oldRoles, newRoles); - /** - * Compares the {@code initial} values of a {@link State} and a {@link StateDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeInitial"} - * {@link UpdateAction}. If both {@link State} and {@link StateDraft} have the same - * {@code initial} values, then no update action is needed and empty optional will be returned. - * - * @param oldState the state that should be updated. - * @param newState the state draft which contains the new initial. - * @return optional containing update action or empty optional if initial are identical. - */ - @Nonnull - public static Optional> buildChangeInitialAction( - @Nonnull final State oldState, - @Nonnull final StateDraft newState) { - - final boolean isNewStateInitial = toBoolean(newState.isInitial()); - final boolean isOldStateInitial = toBoolean(oldState.isInitial()); - - return buildUpdateAction(isOldStateInitial, isNewStateInitial, - () -> ChangeInitial.of(isNewStateInitial)); + if (!remove.isEmpty()) { + actions.add(RemoveRoles.of(remove)); } - /** - * Compares the roles of a {@link State} and a {@link StateDraft} and returns a list of - * {@link UpdateAction}<{@link State}> as a result. If both the {@link State} and - * the {@link StateDraft} have identical roles, then no update action is needed and hence an empty - * {@link List} is returned. - * - * @param oldState the state which should be updated. - * @param newState the state draft where we get the key. - * @return A list with the update actions or an empty list if the roles are identical. - */ - @Nonnull - public static List> buildRolesUpdateActions( - @Nonnull final State oldState, - @Nonnull final StateDraft newState) { - - boolean emptyNew = newState.getRoles() == null || newState.getRoles().isEmpty(); - boolean emptyOld = oldState.getRoles() == null || oldState.getRoles().isEmpty(); - - if (emptyNew && emptyOld) { - return emptyList(); - } - - Set newRoles = emptyNew ? new HashSet<>() : newState.getRoles(); - Set oldRoles = emptyOld ? new HashSet<>() : oldState.getRoles(); - - List> actions = new ArrayList<>(); + Set add = diffRoles(newRoles, oldRoles); - Set remove = diffRoles(oldRoles, newRoles); - - if (!remove.isEmpty()) { - actions.add(RemoveRoles.of(remove)); - } - - Set add = diffRoles(newRoles, oldRoles); - - if (!add.isEmpty()) { - actions.add(AddRoles.of(add)); - } - - return actions; + if (!add.isEmpty()) { + actions.add(AddRoles.of(add)); } - /** - * Compares the {@code transitions} values of a {@link State} and a {@link StateDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setTransitions"} - * {@link UpdateAction}. If both {@link State} and {@link StateDraft} have the same - * {@code transitions} values, then no update action is needed and empty optional will be returned. - * if not, the transition of the old State gets overwritten with the transitions of the statedraft - * - * @param oldState the {@link State} which should be updated. - * @param newState the {@link StateDraft} where we get the new data. - * @return optional containing update action or empty optional if there are no changes detected or there was - * an error. - */ - @Nonnull - public static Optional> buildSetTransitionsAction( - @Nonnull final State oldState, - @Nonnull final StateDraft newState) { - - boolean emptyNew = newState.getTransitions() == null + return actions; + } + + /** + * Compares the {@code transitions} values of a {@link State} and a {@link StateDraft} and returns + * an {@link Optional} of update action, which would contain the {@code "setTransitions"} {@link + * UpdateAction}. If both {@link State} and {@link StateDraft} have the same {@code transitions} + * values, then no update action is needed and empty optional will be returned. if not, the + * transition of the old State gets overwritten with the transitions of the statedraft + * + * @param oldState the {@link State} which should be updated. + * @param newState the {@link StateDraft} where we get the new data. + * @return optional containing update action or empty optional if there are no changes detected or + * there was an error. + */ + @Nonnull + public static Optional> buildSetTransitionsAction( + @Nonnull final State oldState, @Nonnull final StateDraft newState) { + + boolean emptyNew = + newState.getTransitions() == null || newState.getTransitions().isEmpty() || newState.getTransitions().stream().noneMatch(Objects::nonNull); - boolean emptyOld = oldState.getTransitions() == null + boolean emptyOld = + oldState.getTransitions() == null || oldState.getTransitions().isEmpty() || oldState.getTransitions().stream().noneMatch(Objects::nonNull); - if (emptyNew && emptyOld) { - return Optional.empty(); - } else if (emptyNew) { - return Optional.of(SetTransitions.of(emptySet())); - } - - final Set> newTransitions = newState.getTransitions() - .stream() - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - final Set> oldTransitions = oldState.getTransitions() - .stream() - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - return buildUpdateAction(oldTransitions, newTransitions, - () -> { - Set> transitions = newTransitions - .stream() - .map(transition -> State.referenceOfId(transition.getId())) - .collect(Collectors.toSet()); - return SetTransitions.of(transitions); - }); + if (emptyNew && emptyOld) { + return Optional.empty(); + } else if (emptyNew) { + return Optional.of(SetTransitions.of(emptySet())); } - private static Set diffRoles(final Set src, final Set dst) { - return src - .stream() - .map(role -> { - if (!dst.contains(role)) { - return role; - } - return null; + final Set> newTransitions = + newState.getTransitions().stream().filter(Objects::nonNull).collect(Collectors.toSet()); + + final Set> oldTransitions = + oldState.getTransitions().stream().filter(Objects::nonNull).collect(Collectors.toSet()); + + return buildUpdateAction( + oldTransitions, + newTransitions, + () -> { + Set> transitions = + newTransitions.stream() + .map(transition -> State.referenceOfId(transition.getId())) + .collect(Collectors.toSet()); + return SetTransitions.of(transitions); + }); + } + + private static Set diffRoles(final Set src, final Set dst) { + return src.stream() + .map( + role -> { + if (!dst.contains(role)) { + return role; + } + return null; }) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } - + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySync.java b/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySync.java index 042c5ac323..cd6b0e04d4 100644 --- a/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySync.java +++ b/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySync.java @@ -1,5 +1,14 @@ package com.commercetools.sync.taxcategories; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static com.commercetools.sync.taxcategories.utils.TaxCategorySyncUtils.buildActions; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.allOf; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.services.TaxCategoryService; @@ -9,286 +18,318 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.taxcategories.TaxCategoryDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static com.commercetools.sync.taxcategories.utils.TaxCategorySyncUtils.buildActions; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.allOf; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; - -public class TaxCategorySync extends BaseSync { - - private static final String TAX_CATEGORY_FETCH_FAILED = "Failed to fetch existing tax categories with keys: '%s'."; - private static final String TAX_CATEGORY_UPDATE_FAILED = "Failed to update tax category with key: '%s'. Reason: %s"; - - private final TaxCategoryService taxCategoryService; - private final TaxCategoryBatchValidator batchValidator; - - public TaxCategorySync(@Nonnull final TaxCategorySyncOptions taxCategorySyncOptions) { - this(taxCategorySyncOptions, new TaxCategoryServiceImpl(taxCategorySyncOptions)); - } - - /** - * Takes a {@link TaxCategorySyncOptions} and a {@link TaxCategorySync} instances to instantiate - * a new {@link TaxCategorySync} instance that could be used to sync tax category drafts in the CTP project - * specified in the injected {@link TaxCategorySyncOptions} instance. - * - *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and passed to. - * - * @param taxCategorySyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param taxCategoryService the tax category service which is responsible for fetching/caching the - * tax categories from the CTP project. - */ - TaxCategorySync(@Nonnull final TaxCategorySyncOptions taxCategorySyncOptions, - @Nonnull final TaxCategoryService taxCategoryService) { - super(new TaxCategorySyncStatistics(), taxCategorySyncOptions); - this.taxCategoryService = taxCategoryService; - this.batchValidator = new TaxCategoryBatchValidator(getSyncOptions(), getStatistics()); - } - - @Override - protected CompletionStage process(@Nonnull final List resourceDrafts) { - List> batches = batchElements(resourceDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, completedFuture(statistics)); +public class TaxCategorySync + extends BaseSync { + + private static final String TAX_CATEGORY_FETCH_FAILED = + "Failed to fetch existing tax categories with keys: '%s'."; + private static final String TAX_CATEGORY_UPDATE_FAILED = + "Failed to update tax category with key: '%s'. Reason: %s"; + + private final TaxCategoryService taxCategoryService; + private final TaxCategoryBatchValidator batchValidator; + + public TaxCategorySync(@Nonnull final TaxCategorySyncOptions taxCategorySyncOptions) { + this(taxCategorySyncOptions, new TaxCategoryServiceImpl(taxCategorySyncOptions)); + } + + /** + * Takes a {@link TaxCategorySyncOptions} and a {@link TaxCategorySync} instances to instantiate a + * new {@link TaxCategorySync} instance that could be used to sync tax category drafts in the CTP + * project specified in the injected {@link TaxCategorySyncOptions} instance. + * + *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and + * passed to. + * + * @param taxCategorySyncOptions the container of all the options of the sync process including + * the CTP project client and/or configuration and other sync-specific options. + * @param taxCategoryService the tax category service which is responsible for fetching/caching + * the tax categories from the CTP project. + */ + TaxCategorySync( + @Nonnull final TaxCategorySyncOptions taxCategorySyncOptions, + @Nonnull final TaxCategoryService taxCategoryService) { + super(new TaxCategorySyncStatistics(), taxCategorySyncOptions); + this.taxCategoryService = taxCategoryService; + this.batchValidator = new TaxCategoryBatchValidator(getSyncOptions(), getStatistics()); + } + + @Override + protected CompletionStage process( + @Nonnull final List resourceDrafts) { + List> batches = + batchElements(resourceDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, completedFuture(statistics)); + } + + /** + * This method first creates a new {@link Set} of valid {@link TaxCategoryDraft} elements. For + * more on the rules of validation, check: {@link + * TaxCategoryBatchValidator#validateAndCollectReferencedKeys(List)}. Using the resulting set of + * {@code validTaxCategoryDrafts}, the matching tax categories in the target CTP project are + * fetched then the method {@link TaxCategorySync#syncBatch(Set, Set)} is called to perform the + * sync (update or create requests accordingly) on the target project. + * + *

In case of error during of fetching of existing tax categories, the error callback will be + * triggered. And the sync process would stop for the given batch. + * + * @param batch batch of drafts that need to be synced + * @return a {@link CompletionStage} containing an instance of {@link TaxCategorySyncStatistics} + * which contains information about the result of syncing the supplied batch to the target + * project. + */ + @Override + protected CompletionStage processBatch( + @Nonnull final List batch) { + + final ImmutablePair, Set> result = + batchValidator.validateAndCollectReferencedKeys(batch); + + final Set validDrafts = result.getLeft(); + if (validDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return CompletableFuture.completedFuture(statistics); } - - /** - * This method first creates a new {@link Set} of valid {@link TaxCategoryDraft} elements. For more on the rules of - * validation, check: {@link TaxCategoryBatchValidator#validateAndCollectReferencedKeys(List)}. Using the resulting - * set of {@code validTaxCategoryDrafts}, the matching tax categories in the target CTP project are fetched then - * the method {@link TaxCategorySync#syncBatch(Set, Set)} is called to perform the sync (update or - * create requests accordingly) on the target project. - * - *

In case of error during of fetching of existing tax categories, the error callback will be triggered. - * And the sync process would stop for the given batch. - *

- * - * @param batch batch of drafts that need to be synced - * @return a {@link CompletionStage} containing an instance - * of {@link TaxCategorySyncStatistics} which contains information about the result of syncing the supplied - * batch to the target project. - */ - @Override - protected CompletionStage processBatch(@Nonnull final List batch) { - - final ImmutablePair, Set> result = - batchValidator.validateAndCollectReferencedKeys(batch); - - final Set validDrafts = result.getLeft(); - if (validDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return CompletableFuture.completedFuture(statistics); - } - final Set validTaxCategoryKeys = result.getRight(); - - return taxCategoryService - .fetchMatchingTaxCategoriesByKeys(validTaxCategoryKeys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - Set fetchedTaxCategories = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(TAX_CATEGORY_FETCH_FAILED, validTaxCategoryKeys); - handleError(new SyncException(errorMessage, exception),null,null,null, - validTaxCategoryKeys.size()); - return completedFuture(null); - } else { - return syncBatch(fetchedTaxCategories, validDrafts); - } + final Set validTaxCategoryKeys = result.getRight(); + + return taxCategoryService + .fetchMatchingTaxCategoriesByKeys(validTaxCategoryKeys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + Set fetchedTaxCategories = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = format(TAX_CATEGORY_FETCH_FAILED, validTaxCategoryKeys); + handleError( + new SyncException(errorMessage, exception), + null, + null, + null, + validTaxCategoryKeys.size()); + return completedFuture(null); + } else { + return syncBatch(fetchedTaxCategories, validDrafts); + } }) - .thenApply(ignored -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignored -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed states to sync. - * - * @param syncException The exception describing the reason(s) of failure. - * @param entry The Resource, which should be updated - * @param draft The draft, which should be sync - * @param updateActions Generated update actions - * @param failedTimes The number of times that the failed states counter is incremented. - */ - private void handleError(@Nonnull final SyncException syncException, @Nullable final TaxCategory entry, - @Nullable final TaxCategoryDraft draft, - @Nullable final List> updateActions, - final int failedTimes) { - syncOptions.applyErrorCallback(syncException, entry, draft, updateActions); - statistics.incrementFailed(failedTimes); - } - - - /** - * Given a set of tax category drafts, attempts to sync the drafts with the existing tax categories in the CTP - * project. The tax category and the draft are considered to match if they have the same key. When there will be no - * error it will attempt to sync the drafts transactions. - * - * @param oldTaxCategories old tax categories. - * @param newTaxCategories drafts that need to be synced. - * @return a {@link CompletionStage} which contains an empty result after execution of the update - */ - @Nonnull - private CompletionStage syncBatch( - @Nonnull final Set oldTaxCategories, - @Nonnull final Set newTaxCategories) { - - final Map oldTaxCategoryMap = oldTaxCategories - .stream() - .collect(toMap(TaxCategory::getKey, identity())); - - return allOf(newTaxCategories - .stream() - .map(newTaxCategory -> { - final TaxCategory oldTaxCategory = oldTaxCategoryMap.get(newTaxCategory.getKey()); - - return ofNullable(oldTaxCategory) - .map(taxCategory -> buildActionsAndUpdate(oldTaxCategory, newTaxCategory)) - .orElseGet(() -> applyCallbackAndCreate(newTaxCategory)); - }) + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed states to sync. + * + * @param syncException The exception describing the reason(s) of failure. + * @param entry The Resource, which should be updated + * @param draft The draft, which should be sync + * @param updateActions Generated update actions + * @param failedTimes The number of times that the failed states counter is incremented. + */ + private void handleError( + @Nonnull final SyncException syncException, + @Nullable final TaxCategory entry, + @Nullable final TaxCategoryDraft draft, + @Nullable final List> updateActions, + final int failedTimes) { + syncOptions.applyErrorCallback(syncException, entry, draft, updateActions); + statistics.incrementFailed(failedTimes); + } + + /** + * Given a set of tax category drafts, attempts to sync the drafts with the existing tax + * categories in the CTP project. The tax category and the draft are considered to match if they + * have the same key. When there will be no error it will attempt to sync the drafts transactions. + * + * @param oldTaxCategories old tax categories. + * @param newTaxCategories drafts that need to be synced. + * @return a {@link CompletionStage} which contains an empty result after execution of the update + */ + @Nonnull + private CompletionStage syncBatch( + @Nonnull final Set oldTaxCategories, + @Nonnull final Set newTaxCategories) { + + final Map oldTaxCategoryMap = + oldTaxCategories.stream().collect(toMap(TaxCategory::getKey, identity())); + + return allOf( + newTaxCategories.stream() + .map( + newTaxCategory -> { + final TaxCategory oldTaxCategory = oldTaxCategoryMap.get(newTaxCategory.getKey()); + + return ofNullable(oldTaxCategory) + .map(taxCategory -> buildActionsAndUpdate(oldTaxCategory, newTaxCategory)) + .orElseGet(() -> applyCallbackAndCreate(newTaxCategory)); + }) .map(CompletionStage::toCompletableFuture) .toArray(CompletableFuture[]::new)); + } + + /** + * Given a tax category draft, this method applies the beforeCreateCallback and then issues a + * create request to the CTP project to create the corresponding TaxCategory. + * + * @param taxCategoryDraft the tax category draft to create the tax category from. + * @return a {@link CompletionStage} which contains an empty result after execution of the create. + */ + @Nonnull + private CompletionStage> applyCallbackAndCreate( + @Nonnull final TaxCategoryDraft taxCategoryDraft) { + + return syncOptions + .applyBeforeCreateCallback(taxCategoryDraft) + .map( + draft -> + taxCategoryService + .createTaxCategory(draft) + .thenApply( + taxCategoryOptional -> { + if (taxCategoryOptional.isPresent()) { + statistics.incrementCreated(); + } else { + statistics.incrementFailed(); + } + return taxCategoryOptional; + })) + .orElse(completedFuture(Optional.empty())); + } + + @Nonnull + private CompletionStage> buildActionsAndUpdate( + @Nonnull final TaxCategory oldTaxCategory, @Nonnull final TaxCategoryDraft newTaxCategory) { + + final List> updateActions = + buildActions(oldTaxCategory, newTaxCategory); + + List> updateActionsAfterCallback = + syncOptions.applyBeforeUpdateCallback(updateActions, newTaxCategory, oldTaxCategory); + + if (!updateActionsAfterCallback.isEmpty()) { + return updateTaxCategory(oldTaxCategory, newTaxCategory, updateActionsAfterCallback); } - /** - * Given a tax category draft, this method applies the beforeCreateCallback and then issues a create request to the - * CTP project to create the corresponding TaxCategory. - * - * @param taxCategoryDraft the tax category draft to create the tax category from. - * @return a {@link CompletionStage} which contains an empty result after execution of the create. - */ - @Nonnull - private CompletionStage> applyCallbackAndCreate( - @Nonnull final TaxCategoryDraft taxCategoryDraft) { - - return syncOptions - .applyBeforeCreateCallback(taxCategoryDraft) - .map(draft -> taxCategoryService - .createTaxCategory(draft) - .thenApply(taxCategoryOptional -> { - if (taxCategoryOptional.isPresent()) { - statistics.incrementCreated(); - } else { - statistics.incrementFailed(); - } - return taxCategoryOptional; - }) - ) - .orElse(completedFuture(Optional.empty())); - } - - @Nonnull - private CompletionStage> buildActionsAndUpdate( - @Nonnull final TaxCategory oldTaxCategory, - @Nonnull final TaxCategoryDraft newTaxCategory) { - - final List> updateActions = buildActions(oldTaxCategory, newTaxCategory); - - List> updateActionsAfterCallback = - syncOptions.applyBeforeUpdateCallback(updateActions, newTaxCategory, oldTaxCategory); - - if (!updateActionsAfterCallback.isEmpty()) { - return updateTaxCategory(oldTaxCategory, newTaxCategory, updateActionsAfterCallback); - } - - return completedFuture(null); - } - - /** - * Given an existing {@link TaxCategory} and a new {@link TaxCategoryDraft}, the method calculates all the - * update actions required to synchronize the existing tax category to be the same as the new one. If there are - * update actions found, a request is made to CTP to update the existing tax category, otherwise it doesn't issue a - * request. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP, the error handling method - * is called. - * - * @param oldTaxCategory existing tax category that could be updated. - * @param newTaxCategory draft containing data that could differ from data in {@code oldTaxCategory}. - * @return a {@link CompletionStage} which contains an empty result after execution of the update. - */ - @Nonnull - private CompletionStage> updateTaxCategory( - @Nonnull final TaxCategory oldTaxCategory, - @Nonnull final TaxCategoryDraft newTaxCategory, - @Nonnull final List> updateActions) { - - return taxCategoryService - .updateTaxCategory(oldTaxCategory, updateActions) - .handle(ImmutablePair::new) - .thenCompose(updateResponse -> { - final TaxCategory updatedTaxCategory = updateResponse.getKey(); - final Throwable sphereException = updateResponse.getValue(); - - if (sphereException != null) { - return executeSupplierIfConcurrentModificationException( - sphereException, - () -> fetchAndUpdate(oldTaxCategory, newTaxCategory), - () -> { - final String errorMessage = - format(TAX_CATEGORY_UPDATE_FAILED, newTaxCategory.getKey(), - sphereException.getMessage()); - handleError(new SyncException(errorMessage, sphereException), oldTaxCategory, - newTaxCategory, - updateActions, 1); - return completedFuture(Optional.empty()); - }); - } else { - statistics.incrementUpdated(); - return completedFuture(Optional.of(updatedTaxCategory)); - } + return completedFuture(null); + } + + /** + * Given an existing {@link TaxCategory} and a new {@link TaxCategoryDraft}, the method calculates + * all the update actions required to synchronize the existing tax category to be the same as the + * new one. If there are update actions found, a request is made to CTP to update the existing tax + * category, otherwise it doesn't issue a request. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP, + * the error handling method is called. + * + * @param oldTaxCategory existing tax category that could be updated. + * @param newTaxCategory draft containing data that could differ from data in {@code + * oldTaxCategory}. + * @return a {@link CompletionStage} which contains an empty result after execution of the update. + */ + @Nonnull + private CompletionStage> updateTaxCategory( + @Nonnull final TaxCategory oldTaxCategory, + @Nonnull final TaxCategoryDraft newTaxCategory, + @Nonnull final List> updateActions) { + + return taxCategoryService + .updateTaxCategory(oldTaxCategory, updateActions) + .handle(ImmutablePair::new) + .thenCompose( + updateResponse -> { + final TaxCategory updatedTaxCategory = updateResponse.getKey(); + final Throwable sphereException = updateResponse.getValue(); + + if (sphereException != null) { + return executeSupplierIfConcurrentModificationException( + sphereException, + () -> fetchAndUpdate(oldTaxCategory, newTaxCategory), + () -> { + final String errorMessage = + format( + TAX_CATEGORY_UPDATE_FAILED, + newTaxCategory.getKey(), + sphereException.getMessage()); + handleError( + new SyncException(errorMessage, sphereException), + oldTaxCategory, + newTaxCategory, + updateActions, + 1); + return completedFuture(Optional.empty()); + }); + } else { + statistics.incrementUpdated(); + return completedFuture(Optional.of(updatedTaxCategory)); + } }); - } - - @Nonnull - private CompletionStage> fetchAndUpdate( - @Nonnull final TaxCategory oldTaxCategory, - @Nonnull final TaxCategoryDraft newTaxCategory) { - - final String key = oldTaxCategory.getKey(); - return taxCategoryService - .fetchTaxCategory(key) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Optional fetchedTaxCategoryOptional = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(TAX_CATEGORY_UPDATE_FAILED, key, + } + + @Nonnull + private CompletionStage> fetchAndUpdate( + @Nonnull final TaxCategory oldTaxCategory, @Nonnull final TaxCategoryDraft newTaxCategory) { + + final String key = oldTaxCategory.getKey(); + return taxCategoryService + .fetchTaxCategory(key) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Optional fetchedTaxCategoryOptional = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + TAX_CATEGORY_UPDATE_FAILED, + key, "Failed to fetch from CTP while retrying after concurrency modification."); - handleError(new SyncException(errorMessage, exception),oldTaxCategory,newTaxCategory, - null,1); - return completedFuture(null); - } - - return fetchedTaxCategoryOptional - .map(fetchedTaxCategory -> buildActionsAndUpdate(fetchedTaxCategory, newTaxCategory)) - .orElseGet(() -> { - final String errorMessage = format(TAX_CATEGORY_UPDATE_FAILED, key, - "Not found when attempting to fetch while retrying " - + "after concurrency modification."); - handleError(new SyncException(errorMessage),oldTaxCategory,newTaxCategory,null, 1); + handleError( + new SyncException(errorMessage, exception), + oldTaxCategory, + newTaxCategory, + null, + 1); + return completedFuture(null); + } + + return fetchedTaxCategoryOptional + .map( + fetchedTaxCategory -> + buildActionsAndUpdate(fetchedTaxCategory, newTaxCategory)) + .orElseGet( + () -> { + final String errorMessage = + format( + TAX_CATEGORY_UPDATE_FAILED, + key, + "Not found when attempting to fetch while retrying " + + "after concurrency modification."); + handleError( + new SyncException(errorMessage), + oldTaxCategory, + newTaxCategory, + null, + 1); return completedFuture(null); - }); + }); }); - } + } } diff --git a/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptions.java b/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptions.java index 24bc25b373..2dc3343c9b 100644 --- a/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptions.java +++ b/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptions.java @@ -9,33 +9,43 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.taxcategories.TaxCategoryDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class TaxCategorySyncOptions extends BaseSyncOptions { - TaxCategorySyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallBack, - @Nullable final TriConsumer, Optional> - warningCallBack, - final int batchSize, - @Nullable final TriFunction>, TaxCategoryDraft, TaxCategory, - List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, final long cacheSize) { - super( - ctpClient, - errorCallBack, - warningCallBack, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize); - } - + TaxCategorySyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallBack, + @Nullable + final TriConsumer, Optional> + warningCallBack, + final int batchSize, + @Nullable + final TriFunction< + List>, + TaxCategoryDraft, + TaxCategory, + List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { + super( + ctpClient, + errorCallBack, + warningCallBack, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } } diff --git a/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsBuilder.java index 07ddca2b65..45ef1df620 100644 --- a/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsBuilder.java @@ -4,57 +4,57 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.taxcategories.TaxCategoryDraft; - import javax.annotation.Nonnull; -public final class TaxCategorySyncOptionsBuilder extends BaseSyncOptionsBuilder { - - public static final int BATCH_SIZE_DEFAULT = 50; - - private TaxCategorySyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } - - /** - * Creates a new instance of {@link TaxCategorySyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 50). - * - * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link TaxCategorySyncOptionsBuilder} - */ - public static TaxCategorySyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new TaxCategorySyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); - } - - /** - * Creates new instance of {@link TaxCategorySyncOptions} enriched with all attributes provided to {@code this} - * builder. - * - * @return new instance of {@link TaxCategorySyncOptions} - */ - @Override - public TaxCategorySyncOptions build() { - return new TaxCategorySyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } - - /** - * Returns an instance of this class to be used in the superclass's generic methods. Please see the JavaDoc in the - * overridden method for further details. - * - * @return an instance of this class. - */ - @Override - protected TaxCategorySyncOptionsBuilder getThis() { - return this; - } - +public final class TaxCategorySyncOptionsBuilder + extends BaseSyncOptionsBuilder< + TaxCategorySyncOptionsBuilder, TaxCategorySyncOptions, TaxCategory, TaxCategoryDraft> { + + public static final int BATCH_SIZE_DEFAULT = 50; + + private TaxCategorySyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } + + /** + * Creates a new instance of {@link TaxCategorySyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 50). + * + * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the + * target CTP project. + * @return new instance of {@link TaxCategorySyncOptionsBuilder} + */ + public static TaxCategorySyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new TaxCategorySyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } + + /** + * Creates new instance of {@link TaxCategorySyncOptions} enriched with all attributes provided to + * {@code this} builder. + * + * @return new instance of {@link TaxCategorySyncOptions} + */ + @Override + public TaxCategorySyncOptions build() { + return new TaxCategorySyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } + + /** + * Returns an instance of this class to be used in the superclass's generic methods. Please see + * the JavaDoc in the overridden method for further details. + * + * @return an instance of this class. + */ + @Override + protected TaxCategorySyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/taxcategories/helpers/TaxCategoryBatchValidator.java b/src/main/java/com/commercetools/sync/taxcategories/helpers/TaxCategoryBatchValidator.java index 85ec2b5485..35686376a9 100644 --- a/src/main/java/com/commercetools/sync/taxcategories/helpers/TaxCategoryBatchValidator.java +++ b/src/main/java/com/commercetools/sync/taxcategories/helpers/TaxCategoryBatchValidator.java @@ -1,127 +1,136 @@ package com.commercetools.sync.taxcategories.helpers; +import static java.lang.String.format; +import static java.util.stream.Collectors.toSet; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.helpers.BaseBatchValidator; import com.commercetools.sync.taxcategories.TaxCategorySyncOptions; - import io.sphere.sdk.taxcategories.TaxCategoryDraft; import io.sphere.sdk.taxcategories.TaxRateDraft; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; - -import static java.lang.String.format; -import static java.util.stream.Collectors.toSet; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; public class TaxCategoryBatchValidator - extends BaseBatchValidator { - - static final String TAX_CATEGORY_DRAFT_KEY_NOT_SET = "TaxCategoryDraft with name: %s doesn't have a key. " - + "Please make sure all tax category drafts have keys."; - static final String TAX_CATEGORY_DRAFT_IS_NULL = "TaxCategoryDraft is null."; - static final String TAX_CATEGORY_DUPLICATED_COUNTRY = "Tax rate drafts have duplicated country " - + "codes. Duplicated tax rate country code: '%s'. Tax rate country codes and " - + "states are expected to be unique inside their tax category."; - static final String TAX_CATEGORY_DUPLICATED_COUNTRY_AND_STATE = "Tax rate drafts have duplicated country " - + "codes and states. Duplicated tax rate country code: '%s'. state : '%s'. Tax rate country codes and " - + "states are expected to be unique inside their tax category."; - - public TaxCategoryBatchValidator(@Nonnull final TaxCategorySyncOptions syncOptions, - @Nonnull final TaxCategorySyncStatistics syncStatistics) { - super(syncOptions, syncStatistics); - } - - /** - * Given the {@link List}<{@link TaxCategoryDraft}> of drafts this method attempts to validate - * drafts and return an {@link ImmutablePair}<{@link Set}<{@link TaxCategoryDraft}>,{@link Set}< - * {@link String}>> which contains the {@link Set} of valid drafts and valid tax category keys. - * - *

A valid tax category draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a key which is not blank (null/empty)
  4. - *
  5. Tax rates have not duplicated country and state.
  6. - *
- * - * @param taxCategoryDrafts the tax category drafts to validate and collect valid tax category keys. - * @return {@link ImmutablePair}<{@link Set}<{@link TaxCategoryDraft}>, - * {@link Set}<{@link String}>> which contains the {@link Set} of valid drafts and - * valid tax category keys. - */ - @Override - public ImmutablePair, Set> validateAndCollectReferencedKeys( - @Nonnull final List taxCategoryDrafts) { - - final Set validDrafts = taxCategoryDrafts - .stream() - .filter(this::isValidTaxCategoryDraft) - .collect(toSet()); - - final Set validKeys = validDrafts - .stream() - .map(TaxCategoryDraft::getKey) - .collect(toSet()); - - return ImmutablePair.of(validDrafts, validKeys); + extends BaseBatchValidator< + TaxCategoryDraft, TaxCategorySyncOptions, TaxCategorySyncStatistics> { + + static final String TAX_CATEGORY_DRAFT_KEY_NOT_SET = + "TaxCategoryDraft with name: %s doesn't have a key. " + + "Please make sure all tax category drafts have keys."; + static final String TAX_CATEGORY_DRAFT_IS_NULL = "TaxCategoryDraft is null."; + static final String TAX_CATEGORY_DUPLICATED_COUNTRY = + "Tax rate drafts have duplicated country " + + "codes. Duplicated tax rate country code: '%s'. Tax rate country codes and " + + "states are expected to be unique inside their tax category."; + static final String TAX_CATEGORY_DUPLICATED_COUNTRY_AND_STATE = + "Tax rate drafts have duplicated country " + + "codes and states. Duplicated tax rate country code: '%s'. state : '%s'. Tax rate country codes and " + + "states are expected to be unique inside their tax category."; + + public TaxCategoryBatchValidator( + @Nonnull final TaxCategorySyncOptions syncOptions, + @Nonnull final TaxCategorySyncStatistics syncStatistics) { + super(syncOptions, syncStatistics); + } + + /** + * Given the {@link List}<{@link TaxCategoryDraft}> of drafts this method attempts to + * validate drafts and return an {@link ImmutablePair}<{@link Set}<{@link + * TaxCategoryDraft}>,{@link Set}< {@link String}>> which contains the {@link Set} of + * valid drafts and valid tax category keys. + * + *

A valid tax category draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a key which is not blank (null/empty) + *
  3. Tax rates have not duplicated country and state. + *
+ * + * @param taxCategoryDrafts the tax category drafts to validate and collect valid tax category + * keys. + * @return {@link ImmutablePair}<{@link Set}<{@link TaxCategoryDraft}>, {@link + * Set}<{@link String}>> which contains the {@link Set} of valid drafts and valid tax + * category keys. + */ + @Override + public ImmutablePair, Set> validateAndCollectReferencedKeys( + @Nonnull final List taxCategoryDrafts) { + + final Set validDrafts = + taxCategoryDrafts.stream().filter(this::isValidTaxCategoryDraft).collect(toSet()); + + final Set validKeys = + validDrafts.stream().map(TaxCategoryDraft::getKey).collect(toSet()); + + return ImmutablePair.of(validDrafts, validKeys); + } + + private boolean isValidTaxCategoryDraft(@Nullable final TaxCategoryDraft taxCategoryDraft) { + + if (taxCategoryDraft == null) { + handleError(TAX_CATEGORY_DRAFT_IS_NULL); + } else if (isBlank(taxCategoryDraft.getKey())) { + handleError(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, taxCategoryDraft.getName())); + } else if (taxCategoryDraft.getTaxRates() != null + && !taxCategoryDraft.getTaxRates().isEmpty()) { + return validateIfDuplicateCountryAndState(taxCategoryDraft.getTaxRates()); + } else { + return true; } - private boolean isValidTaxCategoryDraft( - @Nullable final TaxCategoryDraft taxCategoryDraft) { - - if (taxCategoryDraft == null) { - handleError(TAX_CATEGORY_DRAFT_IS_NULL); - } else if (isBlank(taxCategoryDraft.getKey())) { - handleError(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, taxCategoryDraft.getName())); - } else if (taxCategoryDraft.getTaxRates() != null && !taxCategoryDraft.getTaxRates().isEmpty()) { - return validateIfDuplicateCountryAndState(taxCategoryDraft.getTaxRates()); - } else { - return true; + return false; + } + + private boolean validateIfDuplicateCountryAndState(final List taxRateDrafts) { + /* + For TaxRates uniqueness could be ensured by country code and states. + So in tax category sync are using country code and states for matching. + + Representation of the commercetools platform error when country code is duplicated, + { + "statusCode": 400, + "message": "A duplicate value '{\"country\":\"DE\"}' exists for field 'country'.", + "errors": [ + { + "code": "DuplicateField", + .... + ] } - - return false; - } - - private boolean validateIfDuplicateCountryAndState(final List taxRateDrafts) { - /* - For TaxRates uniqueness could be ensured by country code and states. - So in tax category sync are using country code and states for matching. - - Representation of the commercetools platform error when country code is duplicated, - { - "statusCode": 400, - "message": "A duplicate value '{\"country\":\"DE\"}' exists for field 'country'.", - "errors": [ - { - "code": "DuplicateField", - .... - ] - } - */ - Map> map = taxRateDrafts.stream().collect( - Collectors.groupingBy(draft -> Objects.toString(draft.getCountry(), ""), - Collectors.groupingBy(draft -> Objects.toString(draft.getState(), ""), - Collectors.counting()))); - - for (Map.Entry> countryEntry : map.entrySet()) { - for (Map.Entry stateEntry: countryEntry.getValue().entrySet()) { - if (stateEntry.getValue() > 1L) { - String errorMessage = StringUtils.isBlank(stateEntry.getKey()) - ? format(TAX_CATEGORY_DUPLICATED_COUNTRY, countryEntry.getKey()) - : format(TAX_CATEGORY_DUPLICATED_COUNTRY_AND_STATE, countryEntry.getKey(), stateEntry.getKey()); - handleError(new SyncException(errorMessage)); - return false; - } - } + */ + Map> map = + taxRateDrafts.stream() + .collect( + Collectors.groupingBy( + draft -> Objects.toString(draft.getCountry(), ""), + Collectors.groupingBy( + draft -> Objects.toString(draft.getState(), ""), Collectors.counting()))); + + for (Map.Entry> countryEntry : map.entrySet()) { + for (Map.Entry stateEntry : countryEntry.getValue().entrySet()) { + if (stateEntry.getValue() > 1L) { + String errorMessage = + StringUtils.isBlank(stateEntry.getKey()) + ? format(TAX_CATEGORY_DUPLICATED_COUNTRY, countryEntry.getKey()) + : format( + TAX_CATEGORY_DUPLICATED_COUNTRY_AND_STATE, + countryEntry.getKey(), + stateEntry.getKey()); + handleError(new SyncException(errorMessage)); + return false; } - - return true; + } } + + return true; + } } diff --git a/src/main/java/com/commercetools/sync/taxcategories/helpers/TaxCategorySyncStatistics.java b/src/main/java/com/commercetools/sync/taxcategories/helpers/TaxCategorySyncStatistics.java index 7ff751c573..436bd3b927 100644 --- a/src/main/java/com/commercetools/sync/taxcategories/helpers/TaxCategorySyncStatistics.java +++ b/src/main/java/com/commercetools/sync/taxcategories/helpers/TaxCategorySyncStatistics.java @@ -3,21 +3,22 @@ import com.commercetools.sync.commons.helpers.BaseSyncStatistics; /** - * Tax category sync statistics. - * Keeps track of processed, created, updated and failed states through whole sync process. + * Tax category sync statistics. Keeps track of processed, created, updated and failed states + * through whole sync process. */ public final class TaxCategorySyncStatistics extends BaseSyncStatistics { - /** - * Builds a summary of the tax category sync statistics instance that looks like the following example: - * - *

"Summary: 2 tax categories were processed in total (0 created, 0 updated and 0 failed to sync)." - * - * @return a summary message of the tax category sync statistics instance. - */ - @Override - public String getReportMessage() { - return getDefaultReportMessageForResource("tax categories"); - } - + /** + * Builds a summary of the tax category sync statistics instance that looks like the following + * example: + * + *

"Summary: 2 tax categories were processed in total (0 created, 0 updated and 0 failed to + * sync)." + * + * @return a summary message of the tax category sync statistics instance. + */ + @Override + public String getReportMessage() { + return getDefaultReportMessageForResource("tax categories"); + } } diff --git a/src/main/java/com/commercetools/sync/taxcategories/utils/TaxCategorySyncUtils.java b/src/main/java/com/commercetools/sync/taxcategories/utils/TaxCategorySyncUtils.java index 99da6e5dbe..8b6df17ebc 100644 --- a/src/main/java/com/commercetools/sync/taxcategories/utils/TaxCategorySyncUtils.java +++ b/src/main/java/com/commercetools/sync/taxcategories/utils/TaxCategorySyncUtils.java @@ -1,48 +1,43 @@ package com.commercetools.sync.taxcategories.utils; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildChangeNameAction; +import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildSetDescriptionAction; +import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildTaxRateUpdateActions; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.taxcategories.TaxCategoryDraft; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; - -import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildChangeNameAction; -import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildTaxRateUpdateActions; -import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildSetDescriptionAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import javax.annotation.Nonnull; public final class TaxCategorySyncUtils { - private TaxCategorySyncUtils() { - } - - /** - * Compares all the fields of a {@link TaxCategory} and a - * {@link TaxCategoryDraft}. It returns a {@link List} of {@link UpdateAction}<{@link TaxCategory}> as a - * result. If no update action is needed, for example in case where both the {@link TaxCategory} and the - * {@link TaxCategoryDraft} have the same fields, an empty {@link List} is returned. - * - * @param oldTaxCategory the {@link TaxCategory} which should be updated. - * @param newTaxCategory the {@link TaxCategoryDraft} where we get the new data. - * @return A list of tax category-specific update actions. - */ - @Nonnull - public static List> buildActions( - @Nonnull final TaxCategory oldTaxCategory, - @Nonnull final TaxCategoryDraft newTaxCategory) { - - final List> updateActions = new ArrayList<>( + private TaxCategorySyncUtils() {} + + /** + * Compares all the fields of a {@link TaxCategory} and a {@link TaxCategoryDraft}. It returns a + * {@link List} of {@link UpdateAction}<{@link TaxCategory}> as a result. If no update + * action is needed, for example in case where both the {@link TaxCategory} and the {@link + * TaxCategoryDraft} have the same fields, an empty {@link List} is returned. + * + * @param oldTaxCategory the {@link TaxCategory} which should be updated. + * @param newTaxCategory the {@link TaxCategoryDraft} where we get the new data. + * @return A list of tax category-specific update actions. + */ + @Nonnull + public static List> buildActions( + @Nonnull final TaxCategory oldTaxCategory, @Nonnull final TaxCategoryDraft newTaxCategory) { + + final List> updateActions = + new ArrayList<>( filterEmptyOptionals( buildChangeNameAction(oldTaxCategory, newTaxCategory), - buildSetDescriptionAction(oldTaxCategory, newTaxCategory) - ) - ); - - updateActions.addAll(buildTaxRateUpdateActions(oldTaxCategory, newTaxCategory)); + buildSetDescriptionAction(oldTaxCategory, newTaxCategory))); - return updateActions; - } + updateActions.addAll(buildTaxRateUpdateActions(oldTaxCategory, newTaxCategory)); + return updateActions; + } } diff --git a/src/main/java/com/commercetools/sync/taxcategories/utils/TaxCategoryUpdateActionUtils.java b/src/main/java/com/commercetools/sync/taxcategories/utils/TaxCategoryUpdateActionUtils.java index cc75efa846..37498fb787 100644 --- a/src/main/java/com/commercetools/sync/taxcategories/utils/TaxCategoryUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/taxcategories/utils/TaxCategoryUpdateActionUtils.java @@ -1,78 +1,76 @@ package com.commercetools.sync.taxcategories.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.taxcategories.utils.TaxRatesUpdateActionUtils.buildTaxRatesUpdateActions; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.taxcategories.TaxCategoryDraft; import io.sphere.sdk.taxcategories.commands.updateactions.ChangeName; import io.sphere.sdk.taxcategories.commands.updateactions.SetDescription; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.taxcategories.utils.TaxRatesUpdateActionUtils.buildTaxRatesUpdateActions; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; - +import javax.annotation.Nonnull; public final class TaxCategoryUpdateActionUtils { - private TaxCategoryUpdateActionUtils() { - } - - /** - * Compares the {@code name} values of a {@link TaxCategory} and a {@link TaxCategoryDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeName"} - * {@link UpdateAction}. If both {@link TaxCategory} and {@link TaxCategoryDraft} have the same - * {@code name} values, then no update action is needed and empty optional will be returned. - * - * @param oldTaxCategory the tax category that should be updated. - * @param newTaxCategory the tax category draft which contains the new name. - * @return optional containing update action or empty optional if names are identical. - */ - @Nonnull - public static Optional> buildChangeNameAction( - @Nonnull final TaxCategory oldTaxCategory, - @Nonnull final TaxCategoryDraft newTaxCategory) { + private TaxCategoryUpdateActionUtils() {} - return buildUpdateAction(oldTaxCategory.getName(), newTaxCategory.getName(), - () -> ChangeName.of(newTaxCategory.getName())); - } + /** + * Compares the {@code name} values of a {@link TaxCategory} and a {@link TaxCategoryDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "changeName"} + * {@link UpdateAction}. If both {@link TaxCategory} and {@link TaxCategoryDraft} have the same + * {@code name} values, then no update action is needed and empty optional will be returned. + * + * @param oldTaxCategory the tax category that should be updated. + * @param newTaxCategory the tax category draft which contains the new name. + * @return optional containing update action or empty optional if names are identical. + */ + @Nonnull + public static Optional> buildChangeNameAction( + @Nonnull final TaxCategory oldTaxCategory, @Nonnull final TaxCategoryDraft newTaxCategory) { - /** - * Compares the {@code description} values of a {@link TaxCategory} and a {@link TaxCategoryDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "setDescription"} - * {@link UpdateAction}. If both {@link TaxCategory} and {@link TaxCategoryDraft} have the same - * {@code description} values, then no update action is needed and empty optional will be returned. - * - * @param oldTaxCategory the tax category that should be updated. - * @param newTaxCategory the tax category draft which contains the new description. - * @return optional containing update action or empty optional if descriptions are identical. - */ - @Nonnull - public static Optional> buildSetDescriptionAction( - @Nonnull final TaxCategory oldTaxCategory, - @Nonnull final TaxCategoryDraft newTaxCategory) { + return buildUpdateAction( + oldTaxCategory.getName(), + newTaxCategory.getName(), + () -> ChangeName.of(newTaxCategory.getName())); + } - return buildUpdateAction(oldTaxCategory.getDescription(), newTaxCategory.getDescription(), - () -> SetDescription.of(newTaxCategory.getDescription())); - } + /** + * Compares the {@code description} values of a {@link TaxCategory} and a {@link TaxCategoryDraft} + * and returns an {@link Optional} of update action, which would contain the {@code + * "setDescription"} {@link UpdateAction}. If both {@link TaxCategory} and {@link + * TaxCategoryDraft} have the same {@code description} values, then no update action is needed and + * empty optional will be returned. + * + * @param oldTaxCategory the tax category that should be updated. + * @param newTaxCategory the tax category draft which contains the new description. + * @return optional containing update action or empty optional if descriptions are identical. + */ + @Nonnull + public static Optional> buildSetDescriptionAction( + @Nonnull final TaxCategory oldTaxCategory, @Nonnull final TaxCategoryDraft newTaxCategory) { - /** - * Compares the tax rates of a {@link TaxCategory} and a {@link TaxCategoryDraft} and returns a list of - * {@link UpdateAction}<{@link TaxCategory}> as a result. If both the {@link TaxCategory} and - * the {@link TaxCategoryDraft} have identical tax rates, then no update action is needed and hence an empty - * {@link List} is returned. - * - * @param oldTaxCategory the tax category which should be updated. - * @param newTaxCategory the tax category draft where we get the key. - * @return A list with the update actions or an empty list if the tax rates are identical. - */ - @Nonnull - public static List> buildTaxRateUpdateActions( - @Nonnull final TaxCategory oldTaxCategory, - @Nonnull final TaxCategoryDraft newTaxCategory) { + return buildUpdateAction( + oldTaxCategory.getDescription(), + newTaxCategory.getDescription(), + () -> SetDescription.of(newTaxCategory.getDescription())); + } - return buildTaxRatesUpdateActions(oldTaxCategory.getTaxRates(), newTaxCategory.getTaxRates()); - } + /** + * Compares the tax rates of a {@link TaxCategory} and a {@link TaxCategoryDraft} and returns a + * list of {@link UpdateAction}<{@link TaxCategory}> as a result. If both the {@link + * TaxCategory} and the {@link TaxCategoryDraft} have identical tax rates, then no update action + * is needed and hence an empty {@link List} is returned. + * + * @param oldTaxCategory the tax category which should be updated. + * @param newTaxCategory the tax category draft where we get the key. + * @return A list with the update actions or an empty list if the tax rates are identical. + */ + @Nonnull + public static List> buildTaxRateUpdateActions( + @Nonnull final TaxCategory oldTaxCategory, @Nonnull final TaxCategoryDraft newTaxCategory) { + return buildTaxRatesUpdateActions(oldTaxCategory.getTaxRates(), newTaxCategory.getTaxRates()); + } } diff --git a/src/main/java/com/commercetools/sync/taxcategories/utils/TaxRatesUpdateActionUtils.java b/src/main/java/com/commercetools/sync/taxcategories/utils/TaxRatesUpdateActionUtils.java index 222667660e..49d0f815f2 100644 --- a/src/main/java/com/commercetools/sync/taxcategories/utils/TaxRatesUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/taxcategories/utils/TaxRatesUpdateActionUtils.java @@ -1,5 +1,9 @@ package com.commercetools.sync.taxcategories.utils; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + import com.neovisionaries.i18n.CountryCode; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.taxcategories.TaxCategory; @@ -9,181 +13,169 @@ import io.sphere.sdk.taxcategories.commands.updateactions.AddTaxRate; import io.sphere.sdk.taxcategories.commands.updateactions.RemoveTaxRate; import io.sphere.sdk.taxcategories.commands.updateactions.ReplaceTaxRate; -import org.apache.commons.lang3.StringUtils; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.StringUtils; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; - -/** - * This class is only meant for the internal use of the commercetools-sync-java library. - */ +/** This class is only meant for the internal use of the commercetools-sync-java library. */ final class TaxRatesUpdateActionUtils { - private TaxRatesUpdateActionUtils() { - } - - /** - * Compares a list of {@link TaxRate}s with a list of {@link TaxRateDraft}s to - * returns a {@link List} of {@link UpdateAction}<{@link TaxCategory}>. If both lists have identical - * TaxRates, then no update actions are needed and hence an empty {@link List} is returned. - * - *

If the list of new {@link TaxRateDraft}s is empty, then remove actions are built for - * every existing tax rate in the {@code oldTaxRates} list. - * - *

Notes: The method will ignore/filter out: - *

    - *
  • - * the {@code null} tax rates drafts from the passed {@code newTaxRateDrafts}. - *
  • - *
  • - * the duplicated (has same country code and state) tax rates drafts from the passed {@code newTaxRateDrafts} - * and will create update action only for the first tax rate draft in the {@code newTaxRateDrafts}. - *
  • - *
- * - * @param oldTaxRates the old list of tax rates. - * @param newTaxRatesDrafts the new list of tax rates drafts. - * @return a list of tax rates update actions if the list of tax rates are not identical. - * Otherwise, if the tax rates are identical, an empty list is returned. - - */ - @Nonnull - static List> buildTaxRatesUpdateActions( - @Nonnull final List oldTaxRates, final List newTaxRatesDrafts) { - - if (newTaxRatesDrafts != null && !newTaxRatesDrafts.isEmpty()) { - return buildUpdateActions( - oldTaxRates, - newTaxRatesDrafts.stream().filter(Objects::nonNull).collect(toList()) - ); - } else { - return oldTaxRates - .stream() - .map(TaxRate::getId) - .filter(Objects::nonNull) - .map(RemoveTaxRate::of) - .collect(Collectors.toList()); - } - } - - /** - * Compares a list of {@link TaxRate}s with a list of {@link TaxRateDraft}s. - * The method serves as an implementation for tax rates syncing. The method takes in functions - * for building the required update actions (AddTaxRate, ReplaceTaxRate, RemoveTaxRate) for the required - * resource. - * - * @param oldTaxRates the old list of tax rates. - * @param newTaxRatesDrafts the new list of tax rates drafts. - * @return a list of tax rates update actions if the list of tax rates is not identical. - * Otherwise, if the tax rates are identical, an empty list is returned. - */ - @Nonnull - private static List> buildUpdateActions( - @Nonnull final List oldTaxRates, - @Nonnull final List newTaxRatesDrafts) { - - List newTaxRateDraftsCopy = new ArrayList<>(newTaxRatesDrafts); - - final List> updateActions = - buildRemoveOrReplaceTaxRateUpdateActions( - oldTaxRates, - newTaxRateDraftsCopy - ); - updateActions.addAll( - buildAddTaxRateUpdateActions( - oldTaxRates, - newTaxRateDraftsCopy - ) - ); - return updateActions; - } - - /** - * Checks if there are any tax rates which are not existing in the {@code newTaxRatesDrafts}. - * If there are, then "remove" tax rate update actions are built. - * Otherwise, if the tax rate still exists in the new draft, then compare the tax rate - * fields (amount, country, state, etc..), and add the computed actions to the list of update actions. - * - * @param oldTaxRates the list of old {@link TaxRate}s. - * @param newTaxRatesDrafts the list of new {@link TaxRateDraft}s. - * @return a list of tax rate update actions if there are tax rates that are not existing - * in the new draft. If the tax rate still exists in the new draft, then compare the fields, and add - * the computed actions to the list of update actions. - * Otherwise, if the tax rates are identical, an empty list is returned. - */ - @Nonnull - private static List> buildRemoveOrReplaceTaxRateUpdateActions( - @Nonnull final List oldTaxRates, - @Nonnull final List newTaxRatesDrafts) { - - return oldTaxRates - .stream() - .map(oldTaxRate -> newTaxRatesDrafts - .stream() - .filter(taxRateDraft -> Objects.equals(oldTaxRate.getCountry(), taxRateDraft.getCountry())) - .filter(taxRateDraft -> oldTaxRate.getState() == null - || (oldTaxRate.getState() != null && taxRateDraft.getState() == null) - || Objects.equals(oldTaxRate.getState(), taxRateDraft.getState())) - .findFirst() - .map(matchedTaxRateDraft -> { - if (!hasSameFields(oldTaxRate, matchedTaxRateDraft)) { - newTaxRatesDrafts.remove(matchedTaxRateDraft); - return singletonList(ReplaceTaxRate.of(oldTaxRate.getId(), matchedTaxRateDraft)); - } else { - return new ArrayList>(); - } - }) - .orElseGet(() -> singletonList(RemoveTaxRate.of(oldTaxRate.getId())))) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } - - /** - * Checks if there are any new tax rate drafts which are not existing in the {@code oldTaxRates}. - * If there are, then "add" tax rate update actions are built. - * Otherwise, if there are no new tax rates, then an empty list is returned. - * - * @param oldTaxRates the list of old {@link TaxRate}s. - * @param newTaxRateDrafts the list of new {@link TaxRateDraft}s. - * @return a list of tax rate update actions if there are new tax rate that should be added. - * Otherwise, if the tax rates are identical, an empty list is returned. - */ - @Nonnull - private static List> buildAddTaxRateUpdateActions( - @Nonnull final List oldTaxRates, - @Nonnull final List newTaxRateDrafts) { - - final Map taxRateDraftMap = oldTaxRates - .stream() - .collect(toMap(taxRate -> getTaxRateDraftMapKey(taxRate.getCountry(), taxRate.getState()), - taxRateDraft -> taxRateDraft)); - - return newTaxRateDrafts - .stream() - .filter(taxRateDraft -> taxRateDraft.getCountry() != null - && !taxRateDraftMap.containsKey( - getTaxRateDraftMapKey(taxRateDraft.getCountry(), taxRateDraft.getState()))) - .map(AddTaxRate::of) - .collect(toList()); - } - - @Nonnull - private static String getTaxRateDraftMapKey(final CountryCode countryCode, final String state) { - return StringUtils.isEmpty(state) - ? countryCode.toString() - : String.format("%s_%s", countryCode, state); - } - - private static boolean hasSameFields(@Nonnull final TaxRate oldTaxRate, @Nonnull final TaxRateDraft newTaxRate) { - return TaxRateDraftBuilder.of(oldTaxRate).build().equals(newTaxRate); + private TaxRatesUpdateActionUtils() {} + + /** + * Compares a list of {@link TaxRate}s with a list of {@link TaxRateDraft}s to returns a {@link + * List} of {@link UpdateAction}<{@link TaxCategory}>. If both lists have identical + * TaxRates, then no update actions are needed and hence an empty {@link List} is returned. + * + *

If the list of new {@link TaxRateDraft}s is empty, then remove actions are built for every + * existing tax rate in the {@code oldTaxRates} list. + * + *

Notes: The method will ignore/filter out: + * + *

    + *
  • the {@code null} tax rates drafts from the passed {@code newTaxRateDrafts}. + *
  • the duplicated (has same country code and state) tax rates drafts from the passed {@code + * newTaxRateDrafts} and will create update action only for the first tax rate draft in the + * {@code newTaxRateDrafts}. + *
+ * + * @param oldTaxRates the old list of tax rates. + * @param newTaxRatesDrafts the new list of tax rates drafts. + * @return a list of tax rates update actions if the list of tax rates are not identical. + * Otherwise, if the tax rates are identical, an empty list is returned. + */ + @Nonnull + static List> buildTaxRatesUpdateActions( + @Nonnull final List oldTaxRates, final List newTaxRatesDrafts) { + + if (newTaxRatesDrafts != null && !newTaxRatesDrafts.isEmpty()) { + return buildUpdateActions( + oldTaxRates, newTaxRatesDrafts.stream().filter(Objects::nonNull).collect(toList())); + } else { + return oldTaxRates.stream() + .map(TaxRate::getId) + .filter(Objects::nonNull) + .map(RemoveTaxRate::of) + .collect(Collectors.toList()); } + } + + /** + * Compares a list of {@link TaxRate}s with a list of {@link TaxRateDraft}s. The method serves as + * an implementation for tax rates syncing. The method takes in functions for building the + * required update actions (AddTaxRate, ReplaceTaxRate, RemoveTaxRate) for the required resource. + * + * @param oldTaxRates the old list of tax rates. + * @param newTaxRatesDrafts the new list of tax rates drafts. + * @return a list of tax rates update actions if the list of tax rates is not identical. + * Otherwise, if the tax rates are identical, an empty list is returned. + */ + @Nonnull + private static List> buildUpdateActions( + @Nonnull final List oldTaxRates, + @Nonnull final List newTaxRatesDrafts) { + + List newTaxRateDraftsCopy = new ArrayList<>(newTaxRatesDrafts); + + final List> updateActions = + buildRemoveOrReplaceTaxRateUpdateActions(oldTaxRates, newTaxRateDraftsCopy); + updateActions.addAll(buildAddTaxRateUpdateActions(oldTaxRates, newTaxRateDraftsCopy)); + return updateActions; + } + + /** + * Checks if there are any tax rates which are not existing in the {@code newTaxRatesDrafts}. If + * there are, then "remove" tax rate update actions are built. Otherwise, if the tax rate still + * exists in the new draft, then compare the tax rate fields (amount, country, state, etc..), and + * add the computed actions to the list of update actions. + * + * @param oldTaxRates the list of old {@link TaxRate}s. + * @param newTaxRatesDrafts the list of new {@link TaxRateDraft}s. + * @return a list of tax rate update actions if there are tax rates that are not existing in the + * new draft. If the tax rate still exists in the new draft, then compare the fields, and add + * the computed actions to the list of update actions. Otherwise, if the tax rates are + * identical, an empty list is returned. + */ + @Nonnull + private static List> buildRemoveOrReplaceTaxRateUpdateActions( + @Nonnull final List oldTaxRates, + @Nonnull final List newTaxRatesDrafts) { + + return oldTaxRates.stream() + .map( + oldTaxRate -> + newTaxRatesDrafts.stream() + .filter( + taxRateDraft -> + Objects.equals(oldTaxRate.getCountry(), taxRateDraft.getCountry())) + .filter( + taxRateDraft -> + oldTaxRate.getState() == null + || (oldTaxRate.getState() != null + && taxRateDraft.getState() == null) + || Objects.equals(oldTaxRate.getState(), taxRateDraft.getState())) + .findFirst() + .map( + matchedTaxRateDraft -> { + if (!hasSameFields(oldTaxRate, matchedTaxRateDraft)) { + newTaxRatesDrafts.remove(matchedTaxRateDraft); + return singletonList( + ReplaceTaxRate.of(oldTaxRate.getId(), matchedTaxRateDraft)); + } else { + return new ArrayList>(); + } + }) + .orElseGet(() -> singletonList(RemoveTaxRate.of(oldTaxRate.getId())))) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + /** + * Checks if there are any new tax rate drafts which are not existing in the {@code oldTaxRates}. + * If there are, then "add" tax rate update actions are built. Otherwise, if there are no new tax + * rates, then an empty list is returned. + * + * @param oldTaxRates the list of old {@link TaxRate}s. + * @param newTaxRateDrafts the list of new {@link TaxRateDraft}s. + * @return a list of tax rate update actions if there are new tax rate that should be added. + * Otherwise, if the tax rates are identical, an empty list is returned. + */ + @Nonnull + private static List> buildAddTaxRateUpdateActions( + @Nonnull final List oldTaxRates, + @Nonnull final List newTaxRateDrafts) { + + final Map taxRateDraftMap = + oldTaxRates.stream() + .collect( + toMap( + taxRate -> getTaxRateDraftMapKey(taxRate.getCountry(), taxRate.getState()), + taxRateDraft -> taxRateDraft)); + + return newTaxRateDrafts.stream() + .filter( + taxRateDraft -> + taxRateDraft.getCountry() != null + && !taxRateDraftMap.containsKey( + getTaxRateDraftMapKey(taxRateDraft.getCountry(), taxRateDraft.getState()))) + .map(AddTaxRate::of) + .collect(toList()); + } + + @Nonnull + private static String getTaxRateDraftMapKey(final CountryCode countryCode, final String state) { + return StringUtils.isEmpty(state) + ? countryCode.toString() + : String.format("%s_%s", countryCode, state); + } + + private static boolean hasSameFields( + @Nonnull final TaxRate oldTaxRate, @Nonnull final TaxRateDraft newTaxRate) { + return TaxRateDraftBuilder.of(oldTaxRate).build().equals(newTaxRate); + } } diff --git a/src/main/java/com/commercetools/sync/types/TypeSync.java b/src/main/java/com/commercetools/sync/types/TypeSync.java index b7057d611f..2140f2d788 100644 --- a/src/main/java/com/commercetools/sync/types/TypeSync.java +++ b/src/main/java/com/commercetools/sync/types/TypeSync.java @@ -1,5 +1,13 @@ package com.commercetools.sync.types; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static com.commercetools.sync.types.utils.TypeSyncUtils.buildActions; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.commons.BaseSync; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.services.TypeService; @@ -9,309 +17,320 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.TypeDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static com.commercetools.sync.types.utils.TypeSyncUtils.buildActions; -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; - -/** - * This class syncs type drafts with the corresponding types in the CTP project. - */ +/** This class syncs type drafts with the corresponding types in the CTP project. */ public class TypeSync extends BaseSync { - private static final String CTP_TYPE_FETCH_FAILED = "Failed to fetch existing types with keys: '%s'."; - private static final String CTP_TYPE_UPDATE_FAILED = "Failed to update type with key: '%s'. Reason: %s" ; - - private final TypeService typeService; - private final TypeBatchValidator batchValidator; - - public TypeSync(@Nonnull final TypeSyncOptions typeSyncOptions) { - this(typeSyncOptions, new TypeServiceImpl(typeSyncOptions)); + private static final String CTP_TYPE_FETCH_FAILED = + "Failed to fetch existing types with keys: '%s'."; + private static final String CTP_TYPE_UPDATE_FAILED = + "Failed to update type with key: '%s'. Reason: %s"; + + private final TypeService typeService; + private final TypeBatchValidator batchValidator; + + public TypeSync(@Nonnull final TypeSyncOptions typeSyncOptions) { + this(typeSyncOptions, new TypeServiceImpl(typeSyncOptions)); + } + + /** + * Takes a {@link TypeSyncOptions} and a {@link TypeService} instances to instantiate a new {@link + * TypeSync} instance that could be used to sync type drafts in the CTP project specified in the + * injected {@link TypeSyncOptions} instance. + * + *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and + * passed to. + * + * @param typeSyncOptions the container of all the options of the sync process including the CTP + * project client and/or configuration and other sync-specific options. + * @param typeService the type service which is responsible for fetching/caching the Types from + * the CTP project. + */ + TypeSync(@Nonnull final TypeSyncOptions typeSyncOptions, @Nonnull final TypeService typeService) { + super(new TypeSyncStatistics(), typeSyncOptions); + this.typeService = typeService; + this.batchValidator = new TypeBatchValidator(getSyncOptions(), getStatistics()); + } + + /** + * Iterates through the whole {@code typeDrafts} list and accumulates its valid drafts to batches. + * Every batch is then processed by {@link TypeSync#processBatch(List)}. + * + *

Inherited doc: {@inheritDoc} + * + * @param typeDrafts {@link List} of {@link TypeDraft}'s that would be synced into CTP project. + * @return {@link CompletionStage} with {@link TypeSyncStatistics} holding statistics of all sync + * processes performed by this sync instance. + */ + @Override + protected CompletionStage process(@Nonnull final List typeDrafts) { + final List> batches = batchElements(typeDrafts, syncOptions.getBatchSize()); + return syncBatches(batches, CompletableFuture.completedFuture(statistics)); + } + + /** + * This method first creates a new {@link Set} of valid {@link TypeDraft} elements. For more on + * the rules of validation, check: {@link + * TypeBatchValidator#validateAndCollectReferencedKeys(List)}. Using the resulting set of {@code + * validTypeDrafts}, the matching types in the target CTP project are fetched then the method + * {@link TypeSync#syncBatch(Set, Set)} is called to perform the sync (update or + * create requests accordingly) on the target project. + * + *

In case of error during of fetching of existing types, the error callback will be triggered. + * And the sync process would stop for the given batch. + * + * @param batch batch of drafts that need to be synced + * @return a {@link CompletionStage} containing an instance of {@link TypeSyncStatistics} which + * contains information about the result of syncing the supplied batch to the target project. + */ + @Override + protected CompletionStage processBatch(@Nonnull final List batch) { + + final ImmutablePair, Set> result = + batchValidator.validateAndCollectReferencedKeys(batch); + + final Set validDrafts = result.getLeft(); + if (validDrafts.isEmpty()) { + statistics.incrementProcessed(batch.size()); + return CompletableFuture.completedFuture(statistics); } - - /** - * Takes a {@link TypeSyncOptions} and a {@link TypeService} instances to instantiate - * a new {@link TypeSync} instance that could be used to sync type drafts in the CTP project specified - * in the injected {@link TypeSyncOptions} instance. - * - *

NOTE: This constructor is mainly to be used for tests where the services can be mocked and passed to. - * - * @param typeSyncOptions the container of all the options of the sync process including the CTP project - * client and/or configuration and other sync-specific options. - * @param typeService the type service which is responsible for fetching/caching the Types from the CTP - * project. - */ - TypeSync(@Nonnull final TypeSyncOptions typeSyncOptions, @Nonnull final TypeService typeService) { - super(new TypeSyncStatistics(), typeSyncOptions); - this.typeService = typeService; - this.batchValidator = new TypeBatchValidator(getSyncOptions(), getStatistics()); - } - - /** - * Iterates through the whole {@code typeDrafts} list and accumulates its valid drafts to batches. - * Every batch is then processed by {@link TypeSync#processBatch(List)}. - * - *

Inherited doc: - * {@inheritDoc} - * - * @param typeDrafts {@link List} of {@link TypeDraft}'s that would be synced into CTP project. - * @return {@link CompletionStage} with {@link TypeSyncStatistics} holding statistics of all sync - * processes performed by this sync instance. - */ - @Override - protected CompletionStage process(@Nonnull final List typeDrafts) { - final List> batches = batchElements(typeDrafts, syncOptions.getBatchSize()); - return syncBatches(batches, CompletableFuture.completedFuture(statistics)); - } - - /** - * This method first creates a new {@link Set} of valid {@link TypeDraft} elements. For more on the rules of - * validation, check: {@link TypeBatchValidator#validateAndCollectReferencedKeys(List)}. Using the resulting set of - * {@code validTypeDrafts}, the matching types in the target CTP project are fetched then the method - * {@link TypeSync#syncBatch(Set, Set)} is called to perform the sync (update or create - * requests accordingly) on the target project. - * - *

In case of error during of fetching of existing types, the error callback will be triggered. - * And the sync process would stop for the given batch. - *

- * - * @param batch batch of drafts that need to be synced - * @return a {@link CompletionStage} containing an instance - * of {@link TypeSyncStatistics} which contains information about the result of syncing the supplied - * batch to the target project. - */ - @Override - protected CompletionStage processBatch(@Nonnull final List batch) { - - final ImmutablePair, Set> result = - batchValidator.validateAndCollectReferencedKeys(batch); - - final Set validDrafts = result.getLeft(); - if (validDrafts.isEmpty()) { - statistics.incrementProcessed(batch.size()); - return CompletableFuture.completedFuture(statistics); - } - final Set validTypeKeys = result.getRight(); - - return typeService - .fetchMatchingTypesByKeys(validTypeKeys) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Set fetchedTypes = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(CTP_TYPE_FETCH_FAILED, validTypeKeys); - handleError(errorMessage, exception, validTypeKeys.size()); - return CompletableFuture.completedFuture(null); - } else { - return syncBatch(fetchedTypes, validDrafts); - } + final Set validTypeKeys = result.getRight(); + + return typeService + .fetchMatchingTypesByKeys(validTypeKeys) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Set fetchedTypes = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = format(CTP_TYPE_FETCH_FAILED, validTypeKeys); + handleError(errorMessage, exception, validTypeKeys.size()); + return CompletableFuture.completedFuture(null); + } else { + return syncBatch(fetchedTypes, validDrafts); + } }) - .thenApply(ignored -> { - statistics.incrementProcessed(batch.size()); - return statistics; + .thenApply( + ignored -> { + statistics.incrementProcessed(batch.size()); + return statistics; }); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed types to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - * @param exception The exception that called caused the failure, if any. - * @param failedTimes The number of times that the failed types counter is incremented. - */ - private void handleError(@Nonnull final String errorMessage, @Nonnull final Throwable exception, - final int failedTimes) { - SyncException syncException = new SyncException(errorMessage, exception); - syncOptions.applyErrorCallback(syncException); - statistics.incrementFailed(failedTimes); - } - - /** - * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this method calls the - * optional error callback specified in the {@code syncOptions} and updates the {@code statistics} instance by - * incrementing the total number of failed types to sync. - * - * @param errorMessage The error message describing the reason(s) of failure. - * @param exception The exception that called caused the failure, if any. - * @param failedTimes The number of times that the failed types counter is incremented. - * @param oldType existing type that could be updated. - * @param newType draft containing data that could differ from data in {@code oldType}. - * @param updateActions the update actions to update the {@link Type} with. - */ - private void handleError(@Nonnull final String errorMessage, @Nullable final Throwable exception, - final int failedTimes, @Nullable final Type oldType, @Nullable final TypeDraft newType, - @Nullable final List> updateActions) { - SyncException syncException = exception != null ? new SyncException(errorMessage, exception) + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed types to sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + * @param exception The exception that called caused the failure, if any. + * @param failedTimes The number of times that the failed types counter is incremented. + */ + private void handleError( + @Nonnull final String errorMessage, + @Nonnull final Throwable exception, + final int failedTimes) { + SyncException syncException = new SyncException(errorMessage, exception); + syncOptions.applyErrorCallback(syncException); + statistics.incrementFailed(failedTimes); + } + + /** + * Given a {@link String} {@code errorMessage} and a {@link Throwable} {@code exception}, this + * method calls the optional error callback specified in the {@code syncOptions} and updates the + * {@code statistics} instance by incrementing the total number of failed types to sync. + * + * @param errorMessage The error message describing the reason(s) of failure. + * @param exception The exception that called caused the failure, if any. + * @param failedTimes The number of times that the failed types counter is incremented. + * @param oldType existing type that could be updated. + * @param newType draft containing data that could differ from data in {@code oldType}. + * @param updateActions the update actions to update the {@link Type} with. + */ + private void handleError( + @Nonnull final String errorMessage, + @Nullable final Throwable exception, + final int failedTimes, + @Nullable final Type oldType, + @Nullable final TypeDraft newType, + @Nullable final List> updateActions) { + SyncException syncException = + exception != null + ? new SyncException(errorMessage, exception) : new SyncException(errorMessage); - syncOptions.applyErrorCallback(syncException, oldType, newType, updateActions); - statistics.incrementFailed(failedTimes); - } - - /** - * Given a set of type drafts, attempts to sync the drafts with the existing types in the CTP - * project. The type and the draft are considered to match if they have the same key. - * - * @param oldTypes old types. - * @param newTypes drafts that need to be synced. - * @return a {@link CompletionStage} which contains an empty result after execution of the update - */ - @Nonnull - private CompletionStage syncBatch( - @Nonnull final Set oldTypes, - @Nonnull final Set newTypes) { - - final Map oldTypeMap = oldTypes.stream().collect(toMap(Type::getKey, identity())); - - return CompletableFuture.allOf(newTypes - .stream() - .map(newType -> { - final Type oldType = oldTypeMap.get(newType.getKey()); - - return ofNullable(oldType) - .map(type -> buildActionsAndUpdate(oldType, newType)) - .orElseGet(() -> applyCallbackAndCreate(newType)); - }) - .map(CompletionStage::toCompletableFuture) - .toArray(CompletableFuture[]::new)); - } - - /** - * Given a type draft, this method applies the beforeCreateCallback and then issues a create request to the - * CTP project to create the corresponding Type. - * - * @param typeDraft the type draft to create the type from. - * @return a {@link CompletionStage} which contains an empty result after execution of the create. - */ - @Nonnull - private CompletionStage> applyCallbackAndCreate( - @Nonnull final TypeDraft typeDraft) { - - return syncOptions - .applyBeforeCreateCallback(typeDraft) - .map(draft -> typeService - .createType(draft) - .thenApply(typeOptional -> { - if (typeOptional.isPresent()) { - statistics.incrementCreated(); - } else { - statistics.incrementFailed(); - } - return typeOptional; + syncOptions.applyErrorCallback(syncException, oldType, newType, updateActions); + statistics.incrementFailed(failedTimes); + } + + /** + * Given a set of type drafts, attempts to sync the drafts with the existing types in the CTP + * project. The type and the draft are considered to match if they have the same key. + * + * @param oldTypes old types. + * @param newTypes drafts that need to be synced. + * @return a {@link CompletionStage} which contains an empty result after execution of the update + */ + @Nonnull + private CompletionStage syncBatch( + @Nonnull final Set oldTypes, @Nonnull final Set newTypes) { + + final Map oldTypeMap = oldTypes.stream().collect(toMap(Type::getKey, identity())); + + return CompletableFuture.allOf( + newTypes.stream() + .map( + newType -> { + final Type oldType = oldTypeMap.get(newType.getKey()); + + return ofNullable(oldType) + .map(type -> buildActionsAndUpdate(oldType, newType)) + .orElseGet(() -> applyCallbackAndCreate(newType)); }) - ) - .orElse(CompletableFuture.completedFuture(Optional.empty())); + .map(CompletionStage::toCompletableFuture) + .toArray(CompletableFuture[]::new)); + } + + /** + * Given a type draft, this method applies the beforeCreateCallback and then issues a create + * request to the CTP project to create the corresponding Type. + * + * @param typeDraft the type draft to create the type from. + * @return a {@link CompletionStage} which contains an empty result after execution of the create. + */ + @Nonnull + private CompletionStage> applyCallbackAndCreate( + @Nonnull final TypeDraft typeDraft) { + + return syncOptions + .applyBeforeCreateCallback(typeDraft) + .map( + draft -> + typeService + .createType(draft) + .thenApply( + typeOptional -> { + if (typeOptional.isPresent()) { + statistics.incrementCreated(); + } else { + statistics.incrementFailed(); + } + return typeOptional; + })) + .orElse(CompletableFuture.completedFuture(Optional.empty())); + } + + @Nonnull + private CompletionStage> buildActionsAndUpdate( + @Nonnull final Type oldType, @Nonnull final TypeDraft newType) { + + final List> updateActions = buildActions(oldType, newType, syncOptions); + + final List> updateActionsAfterCallback = + syncOptions.applyBeforeUpdateCallback(updateActions, newType, oldType); + + if (!updateActionsAfterCallback.isEmpty()) { + return updateType(oldType, newType, updateActionsAfterCallback); } - @Nonnull - private CompletionStage> buildActionsAndUpdate( - @Nonnull final Type oldType, - @Nonnull final TypeDraft newType) { - - final List> updateActions = buildActions(oldType, newType, syncOptions); - - final List> updateActionsAfterCallback = - syncOptions.applyBeforeUpdateCallback(updateActions, newType, oldType); - - if (!updateActionsAfterCallback.isEmpty()) { - return updateType(oldType, newType, updateActionsAfterCallback); - } - - return completedFuture(null); - } - - /** - * Given an existing {@link Type} and a new {@link TypeDraft}, the method calculates all the - * update actions required to synchronize the existing type to be the same as the new one. If there are - * update actions found, a request is made to CTP to update the existing type, otherwise it doesn't issue a - * request. - * - *

The {@code statistics} instance is updated accordingly to whether the CTP request was carried - * out successfully or not. If an exception was thrown on executing the request to CTP,the error handling method - * is called. - * - * @param oldType existing type that could be updated. - * @param newType draft containing data that could differ from data in {@code oldType}. - * @param updateActions the update actions to update the {@link Type} with. - * @return a {@link CompletionStage} which contains an empty result after execution of the update. - */ - @Nonnull - private CompletionStage> updateType( - @Nonnull final Type oldType, - @Nonnull final TypeDraft newType, - @Nonnull final List> updateActions) { - - return typeService - .updateType(oldType, updateActions) - .handle(ImmutablePair::new) - .thenCompose(updateResponse -> { - final Type updatedType = updateResponse.getKey(); - final Throwable sphereException = updateResponse.getValue(); - if (sphereException != null) { - return executeSupplierIfConcurrentModificationException( - sphereException, - () -> fetchAndUpdate(oldType, newType), - () -> { - final String errorMessage = - format(CTP_TYPE_UPDATE_FAILED, newType.getKey(), - sphereException.getMessage()); - handleError(errorMessage, sphereException, 1, oldType, newType, updateActions); - return CompletableFuture.completedFuture(Optional.empty()); - }); - } else { - statistics.incrementUpdated(); - return CompletableFuture.completedFuture(Optional.of(updatedType)); - } + return completedFuture(null); + } + + /** + * Given an existing {@link Type} and a new {@link TypeDraft}, the method calculates all the + * update actions required to synchronize the existing type to be the same as the new one. If + * there are update actions found, a request is made to CTP to update the existing type, otherwise + * it doesn't issue a request. + * + *

The {@code statistics} instance is updated accordingly to whether the CTP request was + * carried out successfully or not. If an exception was thrown on executing the request to CTP,the + * error handling method is called. + * + * @param oldType existing type that could be updated. + * @param newType draft containing data that could differ from data in {@code oldType}. + * @param updateActions the update actions to update the {@link Type} with. + * @return a {@link CompletionStage} which contains an empty result after execution of the update. + */ + @Nonnull + private CompletionStage> updateType( + @Nonnull final Type oldType, + @Nonnull final TypeDraft newType, + @Nonnull final List> updateActions) { + + return typeService + .updateType(oldType, updateActions) + .handle(ImmutablePair::new) + .thenCompose( + updateResponse -> { + final Type updatedType = updateResponse.getKey(); + final Throwable sphereException = updateResponse.getValue(); + if (sphereException != null) { + return executeSupplierIfConcurrentModificationException( + sphereException, + () -> fetchAndUpdate(oldType, newType), + () -> { + final String errorMessage = + format( + CTP_TYPE_UPDATE_FAILED, + newType.getKey(), + sphereException.getMessage()); + handleError( + errorMessage, sphereException, 1, oldType, newType, updateActions); + return CompletableFuture.completedFuture(Optional.empty()); + }); + } else { + statistics.incrementUpdated(); + return CompletableFuture.completedFuture(Optional.of(updatedType)); + } }); - } - - @Nonnull - private CompletionStage> fetchAndUpdate( - @Nonnull final Type oldType, - @Nonnull final TypeDraft newType) { - - final String key = oldType.getKey(); - return typeService - .fetchType(key) - .handle(ImmutablePair::new) - .thenCompose(fetchResponse -> { - final Optional fetchedTypeOptional = fetchResponse.getKey(); - final Throwable exception = fetchResponse.getValue(); - - if (exception != null) { - final String errorMessage = format(CTP_TYPE_UPDATE_FAILED, key, + } + + @Nonnull + private CompletionStage> fetchAndUpdate( + @Nonnull final Type oldType, @Nonnull final TypeDraft newType) { + + final String key = oldType.getKey(); + return typeService + .fetchType(key) + .handle(ImmutablePair::new) + .thenCompose( + fetchResponse -> { + final Optional fetchedTypeOptional = fetchResponse.getKey(); + final Throwable exception = fetchResponse.getValue(); + + if (exception != null) { + final String errorMessage = + format( + CTP_TYPE_UPDATE_FAILED, + key, "Failed to fetch from CTP while retrying after concurrency modification."); - handleError(errorMessage, exception, 1, oldType, newType, null); - return CompletableFuture.completedFuture(null); - } - - return fetchedTypeOptional - .map(fetchedType -> buildActionsAndUpdate(fetchedType, newType)) - .orElseGet(() -> { + handleError(errorMessage, exception, 1, oldType, newType, null); + return CompletableFuture.completedFuture(null); + } + + return fetchedTypeOptional + .map(fetchedType -> buildActionsAndUpdate(fetchedType, newType)) + .orElseGet( + () -> { final String errorMessage = - format(CTP_TYPE_UPDATE_FAILED, key, + format( + CTP_TYPE_UPDATE_FAILED, + key, "Not found when attempting to fetch while retrying " + "after concurrency modification."); handleError(errorMessage, null, 1, oldType, newType, null); return CompletableFuture.completedFuture(null); - }); + }); }); - } + } } diff --git a/src/main/java/com/commercetools/sync/types/TypeSyncOptions.java b/src/main/java/com/commercetools/sync/types/TypeSyncOptions.java index 9324ad62bb..8caaeacd0e 100644 --- a/src/main/java/com/commercetools/sync/types/TypeSyncOptions.java +++ b/src/main/java/com/commercetools/sync/types/TypeSyncOptions.java @@ -9,35 +9,35 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.TypeDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public final class TypeSyncOptions extends BaseSyncOptions { - TypeSyncOptions( - @Nonnull final SphereClient ctpClient, - @Nullable final QuadConsumer, Optional, - List>> errorCallback, - @Nullable final TriConsumer, Optional> - warningCallback, - final int batchSize, - @Nullable final TriFunction>, TypeDraft, - Type, List>> beforeUpdateCallback, - @Nullable final Function beforeCreateCallback, final long cacheSize - ) { - super( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } - + TypeSyncOptions( + @Nonnull final SphereClient ctpClient, + @Nullable + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallback, + @Nullable + final TriConsumer, Optional> warningCallback, + final int batchSize, + @Nullable + final TriFunction>, TypeDraft, Type, List>> + beforeUpdateCallback, + @Nullable final Function beforeCreateCallback, + final long cacheSize) { + super( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } } diff --git a/src/main/java/com/commercetools/sync/types/TypeSyncOptionsBuilder.java b/src/main/java/com/commercetools/sync/types/TypeSyncOptionsBuilder.java index 3d40a88cd3..d3f62ec2dd 100644 --- a/src/main/java/com/commercetools/sync/types/TypeSyncOptionsBuilder.java +++ b/src/main/java/com/commercetools/sync/types/TypeSyncOptionsBuilder.java @@ -4,55 +4,56 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.TypeDraft; - import javax.annotation.Nonnull; public final class TypeSyncOptionsBuilder - extends BaseSyncOptionsBuilder { - - public static final int BATCH_SIZE_DEFAULT = 50; - - private TypeSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { - this.ctpClient = ctpClient; - } - - /** - * Creates a new instance of {@link TypeSyncOptionsBuilder} given a {@link SphereClient} responsible for - * interaction with the target CTP project, with the default batch size ({@code BATCH_SIZE_DEFAULT} = 50). - * - * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the target CTP project. - * @return new instance of {@link TypeSyncOptionsBuilder} - */ - public static TypeSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { - return new TypeSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); - } - - /** - * Creates new instance of {@link TypeSyncOptions} enriched with all fields provided to {@code this} builder. - * - * @return new instance of {@link TypeSyncOptions} - */ - @Override - public TypeSyncOptions build() { - return new TypeSyncOptions( - ctpClient, - errorCallback, - warningCallback, - batchSize, - beforeUpdateCallback, - beforeCreateCallback, - cacheSize - ); - } - - /** - * Returns an instance of this class to be used in the superclass's generic methods. Please see the JavaDoc in the - * overridden method for further details. - * - * @return an instance of this class. - */ - @Override - protected TypeSyncOptionsBuilder getThis() { - return this; - } + extends BaseSyncOptionsBuilder { + + public static final int BATCH_SIZE_DEFAULT = 50; + + private TypeSyncOptionsBuilder(@Nonnull final SphereClient ctpClient) { + this.ctpClient = ctpClient; + } + + /** + * Creates a new instance of {@link TypeSyncOptionsBuilder} given a {@link SphereClient} + * responsible for interaction with the target CTP project, with the default batch size ({@code + * BATCH_SIZE_DEFAULT} = 50). + * + * @param ctpClient instance of the {@link SphereClient} responsible for interaction with the + * target CTP project. + * @return new instance of {@link TypeSyncOptionsBuilder} + */ + public static TypeSyncOptionsBuilder of(@Nonnull final SphereClient ctpClient) { + return new TypeSyncOptionsBuilder(ctpClient).batchSize(BATCH_SIZE_DEFAULT); + } + + /** + * Creates new instance of {@link TypeSyncOptions} enriched with all fields provided to {@code + * this} builder. + * + * @return new instance of {@link TypeSyncOptions} + */ + @Override + public TypeSyncOptions build() { + return new TypeSyncOptions( + ctpClient, + errorCallback, + warningCallback, + batchSize, + beforeUpdateCallback, + beforeCreateCallback, + cacheSize); + } + + /** + * Returns an instance of this class to be used in the superclass's generic methods. Please see + * the JavaDoc in the overridden method for further details. + * + * @return an instance of this class. + */ + @Override + protected TypeSyncOptionsBuilder getThis() { + return this; + } } diff --git a/src/main/java/com/commercetools/sync/types/helpers/TypeBatchValidator.java b/src/main/java/com/commercetools/sync/types/helpers/TypeBatchValidator.java index 81137449cb..c1d75093e8 100644 --- a/src/main/java/com/commercetools/sync/types/helpers/TypeBatchValidator.java +++ b/src/main/java/com/commercetools/sync/types/helpers/TypeBatchValidator.java @@ -1,76 +1,72 @@ package com.commercetools.sync.types.helpers; +import static java.lang.String.format; +import static java.util.stream.Collectors.toSet; +import static org.apache.commons.lang3.StringUtils.isBlank; + import com.commercetools.sync.commons.helpers.BaseBatchValidator; import com.commercetools.sync.types.TypeSyncOptions; import io.sphere.sdk.types.TypeDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Set; import java.util.stream.Collectors; - -import static java.lang.String.format; -import static java.util.stream.Collectors.toSet; -import static org.apache.commons.lang3.StringUtils.isBlank; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.ImmutablePair; public class TypeBatchValidator extends BaseBatchValidator { - static final String TYPE_DRAFT_KEY_NOT_SET = "TypeDraft with name: %s doesn't have a key. " - + "Please make sure all type drafts have keys."; - static final String TYPE_DRAFT_IS_NULL = "TypeDraft is null."; + static final String TYPE_DRAFT_KEY_NOT_SET = + "TypeDraft with name: %s doesn't have a key. " + + "Please make sure all type drafts have keys."; + static final String TYPE_DRAFT_IS_NULL = "TypeDraft is null."; - public TypeBatchValidator(@Nonnull final TypeSyncOptions syncOptions, - @Nonnull final TypeSyncStatistics syncStatistics) { - super(syncOptions, syncStatistics); - } - - /** - * Given the {@link List}<{@link TypeDraft}> of drafts this method attempts to validate - * drafts and return an {@link ImmutablePair}<{@link Set}<{@link TypeDraft}>,{@link Set}<{@link String} - * >> which contains the {@link Set} of valid drafts and valid type keys. - * - *

A valid type draft is one which satisfies the following conditions: - *

    - *
  1. It is not null
  2. - *
  3. It has a key which is not blank (null/empty)
  4. - *
- * - * @param typeDrafts the type drafts to validate and collect valid type keys. - * @return {@link ImmutablePair}<{@link Set}<{@link TypeDraft}>, - * {@link Set}<{@link String}>> which contains the {@link Set} of valid drafts and - * valid type keys. - */ - @Override - public ImmutablePair, Set> validateAndCollectReferencedKeys( - @Nonnull final List typeDrafts) { + public TypeBatchValidator( + @Nonnull final TypeSyncOptions syncOptions, + @Nonnull final TypeSyncStatistics syncStatistics) { + super(syncOptions, syncStatistics); + } - final Set validDrafts = typeDrafts - .stream() - .filter(this::isValidTypeDraft) - .collect(Collectors.toSet()); + /** + * Given the {@link List}<{@link TypeDraft}> of drafts this method attempts to validate + * drafts and return an {@link ImmutablePair}<{@link Set}<{@link TypeDraft}>,{@link + * Set}<{@link String} >> which contains the {@link Set} of valid drafts and valid type + * keys. + * + *

A valid type draft is one which satisfies the following conditions: + * + *

    + *
  1. It is not null + *
  2. It has a key which is not blank (null/empty) + *
+ * + * @param typeDrafts the type drafts to validate and collect valid type keys. + * @return {@link ImmutablePair}<{@link Set}<{@link TypeDraft}>, {@link Set}<{@link + * String}>> which contains the {@link Set} of valid drafts and valid type keys. + */ + @Override + public ImmutablePair, Set> validateAndCollectReferencedKeys( + @Nonnull final List typeDrafts) { - final Set validKeys = validDrafts - .stream() - .map(TypeDraft::getKey) - .collect(toSet()); + final Set validDrafts = + typeDrafts.stream().filter(this::isValidTypeDraft).collect(Collectors.toSet()); - return ImmutablePair.of(validDrafts, validKeys); - } + final Set validKeys = validDrafts.stream().map(TypeDraft::getKey).collect(toSet()); - private boolean isValidTypeDraft( - @Nullable final TypeDraft typeDraft) { + return ImmutablePair.of(validDrafts, validKeys); + } - if (typeDraft == null) { - handleError(TYPE_DRAFT_IS_NULL); - } else if (isBlank(typeDraft.getKey())) { - handleError(format(TYPE_DRAFT_KEY_NOT_SET, typeDraft.getName())); - } else { - return true; - } + private boolean isValidTypeDraft(@Nullable final TypeDraft typeDraft) { - return false; + if (typeDraft == null) { + handleError(TYPE_DRAFT_IS_NULL); + } else if (isBlank(typeDraft.getKey())) { + handleError(format(TYPE_DRAFT_KEY_NOT_SET, typeDraft.getName())); + } else { + return true; } + + return false; + } } diff --git a/src/main/java/com/commercetools/sync/types/helpers/TypeSyncStatistics.java b/src/main/java/com/commercetools/sync/types/helpers/TypeSyncStatistics.java index 4e19b0e5ec..cc13f14e16 100644 --- a/src/main/java/com/commercetools/sync/types/helpers/TypeSyncStatistics.java +++ b/src/main/java/com/commercetools/sync/types/helpers/TypeSyncStatistics.java @@ -3,15 +3,15 @@ import com.commercetools.sync.commons.helpers.BaseSyncStatistics; public class TypeSyncStatistics extends BaseSyncStatistics { - /** - * Builds a summary of the type sync statistics instance that looks like the following example: - * - *

"Summary: 2 types were processed in total (0 created, 0 updated and 0 failed to sync)." - * - * @return a summary message of the types sync statistics instance. - */ - @Override - public String getReportMessage() { - return getDefaultReportMessageForResource("types"); - } + /** + * Builds a summary of the type sync statistics instance that looks like the following example: + * + *

"Summary: 2 types were processed in total (0 created, 0 updated and 0 failed to sync)." + * + * @return a summary message of the types sync statistics instance. + */ + @Override + public String getReportMessage() { + return getDefaultReportMessageForResource("types"); + } } diff --git a/src/main/java/com/commercetools/sync/types/utils/FieldDefinitionUpdateActionUtils.java b/src/main/java/com/commercetools/sync/types/utils/FieldDefinitionUpdateActionUtils.java index 81e6dcb5d2..89c1f74fed 100644 --- a/src/main/java/com/commercetools/sync/types/utils/FieldDefinitionUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/types/utils/FieldDefinitionUpdateActionUtils.java @@ -1,5 +1,10 @@ package com.commercetools.sync.types.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static com.commercetools.sync.types.utils.LocalizedEnumValueUpdateActionUtils.buildLocalizedEnumValuesUpdateActions; +import static com.commercetools.sync.types.utils.PlainEnumValueUpdateActionUtils.buildEnumValuesUpdateActions; + import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.EnumValue; @@ -12,182 +17,177 @@ import io.sphere.sdk.types.SetFieldType; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.commands.updateactions.ChangeFieldDefinitionLabel; - -import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; import java.util.Optional; +import javax.annotation.Nonnull; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static com.commercetools.sync.types.utils.LocalizedEnumValueUpdateActionUtils.buildLocalizedEnumValuesUpdateActions; -import static com.commercetools.sync.types.utils.PlainEnumValueUpdateActionUtils.buildEnumValuesUpdateActions; - -/** - * This class is only meant for the internal use of the commercetools-sync-java library. - */ +/** This class is only meant for the internal use of the commercetools-sync-java library. */ final class FieldDefinitionUpdateActionUtils { - /** - * Compares all the fields of an old {@link FieldDefinition} with a new {@link FieldDefinition} and returns - * a list of {@link UpdateAction}<{@link Type}> as a result. If both the {@link FieldDefinition} - * and the {@link FieldDefinition} have identical fields, then no update action is needed and hence an - * empty {@link List} is returned. - * - * @param oldFieldDefinition the old field definition which should be updated. - * @param newFieldDefinition the new field definition where we get the new fields. - * @return A list with the update actions or an empty list if the field definition fields are identical. - * - * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. - */ - @Nonnull - static List> buildActions( - @Nonnull final FieldDefinition oldFieldDefinition, - @Nonnull final FieldDefinition newFieldDefinition) { - - final List> updateActions = - filterEmptyOptionals(buildChangeLabelUpdateAction(oldFieldDefinition, newFieldDefinition)); - - updateActions.addAll(buildEnumUpdateActions(oldFieldDefinition, newFieldDefinition)); - - return updateActions; - } - - /** - * Checks if both the supplied {@code oldFieldDefinition} and {@code newFieldDefinition} have an - * {@link FieldType} that is either an {@link EnumFieldType} or a {@link LocalizedEnumFieldType} or - * a {@link SetFieldType} with a subtype that is either an {@link EnumFieldType} or a - * {@link LocalizedEnumFieldType}. - * - *

The method compares all the {@link EnumValue} and {@link LocalizedEnumValue} values of the - * {@link FieldType} and the {@link FieldDefinition} types and returns a list of - * {@link UpdateAction}<{@link Type}> as a result. If both the {@code oldFieldDefinition} and - * {@code newFieldDefinition} have identical enum values, then no update action is needed and hence an empty - * {@link List} is returned.

- * - *

Note: This method expects the supplied {@code oldFieldDefinition} and {@code newFieldDefinition} - * to have the same {@link FieldType}. Otherwise, the behaviour is not guaranteed.

- * - * @param oldFieldDefinition the field definition which should be updated. - * @param newFieldDefinition the new field definition draft where we get the new fields. - * @return A list with the update actions or an empty list if the field definition enums are identical. - * - * @throws DuplicateKeyException in case there are enum values with duplicate keys. - */ - @Nonnull - static List> buildEnumUpdateActions( - @Nonnull final FieldDefinition oldFieldDefinition, - @Nonnull final FieldDefinition newFieldDefinition) { - - final FieldType oldFieldDefinitionType = oldFieldDefinition.getType(); - final FieldType newFieldDefinitionType = newFieldDefinition.getType(); - - return getEnumFieldType(oldFieldDefinitionType) - .map(oldEnumFieldType -> + /** + * Compares all the fields of an old {@link FieldDefinition} with a new {@link FieldDefinition} + * and returns a list of {@link UpdateAction}<{@link Type}> as a result. If both the {@link + * FieldDefinition} and the {@link FieldDefinition} have identical fields, then no update action + * is needed and hence an empty {@link List} is returned. + * + * @param oldFieldDefinition the old field definition which should be updated. + * @param newFieldDefinition the new field definition where we get the new fields. + * @return A list with the update actions or an empty list if the field definition fields are + * identical. + * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. + */ + @Nonnull + static List> buildActions( + @Nonnull final FieldDefinition oldFieldDefinition, + @Nonnull final FieldDefinition newFieldDefinition) { + + final List> updateActions = + filterEmptyOptionals(buildChangeLabelUpdateAction(oldFieldDefinition, newFieldDefinition)); + + updateActions.addAll(buildEnumUpdateActions(oldFieldDefinition, newFieldDefinition)); + + return updateActions; + } + + /** + * Checks if both the supplied {@code oldFieldDefinition} and {@code newFieldDefinition} have an + * {@link FieldType} that is either an {@link EnumFieldType} or a {@link LocalizedEnumFieldType} + * or a {@link SetFieldType} with a subtype that is either an {@link EnumFieldType} or a {@link + * LocalizedEnumFieldType}. + * + *

The method compares all the {@link EnumValue} and {@link LocalizedEnumValue} values of the + * {@link FieldType} and the {@link FieldDefinition} types and returns a list of {@link + * UpdateAction}<{@link Type}> as a result. If both the {@code oldFieldDefinition} and + * {@code newFieldDefinition} have identical enum values, then no update action is needed and + * hence an empty {@link List} is returned. + * + *

Note: This method expects the supplied {@code oldFieldDefinition} and {@code + * newFieldDefinition} to have the same {@link FieldType}. Otherwise, the behaviour is not + * guaranteed. + * + * @param oldFieldDefinition the field definition which should be updated. + * @param newFieldDefinition the new field definition draft where we get the new fields. + * @return A list with the update actions or an empty list if the field definition enums are + * identical. + * @throws DuplicateKeyException in case there are enum values with duplicate keys. + */ + @Nonnull + static List> buildEnumUpdateActions( + @Nonnull final FieldDefinition oldFieldDefinition, + @Nonnull final FieldDefinition newFieldDefinition) { + + final FieldType oldFieldDefinitionType = oldFieldDefinition.getType(); + final FieldType newFieldDefinitionType = newFieldDefinition.getType(); + + return getEnumFieldType(oldFieldDefinitionType) + .map( + oldEnumFieldType -> getEnumFieldType(newFieldDefinitionType) - .map(newEnumFieldType -> - buildEnumValuesUpdateActions(oldFieldDefinition.getName(), - oldEnumFieldType.getValues(), - newEnumFieldType.getValues()) - ) - .orElseGet(Collections::emptyList) - ) - .orElseGet(() -> + .map( + newEnumFieldType -> + buildEnumValuesUpdateActions( + oldFieldDefinition.getName(), + oldEnumFieldType.getValues(), + newEnumFieldType.getValues())) + .orElseGet(Collections::emptyList)) + .orElseGet( + () -> getLocalizedEnumFieldType(oldFieldDefinitionType) - .map(oldLocalizedEnumFieldType -> - getLocalizedEnumFieldType(newFieldDefinitionType) - .map(newLocalizedEnumFieldType -> - - buildLocalizedEnumValuesUpdateActions(oldFieldDefinition.getName(), - oldLocalizedEnumFieldType.getValues(), - newLocalizedEnumFieldType.getValues()) - - ) - .orElseGet(Collections::emptyList) - ) - .orElseGet(Collections::emptyList) - ); + .map( + oldLocalizedEnumFieldType -> + getLocalizedEnumFieldType(newFieldDefinitionType) + .map( + newLocalizedEnumFieldType -> + buildLocalizedEnumValuesUpdateActions( + oldFieldDefinition.getName(), + oldLocalizedEnumFieldType.getValues(), + newLocalizedEnumFieldType.getValues())) + .orElseGet(Collections::emptyList)) + .orElseGet(Collections::emptyList)); + } + + /** + * Returns an optional containing the field type if is an {@link EnumFieldType} or if the {@link + * FieldType} is a {@link SetFieldType} with an {@link EnumFieldType} as a subtype, it returns + * this subtype in the optional. Otherwise, an empty optional. + * + * @param fieldType the field type. + * @return an optional containing the field type if is an {@link EnumFieldType} or if the {@link + * FieldType} is a {@link SetFieldType} with an {@link EnumFieldType} as a subtype, it returns + * this subtype in the optional. Otherwise, an empty optional. + */ + private static Optional getEnumFieldType(@Nonnull final FieldType fieldType) { + + if (fieldType instanceof EnumFieldType) { + return Optional.of((EnumFieldType) fieldType); } - /** - * Returns an optional containing the field type if is an {@link EnumFieldType} or if the - * {@link FieldType} is a {@link SetFieldType} with an {@link EnumFieldType} as a subtype, it returns - * this subtype in the optional. Otherwise, an empty optional. - * - * @param fieldType the field type. - * @return an optional containing the field type if is an {@link EnumFieldType} or if the - * {@link FieldType} is a {@link SetFieldType} with an {@link EnumFieldType} as a subtype, it - * returns this subtype in the optional. Otherwise, an empty optional. - */ - private static Optional getEnumFieldType( - @Nonnull final FieldType fieldType) { - - if (fieldType instanceof EnumFieldType) { - return Optional.of((EnumFieldType) fieldType); - } - - if (fieldType instanceof SetFieldType) { - - final SetFieldType setFieldType = (SetFieldType) fieldType; - final FieldType subType = setFieldType.getElementType(); - - if (subType instanceof EnumFieldType) { - return Optional.of((EnumFieldType) subType); - } - } - - return Optional.empty(); - } + if (fieldType instanceof SetFieldType) { + + final SetFieldType setFieldType = (SetFieldType) fieldType; + final FieldType subType = setFieldType.getElementType(); - /** - * Returns an optional containing the field type if is an {@link LocalizedEnumFieldType} or if the - * {@link FieldType} is a {@link SetFieldType} with an {@link LocalizedEnumFieldType} as a subtype, it - * returns this subtype in the optional. Otherwise, an empty optional. - * - * @param fieldType the field type. - * @return an optional containing the field type if is an {@link LocalizedEnumFieldType} or if the - * {@link FieldType} is a {@link SetFieldType} with an {@link LocalizedEnumFieldType} as a - * subtype, it returns this subtype in the optional. Otherwise, an empty optional. - */ - private static Optional getLocalizedEnumFieldType( - @Nonnull final FieldType fieldType) { - - if (fieldType instanceof LocalizedEnumFieldType) { - return Optional.of((LocalizedEnumFieldType) fieldType); - } - - if (fieldType instanceof SetFieldType) { - final SetFieldType setFieldType = (SetFieldType) fieldType; - final FieldType subType = setFieldType.getElementType(); - - if (subType instanceof LocalizedEnumFieldType) { - return Optional.of((LocalizedEnumFieldType) subType); - } - } - - return Optional.empty(); + if (subType instanceof EnumFieldType) { + return Optional.of((EnumFieldType) subType); + } } - /** - * Compares the {@link LocalizedString} labels of old {@link FieldDefinition} with new - * {@link FieldDefinition} and returns an {@link UpdateAction}<{@link Type}> as a result in - * an {@link Optional}. If both the {@link FieldDefinition} and the {@link FieldDefinition} have the - * same label, then no update action is needed and hence an empty {@link Optional} is returned. - * - * @param oldFieldDefinition the old field definition which should be updated. - * @param newFieldDefinition the new field definition draft where we get the new label. - * @return A filled optional with the update action or an empty optional if the labels are identical. - */ - @Nonnull - static Optional> buildChangeLabelUpdateAction( - @Nonnull final FieldDefinition oldFieldDefinition, - @Nonnull final FieldDefinition newFieldDefinition) { - - return buildUpdateAction(oldFieldDefinition.getLabel(), newFieldDefinition.getLabel(), - () -> ChangeFieldDefinitionLabel.of(oldFieldDefinition.getName(), newFieldDefinition.getLabel()) - ); + return Optional.empty(); + } + + /** + * Returns an optional containing the field type if is an {@link LocalizedEnumFieldType} or if the + * {@link FieldType} is a {@link SetFieldType} with an {@link LocalizedEnumFieldType} as a + * subtype, it returns this subtype in the optional. Otherwise, an empty optional. + * + * @param fieldType the field type. + * @return an optional containing the field type if is an {@link LocalizedEnumFieldType} or if the + * {@link FieldType} is a {@link SetFieldType} with an {@link LocalizedEnumFieldType} as a + * subtype, it returns this subtype in the optional. Otherwise, an empty optional. + */ + private static Optional getLocalizedEnumFieldType( + @Nonnull final FieldType fieldType) { + + if (fieldType instanceof LocalizedEnumFieldType) { + return Optional.of((LocalizedEnumFieldType) fieldType); } - private FieldDefinitionUpdateActionUtils() { + if (fieldType instanceof SetFieldType) { + final SetFieldType setFieldType = (SetFieldType) fieldType; + final FieldType subType = setFieldType.getElementType(); + + if (subType instanceof LocalizedEnumFieldType) { + return Optional.of((LocalizedEnumFieldType) subType); + } } + + return Optional.empty(); + } + + /** + * Compares the {@link LocalizedString} labels of old {@link FieldDefinition} with new {@link + * FieldDefinition} and returns an {@link UpdateAction}<{@link Type}> as a result in an + * {@link Optional}. If both the {@link FieldDefinition} and the {@link FieldDefinition} have the + * same label, then no update action is needed and hence an empty {@link Optional} is returned. + * + * @param oldFieldDefinition the old field definition which should be updated. + * @param newFieldDefinition the new field definition draft where we get the new label. + * @return A filled optional with the update action or an empty optional if the labels are + * identical. + */ + @Nonnull + static Optional> buildChangeLabelUpdateAction( + @Nonnull final FieldDefinition oldFieldDefinition, + @Nonnull final FieldDefinition newFieldDefinition) { + + return buildUpdateAction( + oldFieldDefinition.getLabel(), + newFieldDefinition.getLabel(), + () -> + ChangeFieldDefinitionLabel.of( + oldFieldDefinition.getName(), newFieldDefinition.getLabel())); + } + + private FieldDefinitionUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/types/utils/FieldDefinitionsUpdateActionUtils.java b/src/main/java/com/commercetools/sync/types/utils/FieldDefinitionsUpdateActionUtils.java index 2f352c539b..bba7db28b0 100644 --- a/src/main/java/com/commercetools/sync/types/utils/FieldDefinitionsUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/types/utils/FieldDefinitionsUpdateActionUtils.java @@ -1,5 +1,13 @@ package com.commercetools.sync.types.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.types.utils.FieldDefinitionUpdateActionUtils.buildActions; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import com.commercetools.sync.commons.exceptions.DuplicateNameException; @@ -10,9 +18,6 @@ import io.sphere.sdk.types.commands.updateactions.AddFieldDefinition; import io.sphere.sdk.types.commands.updateactions.ChangeFieldDefinitionOrder; import io.sphere.sdk.types.commands.updateactions.RemoveFieldDefinition; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -22,255 +27,246 @@ import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.types.utils.FieldDefinitionUpdateActionUtils.buildActions; -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; - -/** - * This class is only meant for the internal use of the commercetools-sync-java library. - */ +/** This class is only meant for the internal use of the commercetools-sync-java library. */ final class FieldDefinitionsUpdateActionUtils { - /** - * Compares a list of {@link FieldDefinition}s with a list of {@link FieldDefinition}s. - * The method serves as an implementation for field definitions syncing and building the required - * update actions (AddFieldDefinition, RemoveFieldDefinition, ChangeFieldDefinitionOrder) - * and 1-1 update actions on field definitions (e.g. changeFieldDefinitionLabel, etc..) for the required - * resource. - * - *

If the list of new {@link FieldDefinition}s is {@code null}, then remove actions are built for - * every existing field definition in the {@code oldFieldDefinitions} list. - * - *

Note: The method will ignore/filter out {@code null} field definitions from the passed - * {@code newFieldDefinitions}.

- * - * @param oldFieldDefinitions the old list of field definitions. - * @param newFieldDefinitions the new list of field definitions. - * @return a list of field definitions update actions if the list of field definitions is not identical. - * Otherwise, if the field definitions are identical, an empty list is returned. - * @throws BuildUpdateActionException in case there are field definitions with duplicate names or enums - * duplicate keys. - */ - @Nonnull - static List> buildFieldDefinitionsUpdateActions( - @Nonnull final List oldFieldDefinitions, - @Nullable final List newFieldDefinitions) - throws BuildUpdateActionException { + /** + * Compares a list of {@link FieldDefinition}s with a list of {@link FieldDefinition}s. The method + * serves as an implementation for field definitions syncing and building the required update + * actions (AddFieldDefinition, RemoveFieldDefinition, ChangeFieldDefinitionOrder) and 1-1 update + * actions on field definitions (e.g. changeFieldDefinitionLabel, etc..) for the required + * resource. + * + *

If the list of new {@link FieldDefinition}s is {@code null}, then remove actions are built + * for every existing field definition in the {@code oldFieldDefinitions} list. + * + *

Note: The method will ignore/filter out {@code null} field definitions from the passed + * {@code newFieldDefinitions}. + * + * @param oldFieldDefinitions the old list of field definitions. + * @param newFieldDefinitions the new list of field definitions. + * @return a list of field definitions update actions if the list of field definitions is not + * identical. Otherwise, if the field definitions are identical, an empty list is returned. + * @throws BuildUpdateActionException in case there are field definitions with duplicate names or + * enums duplicate keys. + */ + @Nonnull + static List> buildFieldDefinitionsUpdateActions( + @Nonnull final List oldFieldDefinitions, + @Nullable final List newFieldDefinitions) + throws BuildUpdateActionException { - if (newFieldDefinitions != null) { - return buildUpdateActions( - oldFieldDefinitions, - newFieldDefinitions.stream().filter(Objects::nonNull).collect(toList())); - } else { - return oldFieldDefinitions - .stream() - .map(FieldDefinition::getName) - .map(RemoveFieldDefinition::of) - .collect(Collectors.toList()); - } + if (newFieldDefinitions != null) { + return buildUpdateActions( + oldFieldDefinitions, + newFieldDefinitions.stream().filter(Objects::nonNull).collect(toList())); + } else { + return oldFieldDefinitions.stream() + .map(FieldDefinition::getName) + .map(RemoveFieldDefinition::of) + .collect(Collectors.toList()); } + } - /** - * Compares a list of {@link FieldDefinition}s with a list of {@link FieldDefinition}s. - * The method serves as an implementation for field definitions syncing and building the required - * update actions (AddFieldDefinition, RemoveFieldDefinition, ChangeFieldDefinitionOrder) - * and 1-1 update actions on field definitions (e.g. changeFieldDefinitionLabel, etc..) for the required - * resource. - * - * @param oldFieldDefinitions the old list of field definitions. - * @param newFieldDefinitions the new list of field definitions drafts. - * @return a list of field definitions update actions if the list of field definitions is not identical. - * Otherwise, if the field definitions are identical, an empty list is returned. - * @throws BuildUpdateActionException in case there are field definitions with duplicate names or enums - * duplicate keys. - */ - @Nonnull - private static List> buildUpdateActions( - @Nonnull final List oldFieldDefinitions, - @Nonnull final List newFieldDefinitions) - throws BuildUpdateActionException { + /** + * Compares a list of {@link FieldDefinition}s with a list of {@link FieldDefinition}s. The method + * serves as an implementation for field definitions syncing and building the required update + * actions (AddFieldDefinition, RemoveFieldDefinition, ChangeFieldDefinitionOrder) and 1-1 update + * actions on field definitions (e.g. changeFieldDefinitionLabel, etc..) for the required + * resource. + * + * @param oldFieldDefinitions the old list of field definitions. + * @param newFieldDefinitions the new list of field definitions drafts. + * @return a list of field definitions update actions if the list of field definitions is not + * identical. Otherwise, if the field definitions are identical, an empty list is returned. + * @throws BuildUpdateActionException in case there are field definitions with duplicate names or + * enums duplicate keys. + */ + @Nonnull + private static List> buildUpdateActions( + @Nonnull final List oldFieldDefinitions, + @Nonnull final List newFieldDefinitions) + throws BuildUpdateActionException { - try { + try { - final List> updateActions = - buildRemoveFieldDefinitionOrFieldDefinitionUpdateActions(oldFieldDefinitions, newFieldDefinitions); + final List> updateActions = + buildRemoveFieldDefinitionOrFieldDefinitionUpdateActions( + oldFieldDefinitions, newFieldDefinitions); - updateActions.addAll(buildAddFieldDefinitionUpdateActions(oldFieldDefinitions, newFieldDefinitions)); + updateActions.addAll( + buildAddFieldDefinitionUpdateActions(oldFieldDefinitions, newFieldDefinitions)); - buildChangeFieldDefinitionOrderUpdateAction(oldFieldDefinitions, newFieldDefinitions) - .ifPresent(updateActions::add); + buildChangeFieldDefinitionOrderUpdateAction(oldFieldDefinitions, newFieldDefinitions) + .ifPresent(updateActions::add); - return updateActions; + return updateActions; - } catch (final DuplicateNameException | DuplicateKeyException exception) { - throw new BuildUpdateActionException(exception); - } + } catch (final DuplicateNameException | DuplicateKeyException exception) { + throw new BuildUpdateActionException(exception); } + } - /** - * Checks if there are any field definitions which are not existing in the - * {@code newFieldDefinitions}. If there are, then "remove" field definition update actions are - * built. - * Otherwise, if the field definition still exists in the new field definition, then compare the field definition - * fields (label, etc..), and add the computed actions to the list of update actions. - * - *

Note: If the field type field is different, the old field definition is removed and the new field - * definition is added with the new field type. - * - * @param oldFieldDefinitions the list of old {@link FieldDefinition}s. - * @param newFieldDefinitions the list of new {@link FieldDefinition}s. - * @return a list of field definition update actions if there are field that are not existing - * in the new draft. If the field definition still exists in the new draft, then compare the field - * definition fields (name, label, etc..), and add the computed actions to the list of update actions. - * Otherwise, if the field definitions are identical, an empty optional is returned. - * @throws DuplicateNameException in case there are field definitions drafts with duplicate names. - * @throws DuplicateKeyException in case there are enum values with duplicate keys. - */ - @Nonnull - private static List> buildRemoveFieldDefinitionOrFieldDefinitionUpdateActions( - @Nonnull final List oldFieldDefinitions, - @Nonnull final List newFieldDefinitions) { + /** + * Checks if there are any field definitions which are not existing in the {@code + * newFieldDefinitions}. If there are, then "remove" field definition update actions are built. + * Otherwise, if the field definition still exists in the new field definition, then compare the + * field definition fields (label, etc..), and add the computed actions to the list of update + * actions. + * + *

Note: If the field type field is different, the old field definition is removed and the new + * field definition is added with the new field type. + * + * @param oldFieldDefinitions the list of old {@link FieldDefinition}s. + * @param newFieldDefinitions the list of new {@link FieldDefinition}s. + * @return a list of field definition update actions if there are field that are not existing in + * the new draft. If the field definition still exists in the new draft, then compare the + * field definition fields (name, label, etc..), and add the computed actions to the list of + * update actions. Otherwise, if the field definitions are identical, an empty optional is + * returned. + * @throws DuplicateNameException in case there are field definitions drafts with duplicate names. + * @throws DuplicateKeyException in case there are enum values with duplicate keys. + */ + @Nonnull + private static List> buildRemoveFieldDefinitionOrFieldDefinitionUpdateActions( + @Nonnull final List oldFieldDefinitions, + @Nonnull final List newFieldDefinitions) { - final Map newFieldDefinitionsNameMap = - newFieldDefinitions - .stream().collect( - toMap(FieldDefinition::getName, fieldDefinition -> fieldDefinition, + final Map newFieldDefinitionsNameMap = + newFieldDefinitions.stream() + .collect( + toMap( + FieldDefinition::getName, + fieldDefinition -> fieldDefinition, (fieldDefinitionA, fieldDefinitionB) -> { - throw new DuplicateNameException(format("Field definitions have duplicated names. " - + "Duplicated field definition name: '%s'. Field definitions names are " - + "expected to be unique inside their type.", fieldDefinitionA.getName())); + throw new DuplicateNameException( + format( + "Field definitions have duplicated names. " + + "Duplicated field definition name: '%s'. Field definitions names are " + + "expected to be unique inside their type.", + fieldDefinitionA.getName())); })); - return oldFieldDefinitions - .stream() - .map(oldFieldDefinition -> { - final String oldFieldDefinitionName = oldFieldDefinition.getName(); - final FieldDefinition matchingNewFieldDefinition = - newFieldDefinitionsNameMap.get(oldFieldDefinitionName); + return oldFieldDefinitions.stream() + .map( + oldFieldDefinition -> { + final String oldFieldDefinitionName = oldFieldDefinition.getName(); + final FieldDefinition matchingNewFieldDefinition = + newFieldDefinitionsNameMap.get(oldFieldDefinitionName); - return ofNullable(matchingNewFieldDefinition) - .map(newFieldDefinition -> { + return ofNullable(matchingNewFieldDefinition) + .map( + newFieldDefinition -> { if (newFieldDefinition.getType() != null) { - // field type is required so if null we let CTP to throw exception - if (haveSameFieldType(oldFieldDefinition.getType(), newFieldDefinition.getType())) { - return buildActions(oldFieldDefinition, newFieldDefinition); - } else { - // since there is no way to change a field type on CTP, - // we remove the field definition and add a new one with a new field type - return Arrays.asList( - RemoveFieldDefinition.of(oldFieldDefinitionName), - AddFieldDefinition.of(newFieldDefinition) - ); - } + // field type is required so if null we let CTP to throw exception + if (haveSameFieldType( + oldFieldDefinition.getType(), newFieldDefinition.getType())) { + return buildActions(oldFieldDefinition, newFieldDefinition); + } else { + // since there is no way to change a field type on CTP, + // we remove the field definition and add a new one with a new field + // type + return Arrays.asList( + RemoveFieldDefinition.of(oldFieldDefinitionName), + AddFieldDefinition.of(newFieldDefinition)); + } } else { - return new ArrayList>(); + return new ArrayList>(); } - }) - .orElseGet(() -> singletonList(RemoveFieldDefinition.of(oldFieldDefinitionName))); + }) + .orElseGet(() -> singletonList(RemoveFieldDefinition.of(oldFieldDefinitionName))); }) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } - /** - * Compares the field types of the {@code fieldTypeA} and the {@code fieldTypeB}. - * - * @param fieldTypeA the first type to compare. - * @param fieldTypeB the second field type to compare. - * @return true if both field types equal, false otherwise. - */ - private static boolean haveSameFieldType( - @Nonnull final FieldType fieldTypeA, - @Nonnull final FieldType fieldTypeB) { + /** + * Compares the field types of the {@code fieldTypeA} and the {@code fieldTypeB}. + * + * @param fieldTypeA the first type to compare. + * @param fieldTypeB the second field type to compare. + * @return true if both field types equal, false otherwise. + */ + private static boolean haveSameFieldType( + @Nonnull final FieldType fieldTypeA, @Nonnull final FieldType fieldTypeB) { - return fieldTypeA.getClass() == fieldTypeB.getClass(); - } + return fieldTypeA.getClass() == fieldTypeB.getClass(); + } - /** - * Compares the order of a list of old {@link FieldDefinition}s and a list of new - * {@link FieldDefinition}s. - * If there is a change in order, then a change field definition order - * (with the new order) is built. If there are no changes in order an empty optional is returned. - * - * @param oldFieldDefinitions the list of old {@link FieldDefinition}s - * @param newFieldDefinitions the list of new {@link FieldDefinition}s - * @return a list of field definition update actions if the the order of field definitions is not - * identical. Otherwise, if the field definitions order is identical, an empty optional is returned. - */ - @Nonnull - private static Optional> buildChangeFieldDefinitionOrderUpdateAction( - @Nonnull final List oldFieldDefinitions, - @Nonnull final List newFieldDefinitions) { + /** + * Compares the order of a list of old {@link FieldDefinition}s and a list of new {@link + * FieldDefinition}s. If there is a change in order, then a change field definition order (with + * the new order) is built. If there are no changes in order an empty optional is returned. + * + * @param oldFieldDefinitions the list of old {@link FieldDefinition}s + * @param newFieldDefinitions the list of new {@link FieldDefinition}s + * @return a list of field definition update actions if the the order of field definitions is not + * identical. Otherwise, if the field definitions order is identical, an empty optional is + * returned. + */ + @Nonnull + private static Optional> buildChangeFieldDefinitionOrderUpdateAction( + @Nonnull final List oldFieldDefinitions, + @Nonnull final List newFieldDefinitions) { - final List newNames = newFieldDefinitions - .stream() - .map(FieldDefinition::getName) - .collect(toList()); + final List newNames = + newFieldDefinitions.stream().map(FieldDefinition::getName).collect(toList()); - final List existingNames = oldFieldDefinitions - .stream() + final List existingNames = + oldFieldDefinitions.stream() .map(FieldDefinition::getName) .filter(newNames::contains) .collect(toList()); - final List notExistingNames = newNames - .stream() - .filter(newName -> !existingNames.contains(newName)) - .collect(toList()); + final List notExistingNames = + newNames.stream().filter(newName -> !existingNames.contains(newName)).collect(toList()); - final List newFieldDefinitionsOrderNames = newFieldDefinitions - .stream() - .map(FieldDefinition::getName) - .collect(toList()); + final List newFieldDefinitionsOrderNames = + newFieldDefinitions.stream().map(FieldDefinition::getName).collect(toList()); - final List allNames = Stream.concat(existingNames.stream(), notExistingNames.stream()) - .collect(toList()); + final List allNames = + Stream.concat(existingNames.stream(), notExistingNames.stream()).collect(toList()); - return buildUpdateAction( - allNames, - newNames, - () -> ChangeFieldDefinitionOrder.of(newFieldDefinitionsOrderNames) - ); - } + return buildUpdateAction( + allNames, newNames, () -> ChangeFieldDefinitionOrder.of(newFieldDefinitionsOrderNames)); + } - /** - * Checks if there are any new field definition drafts which are not existing in the - * {@code oldFieldDefinitions}. If there are, then "add" field definition update actions are built. - * Otherwise, if there are no new field definitions, then an empty list is returned. - * - * @param oldFieldDefinitions the list of old {@link FieldDefinition}s. - * @param newFieldDefinitions the list of new {@link FieldDefinition}s. - * - * @return a list of field definition update actions if there are new field definition that should be added. - * Otherwise, if the field definitions are identical, an empty optional is returned. - */ - @Nonnull - private static List> buildAddFieldDefinitionUpdateActions( - @Nonnull final List oldFieldDefinitions, - @Nonnull final List newFieldDefinitions) { + /** + * Checks if there are any new field definition drafts which are not existing in the {@code + * oldFieldDefinitions}. If there are, then "add" field definition update actions are built. + * Otherwise, if there are no new field definitions, then an empty list is returned. + * + * @param oldFieldDefinitions the list of old {@link FieldDefinition}s. + * @param newFieldDefinitions the list of new {@link FieldDefinition}s. + * @return a list of field definition update actions if there are new field definition that should + * be added. Otherwise, if the field definitions are identical, an empty optional is returned. + */ + @Nonnull + private static List> buildAddFieldDefinitionUpdateActions( + @Nonnull final List oldFieldDefinitions, + @Nonnull final List newFieldDefinitions) { - final Map oldFieldDefinitionsNameMap = - oldFieldDefinitions - .stream() - .collect(toMap(FieldDefinition::getName, fieldDefinition -> fieldDefinition)); + final Map oldFieldDefinitionsNameMap = + oldFieldDefinitions.stream() + .collect(toMap(FieldDefinition::getName, fieldDefinition -> fieldDefinition)); - return newFieldDefinitions - .stream() - .filter(fieldDefinition -> !oldFieldDefinitionsNameMap.containsKey(fieldDefinition.getName())) - .map(fieldDefinition -> FieldDefinition.of(fieldDefinition.getType(), + return newFieldDefinitions.stream() + .filter( + fieldDefinition -> !oldFieldDefinitionsNameMap.containsKey(fieldDefinition.getName())) + .map( + fieldDefinition -> + FieldDefinition.of( + fieldDefinition.getType(), fieldDefinition.getName(), fieldDefinition.getLabel(), fieldDefinition.isRequired(), fieldDefinition.getInputHint())) - .map(AddFieldDefinition::of) - .collect(Collectors.toList()); - } + .map(AddFieldDefinition::of) + .collect(Collectors.toList()); + } - private FieldDefinitionsUpdateActionUtils() { - } + private FieldDefinitionsUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/types/utils/LocalizedEnumValueUpdateActionUtils.java b/src/main/java/com/commercetools/sync/types/utils/LocalizedEnumValueUpdateActionUtils.java index c27577c3f2..5a31d3fbc5 100644 --- a/src/main/java/com/commercetools/sync/types/utils/LocalizedEnumValueUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/types/utils/LocalizedEnumValueUpdateActionUtils.java @@ -1,60 +1,58 @@ package com.commercetools.sync.types.utils; +import static com.commercetools.sync.commons.utils.EnumValuesUpdateActionUtils.buildActions; + import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedEnumValue; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.commands.updateactions.AddLocalizedEnumValue; import io.sphere.sdk.types.commands.updateactions.ChangeLocalizedEnumValueOrder; - +import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; - -import static com.commercetools.sync.commons.utils.EnumValuesUpdateActionUtils.buildActions; -/** - * This class is only meant for the internal use of the commercetools-sync-java library. - */ +/** This class is only meant for the internal use of the commercetools-sync-java library. */ final class LocalizedEnumValueUpdateActionUtils { - /** - * Compares a list of old {@link LocalizedEnumValue}s with a list of new {@link LocalizedEnumValue}s for a given - * field definition and builds required update actions (e.g addLocalizedEnumValue, changeLocalizedEnumValueOrder). - * If both the {@link LocalizedEnumValue}'s are identical, then no update action is needed and hence - * an empty {@link List} is returned. - * - *

- * Note: Currently this util doesn't support the following: - *

    - *
  • removing the EnumValue/LocalizedEnumValue of a FieldDefinition
  • - *
  • updating the label of a EnumValue/LocalizedEnumValue of a FieldDefinition
  • - *
- * TODO: Check GITHUB ISSUE#339 for missing FieldDefinition update actions. - *

- * - * @param fieldDefinitionName the field name whose localized enum values are going to be synced. - * @param oldEnumValues the old list of localized enum values. - * @param newEnumValues the new list of localized enum values. - * @return a list of localized enum values update actions if the list of localized enum values is not identical. - * Otherwise, if the localized enum values are identical, an empty list is returned. - * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. - */ - @Nonnull - static List> buildLocalizedEnumValuesUpdateActions( - @Nonnull final String fieldDefinitionName, - @Nonnull final List oldEnumValues, - @Nullable final List newEnumValues) { + /** + * Compares a list of old {@link LocalizedEnumValue}s with a list of new {@link + * LocalizedEnumValue}s for a given field definition and builds required update actions (e.g + * addLocalizedEnumValue, changeLocalizedEnumValueOrder). If both the {@link LocalizedEnumValue}'s + * are identical, then no update action is needed and hence an empty {@link List} is returned. + * + *

Note: Currently this util doesn't support the following: + * + *

    + *
  • removing the EnumValue/LocalizedEnumValue of a FieldDefinition + *
  • updating the label of a EnumValue/LocalizedEnumValue of a FieldDefinition + *
+ * + * TODO: Check GITHUB ISSUE#339 for missing FieldDefinition update actions. + * + * @param fieldDefinitionName the field name whose localized enum values are going to be synced. + * @param oldEnumValues the old list of localized enum values. + * @param newEnumValues the new list of localized enum values. + * @return a list of localized enum values update actions if the list of localized enum values is + * not identical. Otherwise, if the localized enum values are identical, an empty list is + * returned. + * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. + */ + @Nonnull + static List> buildLocalizedEnumValuesUpdateActions( + @Nonnull final String fieldDefinitionName, + @Nonnull final List oldEnumValues, + @Nullable final List newEnumValues) { - return buildActions(fieldDefinitionName, - oldEnumValues, - newEnumValues, - null, - null, - AddLocalizedEnumValue::of, - null, - ChangeLocalizedEnumValueOrder::of); - } + return buildActions( + fieldDefinitionName, + oldEnumValues, + newEnumValues, + null, + null, + AddLocalizedEnumValue::of, + null, + ChangeLocalizedEnumValueOrder::of); + } - private LocalizedEnumValueUpdateActionUtils() { - } + private LocalizedEnumValueUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/types/utils/PlainEnumValueUpdateActionUtils.java b/src/main/java/com/commercetools/sync/types/utils/PlainEnumValueUpdateActionUtils.java index 4745530846..6d3932bc3d 100644 --- a/src/main/java/com/commercetools/sync/types/utils/PlainEnumValueUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/types/utils/PlainEnumValueUpdateActionUtils.java @@ -7,54 +7,51 @@ import io.sphere.sdk.types.Type; import io.sphere.sdk.types.commands.updateactions.AddEnumValue; import io.sphere.sdk.types.commands.updateactions.ChangeEnumValueOrder; - +import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; -/** - * This class is only meant for the internal use of the commercetools-sync-java library. - */ +/** This class is only meant for the internal use of the commercetools-sync-java library. */ final class PlainEnumValueUpdateActionUtils { - /** - * Compares a list of old {@link EnumValue}s with a list of new {@link EnumValue}s for a given - * field definition and builds required update actions (e.g addEnumValue, changeEnumValueOrder). - * If both the {@link EnumValue}'s are identical, then no update action is needed and hence - * an empty {@link List} is returned. - * - *

- * Note: Currently this util doesn't support the following: - *

    - *
  • removing the EnumValue/LocalizedEnumValue of a FieldDefinition
  • - *
  • updating the label of a EnumValue/LocalizedEnumValue of a FieldDefinition
  • - *
- * TODO: Check GITHUB ISSUE#339 for missing FieldDefinition update actions. - *

- * - * @param fieldDefinitionName the field name whose plain enum values are going to be synced. - * @param oldEnumValues the old list of plain enum values. - * @param newEnumValues the new list of plain enum values. - * @return a list of plain enum values update actions if the list of plain enum values is not identical. - * Otherwise, if the plain enum values are identical, an empty list is returned. - * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. - */ - @Nonnull - static List> buildEnumValuesUpdateActions( - @Nonnull final String fieldDefinitionName, - @Nonnull final List oldEnumValues, - @Nullable final List newEnumValues) { + /** + * Compares a list of old {@link EnumValue}s with a list of new {@link EnumValue}s for a given + * field definition and builds required update actions (e.g addEnumValue, changeEnumValueOrder). + * If both the {@link EnumValue}'s are identical, then no update action is needed and hence an + * empty {@link List} is returned. + * + *

Note: Currently this util doesn't support the following: + * + *

    + *
  • removing the EnumValue/LocalizedEnumValue of a FieldDefinition + *
  • updating the label of a EnumValue/LocalizedEnumValue of a FieldDefinition + *
+ * + * TODO: Check GITHUB ISSUE#339 for missing FieldDefinition update actions. + * + * @param fieldDefinitionName the field name whose plain enum values are going to be synced. + * @param oldEnumValues the old list of plain enum values. + * @param newEnumValues the new list of plain enum values. + * @return a list of plain enum values update actions if the list of plain enum values is not + * identical. Otherwise, if the plain enum values are identical, an empty list is returned. + * @throws DuplicateKeyException in case there are localized enum values with duplicate keys. + */ + @Nonnull + static List> buildEnumValuesUpdateActions( + @Nonnull final String fieldDefinitionName, + @Nonnull final List oldEnumValues, + @Nullable final List newEnumValues) { - return EnumValuesUpdateActionUtils.buildActions(fieldDefinitionName, - oldEnumValues, - newEnumValues, - null, - null, - AddEnumValue::of, - null, - ChangeEnumValueOrder::of); - } + return EnumValuesUpdateActionUtils.buildActions( + fieldDefinitionName, + oldEnumValues, + newEnumValues, + null, + null, + AddEnumValue::of, + null, + ChangeEnumValueOrder::of); + } - private PlainEnumValueUpdateActionUtils() { - } + private PlainEnumValueUpdateActionUtils() {} } diff --git a/src/main/java/com/commercetools/sync/types/utils/TypeSyncUtils.java b/src/main/java/com/commercetools/sync/types/utils/TypeSyncUtils.java index c55fd32300..efcd9a2ff8 100644 --- a/src/main/java/com/commercetools/sync/types/utils/TypeSyncUtils.java +++ b/src/main/java/com/commercetools/sync/types/utils/TypeSyncUtils.java @@ -1,62 +1,58 @@ package com.commercetools.sync.types.utils; +import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; +import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildChangeNameUpdateAction; +import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildFieldDefinitionsUpdateActions; +import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildSetDescriptionUpdateAction; + import com.commercetools.sync.types.TypeSyncOptions; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.TypeDraft; - -import javax.annotation.Nonnull; import java.util.List; - -import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; -import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildChangeNameUpdateAction; -import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildFieldDefinitionsUpdateActions; -import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildSetDescriptionUpdateAction; +import javax.annotation.Nonnull; public final class TypeSyncUtils { - /** - * Compares all the fields (including the field definitions see - * {@link TypeUpdateActionUtils#buildFieldDefinitionsUpdateActions(Type, TypeDraft, TypeSyncOptions)}) - * of a {@link Type} and a {@link TypeDraft}. - * It returns a {@link List} of {@link UpdateAction}<{@link Type}> as a - * result. If no update actions are needed, for example in case where both the {@link Type} and the - * {@link TypeDraft} have the same fields, an empty {@link List} is returned. - * - * - *

Note: Currently this util doesn't support the following: - *

    - *
  • updating the inputHint of a FieldDefinition
  • - *
  • removing the EnumValue/LocalizedEnumValue of a FieldDefinition
  • - *
  • updating the label of a EnumValue/LocalizedEnumValue of a FieldDefinition
  • - *
- * - * - * @param oldType the {@link Type} which should be updated. - * @param newType the {@link TypeDraft} where we get the new data. - * @param syncOptions the sync options wrapper which contains options related to the sync process supplied by - * the user. For example, custom callbacks to call in case of warnings or errors occurring - * on the build update action process. And other options (See {@link TypeSyncOptions} - * for more info. - * @return A list of type-specific update actions. - */ - @Nonnull - public static List> buildActions( - @Nonnull final Type oldType, - @Nonnull final TypeDraft newType, - @Nonnull final TypeSyncOptions syncOptions) { - - final List> updateActions = - filterEmptyOptionals( - buildChangeNameUpdateAction(oldType, newType), - buildSetDescriptionUpdateAction(oldType, newType) - ); - - updateActions.addAll(buildFieldDefinitionsUpdateActions(oldType, newType, syncOptions)); - - return updateActions; - } - - private TypeSyncUtils() { - } + /** + * Compares all the fields (including the field definitions see {@link + * TypeUpdateActionUtils#buildFieldDefinitionsUpdateActions(Type, TypeDraft, TypeSyncOptions)}) of + * a {@link Type} and a {@link TypeDraft}. It returns a {@link List} of {@link + * UpdateAction}<{@link Type}> as a result. If no update actions are needed, for example in + * case where both the {@link Type} and the {@link TypeDraft} have the same fields, an empty + * {@link List} is returned. + * + *

Note: Currently this util doesn't support the following: + * + *

    + *
  • updating the inputHint of a FieldDefinition + *
  • removing the EnumValue/LocalizedEnumValue of a FieldDefinition + *
  • updating the label of a EnumValue/LocalizedEnumValue of a FieldDefinition + *
+ * + * @param oldType the {@link Type} which should be updated. + * @param newType the {@link TypeDraft} where we get the new data. + * @param syncOptions the sync options wrapper which contains options related to the sync process + * supplied by the user. For example, custom callbacks to call in case of warnings or errors + * occurring on the build update action process. And other options (See {@link + * TypeSyncOptions} for more info. + * @return A list of type-specific update actions. + */ + @Nonnull + public static List> buildActions( + @Nonnull final Type oldType, + @Nonnull final TypeDraft newType, + @Nonnull final TypeSyncOptions syncOptions) { + + final List> updateActions = + filterEmptyOptionals( + buildChangeNameUpdateAction(oldType, newType), + buildSetDescriptionUpdateAction(oldType, newType)); + + updateActions.addAll(buildFieldDefinitionsUpdateActions(oldType, newType, syncOptions)); + + return updateActions; + } + + private TypeSyncUtils() {} } diff --git a/src/main/java/com/commercetools/sync/types/utils/TypeUpdateActionUtils.java b/src/main/java/com/commercetools/sync/types/utils/TypeUpdateActionUtils.java index e5d2347bea..257a2cf9ac 100644 --- a/src/main/java/com/commercetools/sync/types/utils/TypeUpdateActionUtils.java +++ b/src/main/java/com/commercetools/sync/types/utils/TypeUpdateActionUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.types.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static java.lang.String.format; +import static java.util.Collections.emptyList; import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.SyncException; @@ -10,97 +13,94 @@ import io.sphere.sdk.types.TypeDraft; import io.sphere.sdk.types.commands.updateactions.ChangeName; import io.sphere.sdk.types.commands.updateactions.SetDescription; - -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static java.lang.String.format; -import static java.util.Collections.emptyList; +import javax.annotation.Nonnull; public final class TypeUpdateActionUtils { - /** - * Compares the {@link LocalizedString} name values of a {@link Type} and a {@link TypeDraft} - * and returns an {@link Optional} of update action, which would contain the {@code "changeName"} - * {@link UpdateAction} if values are different. - * - * @param oldType the type that should be updated. - * @param newType the type draft which contains the new name. - * @return optional containing update action or empty optional if names are identical. - */ - @Nonnull - public static Optional> buildChangeNameUpdateAction( - @Nonnull final Type oldType, - @Nonnull final TypeDraft newType) { + /** + * Compares the {@link LocalizedString} name values of a {@link Type} and a {@link TypeDraft} and + * returns an {@link Optional} of update action, which would contain the {@code "changeName"} + * {@link UpdateAction} if values are different. + * + * @param oldType the type that should be updated. + * @param newType the type draft which contains the new name. + * @return optional containing update action or empty optional if names are identical. + */ + @Nonnull + public static Optional> buildChangeNameUpdateAction( + @Nonnull final Type oldType, @Nonnull final TypeDraft newType) { - return buildUpdateAction(oldType.getName(), newType.getName(), - () -> ChangeName.of(newType.getName())); - } + return buildUpdateAction( + oldType.getName(), newType.getName(), () -> ChangeName.of(newType.getName())); + } + /** + * Compares the {@link LocalizedString} descriptions of a {@link Type} and a {@link TypeDraft} and + * returns an {@link UpdateAction}<{@link Type}> as a result in an {@link Optional} of + * update action if values are different. + * + * @param oldType the type which should be updated. + * @param newType the type draft where we get the new description. + * @return A filled optional with the update action or an empty optional if the descriptions are + * identical. + */ + @Nonnull + public static Optional> buildSetDescriptionUpdateAction( + @Nonnull final Type oldType, @Nonnull final TypeDraft newType) { - /** - * Compares the {@link LocalizedString} descriptions of a {@link Type} and a {@link TypeDraft} and - * returns an {@link UpdateAction}<{@link Type}> as a result in an {@link Optional} - * of update action if values are different. - * - * @param oldType the type which should be updated. - * @param newType the type draft where we get the new description. - * @return A filled optional with the update action or an empty optional if the descriptions are identical. - */ - @Nonnull - public static Optional> buildSetDescriptionUpdateAction( - @Nonnull final Type oldType, - @Nonnull final TypeDraft newType) { + return buildUpdateAction( + oldType.getDescription(), + newType.getDescription(), + () -> SetDescription.of(newType.getDescription())); + } - return buildUpdateAction(oldType.getDescription(), newType.getDescription(), - () -> SetDescription.of(newType.getDescription())); - } - - /** - * Compares the field definitions of a {@link Type} and a {@link TypeDraft} and returns a list of - * {@link UpdateAction}<{@link Type}> as a result if the values are different. In case, the new type draft has - * a list of field definitions in which a duplicate name exists, the error callback is triggered and an empty list - * is returned. - * - * - *

Note: Currently this util doesn't support the following: - *

    - *
  • updating the inputHint of a FieldDefinition
  • - *
  • removing the EnumValue/LocalizedEnumValue of a FieldDefinition
  • - *
  • updating the label of a EnumValue/LocalizedEnumValue of a FieldDefinition
  • - *
- * TODO: Check GITHUB ISSUE#339 for missing FieldDefinition update actions. - * - * - * @param oldType the type which should be updated. - * @param newType the type draft where we get the key. - * @param syncOptions responsible for supplying the sync options to the sync utility method. - * It is used for triggering the error callback within the utility, in case of - * errors. - * @return A list with the update actions or an empty list if the field definitions are identical. - */ - @Nonnull - public static List> buildFieldDefinitionsUpdateActions( - @Nonnull final Type oldType, - @Nonnull final TypeDraft newType, - @Nonnull final TypeSyncOptions syncOptions) { + /** + * Compares the field definitions of a {@link Type} and a {@link TypeDraft} and returns a list of + * {@link UpdateAction}<{@link Type}> as a result if the values are different. In case, the + * new type draft has a list of field definitions in which a duplicate name exists, the error + * callback is triggered and an empty list is returned. + * + *

Note: Currently this util doesn't support the following: + * + *

    + *
  • updating the inputHint of a FieldDefinition + *
  • removing the EnumValue/LocalizedEnumValue of a FieldDefinition + *
  • updating the label of a EnumValue/LocalizedEnumValue of a FieldDefinition + *
+ * + * TODO: Check GITHUB ISSUE#339 for missing FieldDefinition update actions. + * + * @param oldType the type which should be updated. + * @param newType the type draft where we get the key. + * @param syncOptions responsible for supplying the sync options to the sync utility method. It is + * used for triggering the error callback within the utility, in case of errors. + * @return A list with the update actions or an empty list if the field definitions are identical. + */ + @Nonnull + public static List> buildFieldDefinitionsUpdateActions( + @Nonnull final Type oldType, + @Nonnull final TypeDraft newType, + @Nonnull final TypeSyncOptions syncOptions) { - try { - return FieldDefinitionsUpdateActionUtils.buildFieldDefinitionsUpdateActions( - oldType.getFieldDefinitions(), - newType.getFieldDefinitions() - ); - } catch (final BuildUpdateActionException exception) { - syncOptions.applyErrorCallback( - new SyncException(format("Failed to build update actions for the field definitions " - + "of the type with the key '%s'. Reason: %s", newType.getKey(), exception), - exception), oldType, newType, null); - return emptyList(); - } + try { + return FieldDefinitionsUpdateActionUtils.buildFieldDefinitionsUpdateActions( + oldType.getFieldDefinitions(), newType.getFieldDefinitions()); + } catch (final BuildUpdateActionException exception) { + syncOptions.applyErrorCallback( + new SyncException( + format( + "Failed to build update actions for the field definitions " + + "of the type with the key '%s'. Reason: %s", + newType.getKey(), exception), + exception), + oldType, + newType, + null); + return emptyList(); } + } - private TypeUpdateActionUtils() { - } + private TypeUpdateActionUtils() {} } diff --git a/src/test/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptionsBuilderTest.java index 26d39a46da..67e58f5f26 100644 --- a/src/test/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncOptionsBuilderTest.java @@ -1,5 +1,16 @@ package com.commercetools.sync.cartdiscounts; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -10,289 +21,283 @@ import io.sphere.sdk.cartdiscounts.commands.updateactions.ChangeName; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CartDiscountSyncOptionsBuilderTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private CartDiscountSyncOptionsBuilder cartDiscountSyncOptionsBuilder = - CartDiscountSyncOptionsBuilder.of(CTP_CLIENT); - - @Test - void of_WithClient_ShouldCreateCartDiscountSyncOptionsBuilder() { - assertThat(cartDiscountSyncOptionsBuilder).isNotNull(); - } - - @Test - void build_WithClient_ShouldBuildSyncOptions() { - final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); - assertThat(cartDiscountSyncOptions).isNotNull(); - assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNull(); - assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNull(); - assertThat(cartDiscountSyncOptions.getErrorCallback()).isNull(); - assertThat(cartDiscountSyncOptions.getWarningCallback()).isNull(); - assertThat(cartDiscountSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); - assertThat(cartDiscountSyncOptions.getBatchSize()).isEqualTo(CartDiscountSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(cartDiscountSyncOptions.getCacheSize()).isEqualTo(10_000); - } - - @Test - void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { - final TriFunction>, CartDiscountDraft, CartDiscount, - List>> beforeUpdateCallback = - (updateActions, newCartDiscount, oldCartDiscount) -> emptyList(); - - cartDiscountSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - - final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); - assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNotNull(); - } - - @Test - void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { - cartDiscountSyncOptionsBuilder.beforeCreateCallback((newCartDiscount) -> null); - - final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); - assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNotNull(); - } - - @Test - void errorCallBack_WithCallBack_ShouldSetCallBack() { - final QuadConsumer, - Optional, List>> mockErrorCallBack - = (syncException, draft, cartDiscount, updateActions) -> { - }; - cartDiscountSyncOptionsBuilder.errorCallback(mockErrorCallBack); - - final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); - assertThat(cartDiscountSyncOptions.getErrorCallback()).isNotNull(); - } - - @Test - void warningCallBack_WithCallBack_ShouldSetCallBack() { - final TriConsumer, Optional> mockWarningCallBack - = (syncException, draft, cartDiscount) -> { }; - cartDiscountSyncOptionsBuilder.warningCallback(mockWarningCallBack); - - final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); - assertThat(cartDiscountSyncOptions.getWarningCallback()).isNotNull(); - } - - @Test - void getThis_ShouldReturnCorrectInstance() { - final CartDiscountSyncOptionsBuilder instance = cartDiscountSyncOptionsBuilder.getThis(); - assertThat(instance).isNotNull(); - assertThat(instance).isInstanceOf(CartDiscountSyncOptionsBuilder.class); - assertThat(instance).isEqualTo(cartDiscountSyncOptionsBuilder); - } - - @Test - void cartDiscountSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(CTP_CLIENT) + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private CartDiscountSyncOptionsBuilder cartDiscountSyncOptionsBuilder = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateCartDiscountSyncOptionsBuilder() { + assertThat(cartDiscountSyncOptionsBuilder).isNotNull(); + } + + @Test + void build_WithClient_ShouldBuildSyncOptions() { + final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); + assertThat(cartDiscountSyncOptions).isNotNull(); + assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNull(); + assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNull(); + assertThat(cartDiscountSyncOptions.getErrorCallback()).isNull(); + assertThat(cartDiscountSyncOptions.getWarningCallback()).isNull(); + assertThat(cartDiscountSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); + assertThat(cartDiscountSyncOptions.getBatchSize()) + .isEqualTo(CartDiscountSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + assertThat(cartDiscountSyncOptions.getCacheSize()).isEqualTo(10_000); + } + + @Test + void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { + final TriFunction< + List>, + CartDiscountDraft, + CartDiscount, + List>> + beforeUpdateCallback = (updateActions, newCartDiscount, oldCartDiscount) -> emptyList(); + + cartDiscountSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); + + final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); + assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNotNull(); + } + + @Test + void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { + cartDiscountSyncOptionsBuilder.beforeCreateCallback((newCartDiscount) -> null); + + final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); + assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNotNull(); + } + + @Test + void errorCallBack_WithCallBack_ShouldSetCallBack() { + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + mockErrorCallBack = (syncException, draft, cartDiscount, updateActions) -> {}; + cartDiscountSyncOptionsBuilder.errorCallback(mockErrorCallBack); + + final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); + assertThat(cartDiscountSyncOptions.getErrorCallback()).isNotNull(); + } + + @Test + void warningCallBack_WithCallBack_ShouldSetCallBack() { + final TriConsumer, Optional> + mockWarningCallBack = (syncException, draft, cartDiscount) -> {}; + cartDiscountSyncOptionsBuilder.warningCallback(mockWarningCallBack); + + final CartDiscountSyncOptions cartDiscountSyncOptions = cartDiscountSyncOptionsBuilder.build(); + assertThat(cartDiscountSyncOptions.getWarningCallback()).isNotNull(); + } + + @Test + void getThis_ShouldReturnCorrectInstance() { + final CartDiscountSyncOptionsBuilder instance = cartDiscountSyncOptionsBuilder.getThis(); + assertThat(instance).isNotNull(); + assertThat(instance).isInstanceOf(CartDiscountSyncOptionsBuilder.class); + assertThat(instance).isEqualTo(cartDiscountSyncOptionsBuilder); + } + + @Test + void cartDiscountSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) .batchSize(30) .beforeCreateCallback((newCartDiscount) -> null) .beforeUpdateCallback((updateActions, newCartDiscount, oldCartDiscount) -> emptyList()) .build(); - assertThat(cartDiscountSyncOptions).isNotNull(); - } - - @Test - void batchSize_WithPositiveValue_ShouldSetBatchSize() { - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(10) - .build(); - assertThat(cartDiscountSyncOptions.getBatchSize()).isEqualTo(10); - } - - @Test - void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final CartDiscountSyncOptions cartDiscountSyncOptionsWithZeroBatchSize = - CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(0) - .build(); - - assertThat(cartDiscountSyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(CartDiscountSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - - final CartDiscountSyncOptions cartDiscountSyncOptionsWithNegativeBatchSize = CartDiscountSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(-100) + assertThat(cartDiscountSyncOptions).isNotNull(); + } + + @Test + void batchSize_WithPositiveValue_ShouldSetBatchSize() { + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); + assertThat(cartDiscountSyncOptions.getBatchSize()).isEqualTo(10); + } + + @Test + void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final CartDiscountSyncOptions cartDiscountSyncOptionsWithZeroBatchSize = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); + + assertThat(cartDiscountSyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(CartDiscountSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + + final CartDiscountSyncOptions cartDiscountSyncOptionsWithNegativeBatchSize = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); + assertThat(cartDiscountSyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(CartDiscountSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + + @Test + void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNull(); + + final List> updateActions = + singletonList(ChangeName.of(ofEnglish("name"))); + + final List> filteredList = + cartDiscountSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CartDiscountDraft.class), mock(CartDiscount.class)); + + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { + final TriFunction< + List>, + CartDiscountDraft, + CartDiscount, + List>> + beforeUpdateCallback = (updateActions, newCartDiscount, oldCartDiscount) -> null; + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + cartDiscountSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CartDiscountDraft.class), mock(CartDiscount.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + private interface MockTriFunction + extends TriFunction< + List>, + CartDiscountDraft, + CartDiscount, + List>> {} + + @Test + void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) .build(); - assertThat(cartDiscountSyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(CartDiscountSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } - - @Test - void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) - .build(); - assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - - final List> filteredList = - cartDiscountSyncOptions.applyBeforeUpdateCallback( - updateActions, mock(CartDiscountDraft.class), mock(CartDiscount.class)); - - assertThat(filteredList).isSameAs(updateActions); - } - - @Test - void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { - final TriFunction>, CartDiscountDraft, - CartDiscount, List>> beforeUpdateCallback = - (updateActions, newCartDiscount, oldCartDiscount) -> null; - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = - cartDiscountSyncOptions - .applyBeforeUpdateCallback(updateActions, mock(CartDiscountDraft.class), mock(CartDiscount.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - private interface MockTriFunction extends - TriFunction>, CartDiscountDraft, - CartDiscount, List>> { - } - - @Test - void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); - - final CartDiscountSyncOptions cartDiscountSyncOptions = - CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = emptyList(); - final List> filteredList = - cartDiscountSyncOptions - .applyBeforeUpdateCallback(updateActions, mock(CartDiscountDraft.class), mock(CartDiscount.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - @Test - void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { - final TriFunction>, CartDiscountDraft, - CartDiscount, List>> beforeUpdateCallback = - (updateActions, newCartDiscount, oldCartDiscount) -> emptyList(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = - cartDiscountSyncOptions - .applyBeforeUpdateCallback(updateActions, mock(CartDiscountDraft.class), mock(CartDiscount.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { - final Function draftFunction = - cartDiscountDraft -> - CartDiscountDraftBuilder.of(cartDiscountDraft) - .key(format("%s_filteredKey", cartDiscountDraft.getKey())) - .build(); - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) + assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = emptyList(); + final List> filteredList = + cartDiscountSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CartDiscountDraft.class), mock(CartDiscount.class)); + + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { + final TriFunction< + List>, + CartDiscountDraft, + CartDiscount, + List>> + beforeUpdateCallback = (updateActions, newCartDiscount, oldCartDiscount) -> emptyList(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + assertThat(cartDiscountSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + cartDiscountSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CartDiscountDraft.class), mock(CartDiscount.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { + final Function draftFunction = + cartDiscountDraft -> + CartDiscountDraftBuilder.of(cartDiscountDraft) + .key(format("%s_filteredKey", cartDiscountDraft.getKey())) .build(); - assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNotNull(); - - final CartDiscountDraft resourceDraft = mock(CartDiscountDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); - - - final Optional filteredDraft = cartDiscountSyncOptions - .applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).hasValueSatisfying(cartDiscountDraft -> - assertThat(cartDiscountDraft.getKey()).isEqualTo("myKey_filteredKey")); - } + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); - @Test - void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).build(); - assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNull(); + assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNotNull(); - final CartDiscountDraft resourceDraft = mock(CartDiscountDraft.class); - final Optional filteredDraft = cartDiscountSyncOptions.applyBeforeCreateCallback( - resourceDraft); + final CartDiscountDraft resourceDraft = mock(CartDiscountDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); - assertThat(filteredDraft).containsSame(resourceDraft); - } + final Optional filteredDraft = + cartDiscountSyncOptions.applyBeforeCreateCallback(resourceDraft); - @Test - void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { - final Function draftFunction = cartDiscountDraft -> null; - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback( - draftFunction) - .build(); - assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNotNull(); - - final CartDiscountDraft resourceDraft = mock(CartDiscountDraft.class); - final Optional filteredDraft = - cartDiscountSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isEmpty(); - } - - @Test - void cacheSize_WithPositiveValue_ShouldSetCacheSize() { - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(10) - .build(); - assertThat(cartDiscountSyncOptions.getCacheSize()).isEqualTo(10); - } - - @Test - void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final CartDiscountSyncOptions cartDiscountSyncOptionsWithZeroCacheSize = CartDiscountSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(0) - .build(); - - assertThat(cartDiscountSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - - final CartDiscountSyncOptions cartDiscountSyncOptionsWithNegativeCacheSize = CartDiscountSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(-100) - .build(); - - assertThat(cartDiscountSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } + assertThat(filteredDraft) + .hasValueSatisfying( + cartDiscountDraft -> + assertThat(cartDiscountDraft.getKey()).isEqualTo("myKey_filteredKey")); + } + + @Test + void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNull(); + + final CartDiscountDraft resourceDraft = mock(CartDiscountDraft.class); + final Optional filteredDraft = + cartDiscountSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).containsSame(resourceDraft); + } + + @Test + void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { + final Function draftFunction = cartDiscountDraft -> null; + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(cartDiscountSyncOptions.getBeforeCreateCallback()).isNotNull(); + + final CartDiscountDraft resourceDraft = mock(CartDiscountDraft.class); + final Optional filteredDraft = + cartDiscountSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isEmpty(); + } + + @Test + void cacheSize_WithPositiveValue_ShouldSetCacheSize() { + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); + assertThat(cartDiscountSyncOptions.getCacheSize()).isEqualTo(10); + } + + @Test + void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final CartDiscountSyncOptions cartDiscountSyncOptionsWithZeroCacheSize = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); + + assertThat(cartDiscountSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); + + final CartDiscountSyncOptions cartDiscountSyncOptionsWithNegativeCacheSize = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); + + assertThat(cartDiscountSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncTest.java b/src/test/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncTest.java index f28a6454ad..7ac12f165b 100644 --- a/src/test/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncTest.java +++ b/src/test/java/com/commercetools/sync/cartdiscounts/CartDiscountSyncTest.java @@ -1,5 +1,23 @@ package com.commercetools.sync.cartdiscounts; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static java.lang.String.format; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.cartdiscounts.helpers.CartDiscountSyncStatistics; import com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics; import com.commercetools.sync.commons.exceptions.SyncException; @@ -16,43 +34,25 @@ import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.SphereException; import io.sphere.sdk.types.CustomFieldsDraft; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.concurrent.CompletionException; - -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static java.lang.String.format; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.CompletableFuture.supplyAsync; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; class CartDiscountSyncTest { - private static CartDiscountDraft newCartDiscount; - private static final String KEY = "cart-discount-key"; + private static CartDiscountDraft newCartDiscount; + private static final String KEY = "cart-discount-key"; - @BeforeAll - static void setup() { - newCartDiscount = CartDiscountDraftBuilder - .of(LocalizedString.of(Locale.GERMAN, "Neu Name", Locale.ENGLISH, "new name"), + @BeforeAll + static void setup() { + newCartDiscount = + CartDiscountDraftBuilder.of( + LocalizedString.of(Locale.GERMAN, "Neu Name", Locale.ENGLISH, "new name"), CartPredicate.of("totalPrice >= \"50 EUR\""), CartDiscountValue.ofRelative(1000), ShippingCostTarget.of(), @@ -60,240 +60,255 @@ static void setup() { false) .key(KEY) .active(false) - .description(LocalizedString.of(Locale.GERMAN, "Beschreibung", - Locale.ENGLISH, "description")) + .description( + LocalizedString.of(Locale.GERMAN, "Beschreibung", Locale.ENGLISH, "description")) .validFrom(ZonedDateTime.parse("2019-05-05T00:00:00.000Z")) .validUntil(ZonedDateTime.parse("2019-05-15T00:00:00.000Z")) .build(); - } - - @Test - void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions syncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + } + + @Test + void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CartDiscountSyncOptions syncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - - final CartDiscountService mockCartDiscountService = mock(CartDiscountService.class); - - when(mockCartDiscountService - .fetchMatchingCartDiscountsByKeys(singleton(KEY))) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - final CartDiscountSync cartDiscountSync = - new CartDiscountSync(syncOptions, getMockTypeService(), mockCartDiscountService); - - // test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync - .sync(singletonList(newCartDiscount)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo( - format("Failed to fetch existing cart discounts with keys: '[%s]'.", KEY)) - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(SyncException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); - assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(SphereException.class); + final CartDiscountService mockCartDiscountService = mock(CartDiscountService.class); + + when(mockCartDiscountService.fetchMatchingCartDiscountsByKeys(singleton(KEY))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final CartDiscountSync cartDiscountSync = + new CartDiscountSync(syncOptions, getMockTypeService(), mockCartDiscountService); + + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync.sync(singletonList(newCartDiscount)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .isEqualTo( + format("Failed to fetch existing cart discounts with keys: '[%s]'.", KEY))); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(SyncException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); + assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(SphereException.class); }); - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { - // preparation - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); - - final CartDiscountService cartDiscountService = mock(CartDiscountService.class); - when(cartDiscountService.fetchMatchingCartDiscountsByKeys(anySet())).thenReturn(completedFuture(emptySet())); - when(cartDiscountService.createCartDiscount(any())).thenReturn(completedFuture(Optional.empty())); - - final CartDiscountSyncOptions spyCartDiscountSyncOptions = spy(cartDiscountSyncOptions); - - // test - new CartDiscountSync(spyCartDiscountSyncOptions, getMockTypeService(), cartDiscountService) - .sync(singletonList(newCartDiscount)).toCompletableFuture().join(); - - // assertion - verify(spyCartDiscountSyncOptions).applyBeforeCreateCallback(newCartDiscount); - verify(spyCartDiscountSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - @Test - void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { - // preparation - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { + // preparation + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final CartDiscountService cartDiscountService = mock(CartDiscountService.class); + when(cartDiscountService.fetchMatchingCartDiscountsByKeys(anySet())) + .thenReturn(completedFuture(emptySet())); + when(cartDiscountService.createCartDiscount(any())) + .thenReturn(completedFuture(Optional.empty())); + + final CartDiscountSyncOptions spyCartDiscountSyncOptions = spy(cartDiscountSyncOptions); + + // test + new CartDiscountSync(spyCartDiscountSyncOptions, getMockTypeService(), cartDiscountService) + .sync(singletonList(newCartDiscount)) + .toCompletableFuture() + .join(); + + // assertion + verify(spyCartDiscountSyncOptions).applyBeforeCreateCallback(newCartDiscount); + verify(spyCartDiscountSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { + // preparation + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final CartDiscount mockedExistingCartDiscount = mock(CartDiscount.class); + when(mockedExistingCartDiscount.getKey()).thenReturn(newCartDiscount.getKey()); + + final CartDiscountService cartDiscountService = mock(CartDiscountService.class); + when(cartDiscountService.fetchMatchingCartDiscountsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockedExistingCartDiscount))); + + when(cartDiscountService.updateCartDiscount(any(), any())) + .thenReturn(completedFuture(mockedExistingCartDiscount)); + + final CartDiscountSyncOptions spyCartDiscountSyncOptions = spy(cartDiscountSyncOptions); + + // test + new CartDiscountSync(spyCartDiscountSyncOptions, getMockTypeService(), cartDiscountService) + .sync(singletonList(newCartDiscount)) + .toCompletableFuture() + .join(); + + // assertion + verify(spyCartDiscountSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spyCartDiscountSyncOptions, never()).applyBeforeCreateCallback(newCartDiscount); + } + + @Test + void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final CartDiscountDraft newCartDiscountDraft = null; + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - final CartDiscount mockedExistingCartDiscount = mock(CartDiscount.class); - when(mockedExistingCartDiscount.getKey()).thenReturn(newCartDiscount.getKey()); - - final CartDiscountService cartDiscountService = mock(CartDiscountService.class); - when(cartDiscountService.fetchMatchingCartDiscountsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockedExistingCartDiscount))); - - when(cartDiscountService.updateCartDiscount(any(), any())) - .thenReturn(completedFuture(mockedExistingCartDiscount)); - - final CartDiscountSyncOptions spyCartDiscountSyncOptions = spy(cartDiscountSyncOptions); - - // test - new CartDiscountSync(spyCartDiscountSyncOptions, getMockTypeService(), cartDiscountService) - .sync(singletonList(newCartDiscount)).toCompletableFuture().join(); - - // assertion - verify(spyCartDiscountSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spyCartDiscountSyncOptions, never()).applyBeforeCreateCallback(newCartDiscount); - } - - @Test - void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //preparation - final CartDiscountDraft newCartDiscountDraft = null; - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); + final CartDiscountSync cartDiscountSync = + new CartDiscountSync( + cartDiscountSyncOptions, getMockTypeService(), mock(CartDiscountService.class)); + + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync.sync(singletonList(newCartDiscountDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).isEqualTo("CartDiscountDraft is null.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying(throwable -> assertThat(throwable.getCause()).isNull()); + + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithoutKey_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final CartDiscountDraft newCartDiscountDraftWithoutKey = mock(CartDiscountDraft.class); + when(newCartDiscountDraftWithoutKey.getKey()).thenReturn(null); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); }) - .build(); - - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions, - getMockTypeService(), mock(CartDiscountService.class)); - - //test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync - .sync(singletonList(newCartDiscountDraft)) - .toCompletableFuture() - .join(); - - //assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo("CartDiscountDraft is null.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> assertThat(throwable.getCause()).isNull()); - - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithoutKey_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //preparation - final CartDiscountDraft newCartDiscountDraftWithoutKey = mock(CartDiscountDraft.class); - when(newCartDiscountDraftWithoutKey.getKey()).thenReturn(null); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) .build(); - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions, - getMockTypeService(), mock(CartDiscountService.class)); + final CartDiscountSync cartDiscountSync = + new CartDiscountSync( + cartDiscountSyncOptions, getMockTypeService(), mock(CartDiscountService.class)); - //test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithoutKey)) .toCompletableFuture() .join(); - //assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo("CartDiscountDraft with name: null doesn't have a key. " - + "Please make sure all cart discount drafts have keys.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> assertThat(throwable.getCause()).isNull()); - - assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithFailOnCachingKeysToIds_ShouldTriggerErrorCallbackAndReturnProperStats() { - // preparation - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .isEqualTo( + "CartDiscountDraft with name: null doesn't have a key. " + + "Please make sure all cart discount drafts have keys.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying(throwable -> assertThat(throwable.getCause()).isNull()); + + assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithFailOnCachingKeysToIds_ShouldTriggerErrorCallbackAndReturnProperStats() { + // preparation + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final TypeService typeService = spy(new TypeServiceImpl(cartDiscountSyncOptions)); - when(typeService.cacheKeysToIds(anySet())) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - - final CartDiscountSync cartDiscountSync = new CartDiscountSync(cartDiscountSyncOptions, - typeService, mock(CartDiscountService.class)); - - final CartDiscountDraft newCartDiscountDraftWithCustomType = mock(CartDiscountDraft.class); - when(newCartDiscountDraftWithCustomType.getKey()).thenReturn("cart-discount-key"); - when(newCartDiscountDraftWithCustomType.getCustom()).thenReturn( - CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())); - - //test - final CartDiscountSyncStatistics cartDiscountSyncStatistics = cartDiscountSync + final TypeService typeService = spy(new TypeServiceImpl(cartDiscountSyncOptions)); + when(typeService.cacheKeysToIds(anySet())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final CartDiscountSync cartDiscountSync = + new CartDiscountSync(cartDiscountSyncOptions, typeService, mock(CartDiscountService.class)); + + final CartDiscountDraft newCartDiscountDraftWithCustomType = mock(CartDiscountDraft.class); + when(newCartDiscountDraftWithCustomType.getKey()).thenReturn("cart-discount-key"); + when(newCartDiscountDraftWithCustomType.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())); + + // test + final CartDiscountSyncStatistics cartDiscountSyncStatistics = + cartDiscountSync .sync(singletonList(newCartDiscountDraftWithCustomType)) .toCompletableFuture() .join(); + // assertions + AssertionsForStatistics.assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); - // assertions - AssertionsForStatistics.assertThat(cartDiscountSyncStatistics).hasValues(1, 0, 0, 1); + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to build a cache of keys to ids.")); - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to build a cache of keys to ids.")); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); }); - } - + } } diff --git a/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountBatchValidatorTest.java b/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountBatchValidatorTest.java index 37db46e0e9..2c5292917f 100644 --- a/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountBatchValidatorTest.java @@ -1,121 +1,127 @@ package com.commercetools.sync.cartdiscounts.helpers; +import static com.commercetools.sync.cartdiscounts.helpers.CartDiscountBatchValidator.CART_DISCOUNT_DRAFT_IS_NULL; +import static com.commercetools.sync.cartdiscounts.helpers.CartDiscountBatchValidator.CART_DISCOUNT_DRAFT_KEY_NOT_SET; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.types.CustomFieldsDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; - -import static com.commercetools.sync.cartdiscounts.helpers.CartDiscountBatchValidator.CART_DISCOUNT_DRAFT_IS_NULL; -import static com.commercetools.sync.cartdiscounts.helpers.CartDiscountBatchValidator.CART_DISCOUNT_DRAFT_KEY_NOT_SET; -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CartDiscountBatchValidatorTest { - private CartDiscountSyncOptions syncOptions; - private CartDiscountSyncStatistics syncStatistics; - private List errorCallBackMessages; + private CartDiscountSyncOptions syncOptions; + private CartDiscountSyncStatistics syncStatistics; + private List errorCallBackMessages; - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = CartDiscountSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorCallBackMessages.add(exception.getMessage())) + syncOptions = + CartDiscountSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallBackMessages.add(exception.getMessage())) .build(); - syncStatistics = mock(CartDiscountSyncStatistics.class); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.emptyList()); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullCartDiscountDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.singletonList(null)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(CART_DISCOUNT_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCartDiscountDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final CartDiscountDraft cartDiscountDraft = mock(CartDiscountDraft.class); - final Set validDrafts = getValidDrafts(Collections.singletonList(cartDiscountDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CART_DISCOUNT_DRAFT_KEY_NOT_SET, cartDiscountDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCartDiscountDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { - final CartDiscountDraft cartDiscountDraft = mock(CartDiscountDraft.class); - when(cartDiscountDraft.getKey()).thenReturn(EMPTY); - final Set validDrafts = getValidDrafts(Collections.singletonList(cartDiscountDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CART_DISCOUNT_DRAFT_KEY_NOT_SET, cartDiscountDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { - final CartDiscountDraft validCartDiscountDraft = mock(CartDiscountDraft.class); - when(validCartDiscountDraft.getKey()).thenReturn("validDraftKey"); - when(validCartDiscountDraft.getCustom()) - .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", Collections.emptyMap())); - - final CartDiscountDraft validMainCartDiscountDraft = mock(CartDiscountDraft.class); - when(validMainCartDiscountDraft.getKey()).thenReturn("validDraftKey1"); - - final CartDiscountDraft invalidCartDiscountDraft = mock(CartDiscountDraft.class); - - final CartDiscountBatchValidator cartDiscountBatchValidator = - new CartDiscountBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair - = cartDiscountBatchValidator.validateAndCollectReferencedKeys( - Arrays.asList(validCartDiscountDraft, invalidCartDiscountDraft, validMainCartDiscountDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CART_DISCOUNT_DRAFT_KEY_NOT_SET, invalidCartDiscountDraft.getName())); - assertThat(pair.getLeft()) - .containsExactlyInAnyOrder(validCartDiscountDraft, validMainCartDiscountDraft); - assertThat(pair.getRight()) - .containsExactlyInAnyOrder("typeKey"); - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List cartDiscountDrafts) { - final CartDiscountBatchValidator cartDiscountBatchValidator = - new CartDiscountBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = - cartDiscountBatchValidator.validateAndCollectReferencedKeys(cartDiscountDrafts); - return pair.getLeft(); - } + syncStatistics = mock(CartDiscountSyncStatistics.class); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.emptyList()); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullCartDiscountDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.singletonList(null)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(CART_DISCOUNT_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCartDiscountDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final CartDiscountDraft cartDiscountDraft = mock(CartDiscountDraft.class); + final Set validDrafts = + getValidDrafts(Collections.singletonList(cartDiscountDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CART_DISCOUNT_DRAFT_KEY_NOT_SET, cartDiscountDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCartDiscountDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { + final CartDiscountDraft cartDiscountDraft = mock(CartDiscountDraft.class); + when(cartDiscountDraft.getKey()).thenReturn(EMPTY); + final Set validDrafts = + getValidDrafts(Collections.singletonList(cartDiscountDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CART_DISCOUNT_DRAFT_KEY_NOT_SET, cartDiscountDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { + final CartDiscountDraft validCartDiscountDraft = mock(CartDiscountDraft.class); + when(validCartDiscountDraft.getKey()).thenReturn("validDraftKey"); + when(validCartDiscountDraft.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", Collections.emptyMap())); + + final CartDiscountDraft validMainCartDiscountDraft = mock(CartDiscountDraft.class); + when(validMainCartDiscountDraft.getKey()).thenReturn("validDraftKey1"); + + final CartDiscountDraft invalidCartDiscountDraft = mock(CartDiscountDraft.class); + + final CartDiscountBatchValidator cartDiscountBatchValidator = + new CartDiscountBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + cartDiscountBatchValidator.validateAndCollectReferencedKeys( + Arrays.asList( + validCartDiscountDraft, invalidCartDiscountDraft, validMainCartDiscountDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CART_DISCOUNT_DRAFT_KEY_NOT_SET, invalidCartDiscountDraft.getName())); + assertThat(pair.getLeft()) + .containsExactlyInAnyOrder(validCartDiscountDraft, validMainCartDiscountDraft); + assertThat(pair.getRight()).containsExactlyInAnyOrder("typeKey"); + } + + @Nonnull + private Set getValidDrafts( + @Nonnull final List cartDiscountDrafts) { + final CartDiscountBatchValidator cartDiscountBatchValidator = + new CartDiscountBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + cartDiscountBatchValidator.validateAndCollectReferencedKeys(cartDiscountDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountReferenceResolverTest.java b/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountReferenceResolverTest.java index 0518041c83..d4275b3703 100644 --- a/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountReferenceResolverTest.java @@ -1,5 +1,16 @@ package com.commercetools.sync.cartdiscounts.helpers; +import static com.commercetools.sync.cartdiscounts.helpers.CartDiscountReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; +import static java.lang.String.format; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; @@ -15,44 +26,31 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.MoneyImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.Optional; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.cartdiscounts.helpers.CartDiscountReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; -import static java.lang.String.format; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CartDiscountReferenceResolverTest { - private CartDiscountReferenceResolver referenceResolver; - private TypeService typeService; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - final CartDiscountSyncOptions syncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)).build(); - typeService = getMockTypeService(); - referenceResolver = new CartDiscountReferenceResolver(syncOptions, typeService); - } - - @Test - void resolveReferences_WithoutReferences_ShouldNotResolveReferences() { - final CartDiscountDraft cartDiscountDraft = CartDiscountDraftBuilder - .of("cartPredicate", + private CartDiscountReferenceResolver referenceResolver; + private TypeService typeService; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + final CartDiscountSyncOptions syncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + typeService = getMockTypeService(); + referenceResolver = new CartDiscountReferenceResolver(syncOptions, typeService); + } + + @Test + void resolveReferences_WithoutReferences_ShouldNotResolveReferences() { + final CartDiscountDraft cartDiscountDraft = + CartDiscountDraftBuilder.of( + "cartPredicate", LocalizedString.ofEnglish("foo"), true, "0.1", @@ -61,21 +59,21 @@ void resolveReferences_WithoutReferences_ShouldNotResolveReferences() { .key("cart-discount-key") .build(); - final CartDiscountDraft resolvedDraft = referenceResolver - .resolveReferences(cartDiscountDraft) - .toCompletableFuture() - .join(); + final CartDiscountDraft resolvedDraft = + referenceResolver.resolveReferences(cartDiscountDraft).toCompletableFuture().join(); - assertThat(resolvedDraft).isEqualTo(cartDiscountDraft); - } + assertThat(resolvedDraft).isEqualTo(cartDiscountDraft); + } - @Test - void resolveCustomTypeReference_WithNullKeyOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { - final CustomFieldsDraft newCustomFieldsDraft = mock(CustomFieldsDraft.class); - when(newCustomFieldsDraft.getType()).thenReturn(ResourceIdentifier.ofId(null)); + @Test + void + resolveCustomTypeReference_WithNullKeyOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { + final CustomFieldsDraft newCustomFieldsDraft = mock(CustomFieldsDraft.class); + when(newCustomFieldsDraft.getType()).thenReturn(ResourceIdentifier.ofId(null)); - final CartDiscountDraftBuilder cartDiscountDraftBuilder = CartDiscountDraftBuilder - .of("cartPredicate", + final CartDiscountDraftBuilder cartDiscountDraftBuilder = + CartDiscountDraftBuilder.of( + "cartPredicate", LocalizedString.ofEnglish("foo"), true, "0.1", @@ -84,18 +82,26 @@ void resolveCustomTypeReference_WithNullKeyOnCustomTypeReference_ShouldNotResolv .key("cart-discount-key") .custom(newCustomFieldsDraft); - assertThat(referenceResolver.resolveCustomTypeReference(cartDiscountDraftBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on CartDiscountDraft with " - + "key:'cart-discount-key'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { - final CartDiscountDraftBuilder cartDiscountDraftBuilder = CartDiscountDraftBuilder - .of("cartPredicate", + assertThat( + referenceResolver + .resolveCustomTypeReference(cartDiscountDraftBuilder) + .toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on CartDiscountDraft with " + + "key:'cart-discount-key'. Reason: %s", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void + resolveCustomTypeReference_WithEmptyKeyOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { + final CartDiscountDraftBuilder cartDiscountDraftBuilder = + CartDiscountDraftBuilder.of( + "cartPredicate", LocalizedString.ofEnglish("foo"), true, "0.1", @@ -104,19 +110,26 @@ void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeReference_ShouldNotResol .key("cart-discount-key") .custom(CustomFieldsDraft.ofTypeKeyAndJson("", emptyMap())); - assertThat(referenceResolver.resolveCustomTypeReference(cartDiscountDraftBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on CartDiscountDraft with " - + "key:'cart-discount-key'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { - final String customTypeKey = "customTypeKey"; - final CartDiscountDraftBuilder cartDiscountDraftBuilder = CartDiscountDraftBuilder - .of("cartPredicate", + assertThat( + referenceResolver + .resolveCustomTypeReference(cartDiscountDraftBuilder) + .toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on CartDiscountDraft with " + + "key:'cart-discount-key'. Reason: %s", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { + final String customTypeKey = "customTypeKey"; + final CartDiscountDraftBuilder cartDiscountDraftBuilder = + CartDiscountDraftBuilder.of( + "cartPredicate", LocalizedString.ofEnglish("foo"), true, "0.1", @@ -125,24 +138,26 @@ void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptio .key("cart-discount-key") .custom(CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, emptyMap())); - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - // Test and assertion - final String expectedExceptionMessage = - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, cartDiscountDraftBuilder.getKey()); - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); - assertThat(referenceResolver.resolveCustomTypeReference(cartDiscountDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveReferences_WithAllValidReferences_ShouldResolveReferences() { - final CartDiscountDraft cartDiscountDraft = CartDiscountDraftBuilder - .of("cartPredicate", + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + // Test and assertion + final String expectedExceptionMessage = + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, cartDiscountDraftBuilder.getKey()); + final String expectedMessageWithCause = + format( + "%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); + assertThat(referenceResolver.resolveCustomTypeReference(cartDiscountDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void resolveReferences_WithAllValidReferences_ShouldResolveReferences() { + final CartDiscountDraft cartDiscountDraft = + CartDiscountDraftBuilder.of( + "cartPredicate", LocalizedString.ofEnglish("foo"), true, "0.1", @@ -152,16 +167,14 @@ void resolveReferences_WithAllValidReferences_ShouldResolveReferences() { .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", new HashMap<>())) .build(); - final CartDiscountDraft resolvedDraft = referenceResolver - .resolveReferences(cartDiscountDraft) - .toCompletableFuture() - .join(); + final CartDiscountDraft resolvedDraft = + referenceResolver.resolveReferences(cartDiscountDraft).toCompletableFuture().join(); - final CartDiscountDraft expectedDraft = CartDiscountDraftBuilder - .of(cartDiscountDraft) + final CartDiscountDraft expectedDraft = + CartDiscountDraftBuilder.of(cartDiscountDraft) .custom(CustomFieldsDraft.ofTypeIdAndJson("typeId", new HashMap<>())) .build(); - assertThat(resolvedDraft).isEqualTo(expectedDraft); - } + assertThat(resolvedDraft).isEqualTo(expectedDraft); + } } diff --git a/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountSyncStatisticsTest.java b/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountSyncStatisticsTest.java index 7ac95e663e..2f22375de4 100644 --- a/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountSyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/cartdiscounts/helpers/CartDiscountSyncStatisticsTest.java @@ -1,27 +1,28 @@ package com.commercetools.sync.cartdiscounts.helpers; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - class CartDiscountSyncStatisticsTest { - private CartDiscountSyncStatistics cartDiscountSyncStatistics; + private CartDiscountSyncStatistics cartDiscountSyncStatistics; - @BeforeEach - void setup() { - cartDiscountSyncStatistics = new CartDiscountSyncStatistics(); - } + @BeforeEach + void setup() { + cartDiscountSyncStatistics = new CartDiscountSyncStatistics(); + } - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - cartDiscountSyncStatistics.incrementCreated(1); - cartDiscountSyncStatistics.incrementFailed(2); - cartDiscountSyncStatistics.incrementUpdated(3); - cartDiscountSyncStatistics.incrementProcessed(6); + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + cartDiscountSyncStatistics.incrementCreated(1); + cartDiscountSyncStatistics.incrementFailed(2); + cartDiscountSyncStatistics.incrementUpdated(3); + cartDiscountSyncStatistics.incrementProcessed(6); - assertThat(cartDiscountSyncStatistics.getReportMessage()) - .isEqualTo("Summary: 6 cart discounts were processed in total " + assertThat(cartDiscountSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 6 cart discounts were processed in total " + "(1 created, 3 updated and 2 failed to sync)."); - } + } } diff --git a/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtilsTest.java index 94cccbb9db..535cddf19f 100644 --- a/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtilsTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.cartdiscounts.utils; +import static com.commercetools.sync.commons.MockUtils.getTypeMock; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.cartdiscounts.CartDiscount; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.cartdiscounts.queries.CartDiscountQuery; @@ -7,75 +12,71 @@ import io.sphere.sdk.models.Reference; import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.UUID; - -import static com.commercetools.sync.commons.MockUtils.getTypeMock; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CartDiscountReferenceResolutionUtilsTest { - @Test - void mapToCartDiscountDrafts_WithExpandedReferences_ShouldReturnResourceIdentifiersWithKeys() { - final Type mockCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - - final List mockCartDiscounts = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - final CartDiscount mockCartDiscount = mock(CartDiscount.class); - //Mock cartDiscounts custom fields with expanded type references. - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = Reference.ofResourceTypeIdAndObj("resourceTypeId", - mockCustomType); - when(mockCustomFields.getType()).thenReturn(typeReference); - when(mockCartDiscount.getCustom()).thenReturn(mockCustomFields); - mockCartDiscounts.add(mockCartDiscount); - } - - final List referenceReplacedDrafts = - CartDiscountReferenceResolutionUtils.mapToCartDiscountDrafts(mockCartDiscounts); + @Test + void mapToCartDiscountDrafts_WithExpandedReferences_ShouldReturnResourceIdentifiersWithKeys() { + final Type mockCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + + final List mockCartDiscounts = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + final CartDiscount mockCartDiscount = mock(CartDiscount.class); + // Mock cartDiscounts custom fields with expanded type references. + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = + Reference.ofResourceTypeIdAndObj("resourceTypeId", mockCustomType); + when(mockCustomFields.getType()).thenReturn(typeReference); + when(mockCartDiscount.getCustom()).thenReturn(mockCustomFields); + mockCartDiscounts.add(mockCartDiscount); + } + final List referenceReplacedDrafts = + CartDiscountReferenceResolutionUtils.mapToCartDiscountDrafts(mockCartDiscounts); - referenceReplacedDrafts.forEach(draft -> { - assertThat(draft.getCustom().getType().getId()).isNull(); - assertThat(draft.getCustom().getType().getKey()).isEqualTo(mockCustomType.getKey()); + referenceReplacedDrafts.forEach( + draft -> { + assertThat(draft.getCustom().getType().getId()).isNull(); + assertThat(draft.getCustom().getType().getKey()).isEqualTo(mockCustomType.getKey()); }); + } + + @Test + void + mapToCartDiscountDrafts_WithNonExpandedReferences_ShouldReturnResourceIdentifiersWithoutKeys() { + final String customTypeId = UUID.randomUUID().toString(); + + final List mockCartDiscounts = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + final CartDiscount mockCartDiscount = mock(CartDiscount.class); + // Mock cartDiscounts custom fields with non-expanded type references. + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = + Reference.ofResourceTypeIdAndId("resourceTypeId", customTypeId); + when(mockCustomFields.getType()).thenReturn(typeReference); + when(mockCartDiscount.getCustom()).thenReturn(mockCustomFields); + + mockCartDiscounts.add(mockCartDiscount); } - @Test - void mapToCartDiscountDrafts_WithNonExpandedReferences_ShouldReturnResourceIdentifiersWithoutKeys() { - final String customTypeId = UUID.randomUUID().toString(); - - final List mockCartDiscounts = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - final CartDiscount mockCartDiscount = mock(CartDiscount.class); - //Mock cartDiscounts custom fields with non-expanded type references. - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = Reference.ofResourceTypeIdAndId("resourceTypeId", - customTypeId); - when(mockCustomFields.getType()).thenReturn(typeReference); - when(mockCartDiscount.getCustom()).thenReturn(mockCustomFields); - - mockCartDiscounts.add(mockCartDiscount); - } + final List referenceReplacedDrafts = + CartDiscountReferenceResolutionUtils.mapToCartDiscountDrafts(mockCartDiscounts); - final List referenceReplacedDrafts = - CartDiscountReferenceResolutionUtils.mapToCartDiscountDrafts(mockCartDiscounts); - - - referenceReplacedDrafts.forEach(draft -> { - assertThat(draft.getCustom().getType().getId()).isEqualTo(customTypeId); - assertThat(draft.getCustom().getType().getKey()).isNull(); + referenceReplacedDrafts.forEach( + draft -> { + assertThat(draft.getCustom().getType().getId()).isEqualTo(customTypeId); + assertThat(draft.getCustom().getType().getKey()).isNull(); }); - } - - @Test - void buildCartDiscountQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final CartDiscountQuery cartDiscountQuery = CartDiscountReferenceResolutionUtils.buildCartDiscountQuery(); - assertThat(cartDiscountQuery.expansionPaths()).containsExactly(ExpansionPath.of("custom.type")); - } + } + + @Test + void buildCartDiscountQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final CartDiscountQuery cartDiscountQuery = + CartDiscountReferenceResolutionUtils.buildCartDiscountQuery(); + assertThat(cartDiscountQuery.expansionPaths()).containsExactly(ExpansionPath.of("custom.type")); + } } diff --git a/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountSyncUtilsTest.java b/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountSyncUtilsTest.java index 5af9145c24..7747769ef9 100644 --- a/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountSyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountSyncUtilsTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.cartdiscounts.utils; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountSyncUtils.buildActions; +import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -32,249 +39,248 @@ import io.sphere.sdk.types.CustomFieldsDraftBuilder; import io.sphere.sdk.types.Type; import io.sphere.sdk.utils.MoneyImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.time.ZonedDateTime; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; - -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountSyncUtils.buildActions; -import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CartDiscountSyncUtilsTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - - private static final String KEY = "key"; - private static final LocalizedString NAME = LocalizedString.of(Locale.ENGLISH, "name"); - private static final LocalizedString DESC = - LocalizedString.of(Locale.ENGLISH, "discount- get 10 percent"); - private static final String PREDICATE = - "lineItemExists(sku = \"0123456789\" or sku = \"0246891213\") = true"; - private static final CartDiscountValue VALUE = CartDiscountValue.ofRelative(1000); - private static final CartDiscountTarget TARGET = LineItemsTarget.ofAll(); - private static final ZonedDateTime JANUARY_FROM = ZonedDateTime.parse("2019-01-01T00:00:00.000Z"); - private static final ZonedDateTime JANUARY_UNTIL = ZonedDateTime.parse("2019-01-31T00:00:00.000Z"); - private static final String SORT_ORDER = "0.1"; - private static final Boolean IS_ACTIVE = false; - private static final Boolean IS_REQUIRE_DISC_CODE = false; - private static final StackingMode STACKING_MODE = StackingMode.STACKING; - - private static final String CUSTOM_TYPE_ID = "id"; - private static final String CUSTOM_FIELD_NAME = "field"; - private static final String CUSTOM_FIELD_VALUE = "value"; - - private CartDiscount cartDiscount; - private CartDiscountDraft cartDiscountDraft; - - /** - * Creates a {@code cartDiscount} and a {@code cartDiscountDraft} with identical values. - */ - @BeforeEach - void setup() { - cartDiscount = mock(CartDiscount.class); - when(cartDiscount.getKey()).thenReturn(KEY); - when(cartDiscount.getName()).thenReturn(NAME); - when(cartDiscount.getDescription()).thenReturn(DESC); - when(cartDiscount.getCartPredicate()).thenReturn(PREDICATE); - when(cartDiscount.getValue()).thenReturn(VALUE); - when(cartDiscount.getTarget()).thenReturn(TARGET); - when(cartDiscount.getValidFrom()).thenReturn(JANUARY_FROM); - when(cartDiscount.getValidUntil()).thenReturn(JANUARY_UNTIL); - when(cartDiscount.getSortOrder()).thenReturn(SORT_ORDER); - when(cartDiscount.isActive()).thenReturn(IS_ACTIVE); - when(cartDiscount.isRequiringDiscountCode()).thenReturn(IS_REQUIRE_DISC_CODE); - when(cartDiscount.getStackingMode()).thenReturn(STACKING_MODE); - - final CustomFields customFields = mock(CustomFields.class); - when(customFields.getType()).thenReturn(Type.referenceOfId(CUSTOM_TYPE_ID)); - - final Map customFieldsJsonMapMock = new HashMap<>(); - customFieldsJsonMapMock.put(CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode(CUSTOM_FIELD_VALUE)); - when(customFields.getFieldsJsonMap()).thenReturn(customFieldsJsonMapMock); - - when(cartDiscount.getCustom()).thenReturn(customFields); - - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraftBuilder - .ofTypeId(CUSTOM_TYPE_ID) + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + + private static final String KEY = "key"; + private static final LocalizedString NAME = LocalizedString.of(Locale.ENGLISH, "name"); + private static final LocalizedString DESC = + LocalizedString.of(Locale.ENGLISH, "discount- get 10 percent"); + private static final String PREDICATE = + "lineItemExists(sku = \"0123456789\" or sku = \"0246891213\") = true"; + private static final CartDiscountValue VALUE = CartDiscountValue.ofRelative(1000); + private static final CartDiscountTarget TARGET = LineItemsTarget.ofAll(); + private static final ZonedDateTime JANUARY_FROM = ZonedDateTime.parse("2019-01-01T00:00:00.000Z"); + private static final ZonedDateTime JANUARY_UNTIL = + ZonedDateTime.parse("2019-01-31T00:00:00.000Z"); + private static final String SORT_ORDER = "0.1"; + private static final Boolean IS_ACTIVE = false; + private static final Boolean IS_REQUIRE_DISC_CODE = false; + private static final StackingMode STACKING_MODE = StackingMode.STACKING; + + private static final String CUSTOM_TYPE_ID = "id"; + private static final String CUSTOM_FIELD_NAME = "field"; + private static final String CUSTOM_FIELD_VALUE = "value"; + + private CartDiscount cartDiscount; + private CartDiscountDraft cartDiscountDraft; + + /** Creates a {@code cartDiscount} and a {@code cartDiscountDraft} with identical values. */ + @BeforeEach + void setup() { + cartDiscount = mock(CartDiscount.class); + when(cartDiscount.getKey()).thenReturn(KEY); + when(cartDiscount.getName()).thenReturn(NAME); + when(cartDiscount.getDescription()).thenReturn(DESC); + when(cartDiscount.getCartPredicate()).thenReturn(PREDICATE); + when(cartDiscount.getValue()).thenReturn(VALUE); + when(cartDiscount.getTarget()).thenReturn(TARGET); + when(cartDiscount.getValidFrom()).thenReturn(JANUARY_FROM); + when(cartDiscount.getValidUntil()).thenReturn(JANUARY_UNTIL); + when(cartDiscount.getSortOrder()).thenReturn(SORT_ORDER); + when(cartDiscount.isActive()).thenReturn(IS_ACTIVE); + when(cartDiscount.isRequiringDiscountCode()).thenReturn(IS_REQUIRE_DISC_CODE); + when(cartDiscount.getStackingMode()).thenReturn(STACKING_MODE); + + final CustomFields customFields = mock(CustomFields.class); + when(customFields.getType()).thenReturn(Type.referenceOfId(CUSTOM_TYPE_ID)); + + final Map customFieldsJsonMapMock = new HashMap<>(); + customFieldsJsonMapMock.put( + CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode(CUSTOM_FIELD_VALUE)); + when(customFields.getFieldsJsonMap()).thenReturn(customFieldsJsonMapMock); + + when(cartDiscount.getCustom()).thenReturn(customFields); + + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) .addObject(CUSTOM_FIELD_NAME, CUSTOM_FIELD_VALUE) .build(); - cartDiscountDraft = - CartDiscountDraftBuilder.of(NAME, PREDICATE, VALUE, TARGET, SORT_ORDER, IS_REQUIRE_DISC_CODE) - .key(KEY) - .description(DESC) - .sortOrder(SORT_ORDER) - .active(IS_ACTIVE) - .validFrom(JANUARY_FROM) - .validUntil(JANUARY_UNTIL) - .custom(customFieldsDraft) - .build(); - - } - - @Test - void buildActions_WithSameValues_ShouldNotBuildUpdateActions() { - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).build(); - final List> updateActions = - buildActions(cartDiscount, cartDiscountDraft, cartDiscountSyncOptions); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildActions_FromDraftsWithDifferentNameValues_ShouldBuildUpdateActions() { - final LocalizedString newName = - LocalizedString.of(Locale.GERMAN, "Neu Name", Locale.ENGLISH, "new name"); - - final CartDiscountDraft newCartDiscount = CartDiscountDraftBuilder - .of(cartDiscountDraft) - .name(newName) - .build(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).build(); - final List> updateActions = - buildActions(cartDiscount, newCartDiscount, cartDiscountSyncOptions); - - assertThat(updateActions).isNotEmpty(); - assertThat(updateActions).containsExactly(ChangeName.of(newName)); - } - - @Test - void buildActions_FromDraftsWithAllDifferentValues_ShouldBuildAllUpdateActions() { - final LocalizedString newName = - LocalizedString.of(Locale.GERMAN, "Neu Name", Locale.ENGLISH, "new name"); - final CartPredicate newCartDiscountPredicate = CartPredicate.of("1 = 1"); - final CartDiscountValue newCartDiscountValue = CartDiscountValue.ofAbsolute(MoneyImpl.of(10, EUR)); - final CartDiscountTarget newCartDiscountTarget = LineItemsTarget.of("quantity > 1"); - final String newSortOrder = "0.3"; - final LocalizedString newDesc = - LocalizedString.of(Locale.GERMAN, "Neu Beschreibung", Locale.ENGLISH, "new description"); - final ZonedDateTime newValidFrom = ZonedDateTime.parse("2019-11-01T00:00:00.000Z"); - final ZonedDateTime newValidUntil = ZonedDateTime.parse("2019-11-15T00:00:00.000Z"); - final boolean newIsActive = true; - final boolean newRequireDiscountCode = true; - final StackingMode newStackingMode = StackingMode.STOP_AFTER_THIS_DISCOUNT; - final CustomFieldsDraft newCustomFieldsDraft = CustomFieldsDraftBuilder - .ofTypeId(UUID.randomUUID().toString()) + cartDiscountDraft = + CartDiscountDraftBuilder.of( + NAME, PREDICATE, VALUE, TARGET, SORT_ORDER, IS_REQUIRE_DISC_CODE) + .key(KEY) + .description(DESC) + .sortOrder(SORT_ORDER) + .active(IS_ACTIVE) + .validFrom(JANUARY_FROM) + .validUntil(JANUARY_UNTIL) + .custom(customFieldsDraft) + .build(); + } + + @Test + void buildActions_WithSameValues_ShouldNotBuildUpdateActions() { + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).build(); + final List> updateActions = + buildActions(cartDiscount, cartDiscountDraft, cartDiscountSyncOptions); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildActions_FromDraftsWithDifferentNameValues_ShouldBuildUpdateActions() { + final LocalizedString newName = + LocalizedString.of(Locale.GERMAN, "Neu Name", Locale.ENGLISH, "new name"); + + final CartDiscountDraft newCartDiscount = + CartDiscountDraftBuilder.of(cartDiscountDraft).name(newName).build(); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).build(); + final List> updateActions = + buildActions(cartDiscount, newCartDiscount, cartDiscountSyncOptions); + + assertThat(updateActions).isNotEmpty(); + assertThat(updateActions).containsExactly(ChangeName.of(newName)); + } + + @Test + void buildActions_FromDraftsWithAllDifferentValues_ShouldBuildAllUpdateActions() { + final LocalizedString newName = + LocalizedString.of(Locale.GERMAN, "Neu Name", Locale.ENGLISH, "new name"); + final CartPredicate newCartDiscountPredicate = CartPredicate.of("1 = 1"); + final CartDiscountValue newCartDiscountValue = + CartDiscountValue.ofAbsolute(MoneyImpl.of(10, EUR)); + final CartDiscountTarget newCartDiscountTarget = LineItemsTarget.of("quantity > 1"); + final String newSortOrder = "0.3"; + final LocalizedString newDesc = + LocalizedString.of(Locale.GERMAN, "Neu Beschreibung", Locale.ENGLISH, "new description"); + final ZonedDateTime newValidFrom = ZonedDateTime.parse("2019-11-01T00:00:00.000Z"); + final ZonedDateTime newValidUntil = ZonedDateTime.parse("2019-11-15T00:00:00.000Z"); + final boolean newIsActive = true; + final boolean newRequireDiscountCode = true; + final StackingMode newStackingMode = StackingMode.STOP_AFTER_THIS_DISCOUNT; + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraftBuilder.ofTypeId(UUID.randomUUID().toString()).build(); + + final CartDiscountDraft newCartDiscount = + CartDiscountDraftBuilder.of( + newName, + newCartDiscountPredicate, + newCartDiscountValue, + newCartDiscountTarget, + newSortOrder, + newRequireDiscountCode) + .active(newIsActive) + .description(newDesc) + .validFrom(newValidFrom) + .validUntil(newValidUntil) + .stackingMode(newStackingMode) + .custom(newCustomFieldsDraft) .build(); - final CartDiscountDraft newCartDiscount = CartDiscountDraftBuilder - .of(newName, - newCartDiscountPredicate, - newCartDiscountValue, - newCartDiscountTarget, - newSortOrder, - newRequireDiscountCode) - .active(newIsActive) - .description(newDesc) - .validFrom(newValidFrom) - .validUntil(newValidUntil) - .stackingMode(newStackingMode) - .custom(newCustomFieldsDraft) - .build(); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).build(); - final List> updateActions = - buildActions(cartDiscount, newCartDiscount, cartDiscountSyncOptions); - - assertThat(updateActions).containsExactly( - ChangeValue.of(newCartDiscountValue), - ChangeCartPredicate.of(newCartDiscountPredicate), - ChangeTarget.of(newCartDiscountTarget), - ChangeIsActive.of(newIsActive), - ChangeName.of(newName), - SetDescription.of(newDesc), - ChangeSortOrder.of(newSortOrder), - ChangeRequiresDiscountCode.of(newRequireDiscountCode), - SetValidFromAndUntil.of(newValidFrom, newValidUntil), - ChangeStackingMode.of(newStackingMode), - SetCustomType.ofTypeIdAndJson(newCustomFieldsDraft.getType().getId(), emptyMap()) - ); - } - - @Test - void buildActions_WithDifferentCustomType_ShouldBuildUpdateAction() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraftBuilder.ofTypeId("newId") - .addObject("newField", "newValue") - .build(); - - final CartDiscountDraft cartDiscountDraftWithCustomField = - CartDiscountDraftBuilder.of(cartDiscountDraft) - .custom(customFieldsDraft) - .build(); - - final List> actions = - buildActions(cartDiscount, cartDiscountDraftWithCustomField, - CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - - - assertThat(actions).containsExactly( - SetCustomType.ofTypeIdAndJson(customFieldsDraft.getType().getId(), customFieldsDraft.getFields())); - } - - @Test - void buildActions_WithSameCustomTypeWithNewCustomFields_ShouldBuildUpdateAction() { - final CustomFieldsDraft sameCustomFieldDraftWithNewCustomField = - CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) - .addObject(CUSTOM_FIELD_NAME, CUSTOM_FIELD_VALUE) - .addObject("name_2", "value_2") - .build(); - - final CartDiscountDraft cartDiscountDraftWithCustomField = - CartDiscountDraftBuilder.of(cartDiscountDraft) - .custom(sameCustomFieldDraftWithNewCustomField) - .build(); - - final List> actions = - buildActions(cartDiscount, cartDiscountDraftWithCustomField, - CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - - - assertThat(actions).containsExactly( - SetCustomField.ofJson("name_2", JsonNodeFactory.instance.textNode("value_2"))); - } - - @Test - void buildActions_WithSameCustomTypeWithDifferentCustomFieldValues_ShouldBuildUpdateAction() { - final CustomFieldsDraft sameCustomFieldDraftWithNewValue = CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) - .addObject(CUSTOM_FIELD_NAME, "newValue") - .build(); - - final CartDiscountDraft cartDiscountDraftWithCustomField = - CartDiscountDraftBuilder.of(cartDiscountDraft) - .custom(sameCustomFieldDraftWithNewValue) - .build(); - - final List> actions = - buildActions(cartDiscount, cartDiscountDraftWithCustomField, - CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(CTP_CLIENT).build(); + final List> updateActions = + buildActions(cartDiscount, newCartDiscount, cartDiscountSyncOptions); + + assertThat(updateActions) + .containsExactly( + ChangeValue.of(newCartDiscountValue), + ChangeCartPredicate.of(newCartDiscountPredicate), + ChangeTarget.of(newCartDiscountTarget), + ChangeIsActive.of(newIsActive), + ChangeName.of(newName), + SetDescription.of(newDesc), + ChangeSortOrder.of(newSortOrder), + ChangeRequiresDiscountCode.of(newRequireDiscountCode), + SetValidFromAndUntil.of(newValidFrom, newValidUntil), + ChangeStackingMode.of(newStackingMode), + SetCustomType.ofTypeIdAndJson(newCustomFieldsDraft.getType().getId(), emptyMap())); + } + + @Test + void buildActions_WithDifferentCustomType_ShouldBuildUpdateAction() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraftBuilder.ofTypeId("newId").addObject("newField", "newValue").build(); + + final CartDiscountDraft cartDiscountDraftWithCustomField = + CartDiscountDraftBuilder.of(cartDiscountDraft).custom(customFieldsDraft).build(); + + final List> actions = + buildActions( + cartDiscount, + cartDiscountDraftWithCustomField, + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions) + .containsExactly( + SetCustomType.ofTypeIdAndJson( + customFieldsDraft.getType().getId(), customFieldsDraft.getFields())); + } + + @Test + void buildActions_WithSameCustomTypeWithNewCustomFields_ShouldBuildUpdateAction() { + final CustomFieldsDraft sameCustomFieldDraftWithNewCustomField = + CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) + .addObject(CUSTOM_FIELD_NAME, CUSTOM_FIELD_VALUE) + .addObject("name_2", "value_2") + .build(); + final CartDiscountDraft cartDiscountDraftWithCustomField = + CartDiscountDraftBuilder.of(cartDiscountDraft) + .custom(sameCustomFieldDraftWithNewCustomField) + .build(); - assertThat(actions).containsExactly( - SetCustomField.ofJson(CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode("newValue"))); - } + final List> actions = + buildActions( + cartDiscount, + cartDiscountDraftWithCustomField, + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - @Test - void buildActions_WithJustNewCartDiscountHasNullCustomType_ShouldBuildUpdateAction() { - final CartDiscountDraft cartDiscountDraftWithNullCustomField = - CartDiscountDraftBuilder.of(cartDiscountDraft) - .custom(null) - .build(); + assertThat(actions) + .containsExactly( + SetCustomField.ofJson("name_2", JsonNodeFactory.instance.textNode("value_2"))); + } - final List> actions = - buildActions(cartDiscount, cartDiscountDraftWithNullCustomField, - CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + @Test + void buildActions_WithSameCustomTypeWithDifferentCustomFieldValues_ShouldBuildUpdateAction() { + final CustomFieldsDraft sameCustomFieldDraftWithNewValue = + CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) + .addObject(CUSTOM_FIELD_NAME, "newValue") + .build(); + final CartDiscountDraft cartDiscountDraftWithCustomField = + CartDiscountDraftBuilder.of(cartDiscountDraft) + .custom(sameCustomFieldDraftWithNewValue) + .build(); - assertThat(actions).containsExactly(SetCustomType.ofRemoveType()); - } + final List> actions = + buildActions( + cartDiscount, + cartDiscountDraftWithCustomField, + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions) + .containsExactly( + SetCustomField.ofJson( + CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode("newValue"))); + } + + @Test + void buildActions_WithJustNewCartDiscountHasNullCustomType_ShouldBuildUpdateAction() { + final CartDiscountDraft cartDiscountDraftWithNullCustomField = + CartDiscountDraftBuilder.of(cartDiscountDraft).custom(null).build(); + + final List> actions = + buildActions( + cartDiscount, + cartDiscountDraftWithNullCustomField, + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions).containsExactly(SetCustomType.ofRemoveType()); + } } diff --git a/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountUpdateActionUtilsTest.java index b7544d2b46..8465a103d3 100644 --- a/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/cartdiscounts/utils/CartDiscountUpdateActionUtilsTest.java @@ -1,5 +1,25 @@ package com.commercetools.sync.cartdiscounts.utils; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeCartPredicateUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeIsActiveUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeNameUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeRequiresDiscountCodeUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeSortOrderUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeStackingModeUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeTargetUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeValueUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildSetDescriptionUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildSetValidDatesUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildSetValidFromUpdateAction; +import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildSetValidUntilUpdateAction; +import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; +import static io.sphere.sdk.models.DefaultCurrencyUnits.USD; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.cartdiscounts.CartDiscount; import io.sphere.sdk.cartdiscounts.CartDiscountDraft; import io.sphere.sdk.cartdiscounts.CartDiscountTarget; @@ -31,1380 +51,1432 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.Product; import io.sphere.sdk.utils.MoneyImpl; -import org.javamoney.moneta.Money; -import org.junit.jupiter.api.Test; - -import javax.money.MonetaryAmount; import java.time.ZonedDateTime; import java.util.List; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeCartPredicateUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeIsActiveUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeNameUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeRequiresDiscountCodeUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeSortOrderUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeStackingModeUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeTargetUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildChangeValueUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildSetDescriptionUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildSetValidDatesUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildSetValidFromUpdateAction; -import static com.commercetools.sync.cartdiscounts.utils.CartDiscountUpdateActionUtils.buildSetValidUntilUpdateAction; -import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; -import static io.sphere.sdk.models.DefaultCurrencyUnits.USD; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.money.MonetaryAmount; +import org.javamoney.moneta.Money; +import org.junit.jupiter.api.Test; class CartDiscountUpdateActionUtilsTest { - @Test - void buildChangeValueUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(CartDiscountValue.ofRelative(1000)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final CartDiscountValue tenEuro = CartDiscountValue.ofAbsolute(MoneyImpl.of(10, EUR)); - when(newCartDiscountDraft.getValue()).thenReturn(tenEuro); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(tenEuro)); - } - - @Test - void buildChangeValueUpdateAction_WithDifferentRelativeValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(CartDiscountValue.ofRelative(1000)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final CartDiscountValue twentyPercent = CartDiscountValue.ofRelative(2000); - when(newCartDiscountDraft.getValue()).thenReturn(twentyPercent); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(twentyPercent)); - } - - @Test - void buildChangeValueUpdateAction_WithSameRelativeValues_ShouldNotBuildUpdateAction() { - final CartDiscountValue tenPercent = CartDiscountValue.ofRelative(1000); - final CartDiscountValue tenPercent2 = CartDiscountValue.ofRelative(1000); - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(tenPercent); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()).thenReturn(tenPercent2); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).isNotPresent(); - } - - @Test - void buildChangeValueUpdateAction_WithOnlyNewGiftItemProductValue_ShouldBuildUpdateAction() { - final CartDiscountValue relativeCartDiscountValue = CartDiscountValue.ofRelative(1000); - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(relativeCartDiscountValue); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("product-2"), - 1, null, null); - when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); - } - - @Test - void buildChangeValueUpdateAction_WithOnlyOldGiftItemProductValue_ShouldBuildUpdateAction() { - final GiftLineItemCartDiscountValue giftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("product-2"), - 1, null, null); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(giftLineItemCartDiscountValue); - - final CartDiscountValue relativeCartDiscountValue = CartDiscountValue.ofRelative(1000); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()).thenReturn(relativeCartDiscountValue); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(relativeCartDiscountValue)); - } - - @Test - void buildChangeValueUpdateAction_WithDifferentGiftItemProductValue_ShouldBuildUpdateAction() { - final GiftLineItemCartDiscountValue oldGiftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(Reference.of(Product.referenceTypeId(), "product-1"), - 1, null, null); - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(oldGiftLineItemCartDiscountValue); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("product-2"), - 1, null, null); - when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); - } - - @Test - void buildChangeValueUpdateAction_WithDifferentGiftItemProductVariantValue_ShouldBuildUpdateAction() { - final GiftLineItemCartDiscountValue oldGiftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(Reference.of(Product.referenceTypeId(), "productId"), - 1, null, null); - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(oldGiftLineItemCartDiscountValue); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("productId"), - 2, null, null); - when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); - } + @Test + void buildChangeValueUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(CartDiscountValue.ofRelative(1000)); - @Test - void buildChangeValueUpdateAction_WithDifferentGiftItemSupplyChannelValue_ShouldBuildUpdateAction() { - final GiftLineItemCartDiscountValue oldGiftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(Reference.of(Product.referenceTypeId(), "productId"), - 1, Reference.of(Channel.referenceTypeId(), "supplyChannel-1"), null); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final CartDiscountValue tenEuro = CartDiscountValue.ofAbsolute(MoneyImpl.of(10, EUR)); + when(newCartDiscountDraft.getValue()).thenReturn(tenEuro); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(oldGiftLineItemCartDiscountValue); + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("productId"), - 1, ResourceIdentifier.ofId("supplyChannel-2"), null); - when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); + assertThat(changeValueUpdateAction).contains(ChangeValue.of(tenEuro)); + } - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + @Test + void buildChangeValueUpdateAction_WithDifferentRelativeValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(CartDiscountValue.ofRelative(1000)); - assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); - } + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final CartDiscountValue twentyPercent = CartDiscountValue.ofRelative(2000); + when(newCartDiscountDraft.getValue()).thenReturn(twentyPercent); - @Test - void buildChangeValueUpdateAction_WithDifferentGiftItemDistributionChannelValue_ShouldBuildUpdateAction() { - final GiftLineItemCartDiscountValue oldGiftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(Reference.of(Product.referenceTypeId(), "productId"), - 1, null, Reference.of(Channel.referenceTypeId(), "dist-channel-1")); + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(oldGiftLineItemCartDiscountValue); + assertThat(changeValueUpdateAction).contains(ChangeValue.of(twentyPercent)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("productId"), - 1, null, ResourceIdentifier.ofId("dist-channel-2")); - when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); + @Test + void buildChangeValueUpdateAction_WithSameRelativeValues_ShouldNotBuildUpdateAction() { + final CartDiscountValue tenPercent = CartDiscountValue.ofRelative(1000); + final CartDiscountValue tenPercent2 = CartDiscountValue.ofRelative(1000); - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(tenPercent); - assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); - } + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()).thenReturn(tenPercent2); - @Test - void buildChangeValueUpdateAction_WithSameGiftItemValue_ShouldNotBuildUpdateAction() { - final GiftLineItemCartDiscountValue giftLineItemCartDiscountValue = - GiftLineItemCartDiscountValue.of(Reference.of(Product.referenceTypeId(), "productId"), - 1, null, null); + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).isNotPresent(); + } + + @Test + void buildChangeValueUpdateAction_WithOnlyNewGiftItemProductValue_ShouldBuildUpdateAction() { + final CartDiscountValue relativeCartDiscountValue = CartDiscountValue.ofRelative(1000); - final GiftLineItemCartDiscountValue giftLineItemCartDiscountValue2 = - GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("productId"), - 1, null, null); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(relativeCartDiscountValue); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(giftLineItemCartDiscountValue); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("product-2"), 1, null, null); + when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); + } + + @Test + void buildChangeValueUpdateAction_WithOnlyOldGiftItemProductValue_ShouldBuildUpdateAction() { + final GiftLineItemCartDiscountValue giftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("product-2"), 1, null, null); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(giftLineItemCartDiscountValue); + + final CartDiscountValue relativeCartDiscountValue = CartDiscountValue.ofRelative(1000); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()).thenReturn(relativeCartDiscountValue); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).contains(ChangeValue.of(relativeCartDiscountValue)); + } + + @Test + void buildChangeValueUpdateAction_WithDifferentGiftItemProductValue_ShouldBuildUpdateAction() { + final GiftLineItemCartDiscountValue oldGiftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of( + Reference.of(Product.referenceTypeId(), "product-1"), 1, null, null); + + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(oldGiftLineItemCartDiscountValue); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("product-2"), 1, null, null); + when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); + } + + @Test + void + buildChangeValueUpdateAction_WithDifferentGiftItemProductVariantValue_ShouldBuildUpdateAction() { + final GiftLineItemCartDiscountValue oldGiftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of( + Reference.of(Product.referenceTypeId(), "productId"), 1, null, null); + + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(oldGiftLineItemCartDiscountValue); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("productId"), 2, null, null); + when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); + } + + @Test + void + buildChangeValueUpdateAction_WithDifferentGiftItemSupplyChannelValue_ShouldBuildUpdateAction() { + final GiftLineItemCartDiscountValue oldGiftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of( + Reference.of(Product.referenceTypeId(), "productId"), + 1, + Reference.of(Channel.referenceTypeId(), "supplyChannel-1"), + null); + + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(oldGiftLineItemCartDiscountValue); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of( + ResourceIdentifier.ofId("productId"), + 1, + ResourceIdentifier.ofId("supplyChannel-2"), + null); + when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); + } + + @Test + void + buildChangeValueUpdateAction_WithDifferentGiftItemDistributionChannelValue_ShouldBuildUpdateAction() { + final GiftLineItemCartDiscountValue oldGiftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of( + Reference.of(Product.referenceTypeId(), "productId"), + 1, + null, + Reference.of(Channel.referenceTypeId(), "dist-channel-1")); + + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(oldGiftLineItemCartDiscountValue); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final GiftLineItemCartDiscountValue newGiftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of( + ResourceIdentifier.ofId("productId"), + 1, + null, + ResourceIdentifier.ofId("dist-channel-2")); + when(newCartDiscountDraft.getValue()).thenReturn(newGiftLineItemCartDiscountValue); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).contains(ChangeValue.of(newGiftLineItemCartDiscountValue)); + } + + @Test + void buildChangeValueUpdateAction_WithSameGiftItemValue_ShouldNotBuildUpdateAction() { + final GiftLineItemCartDiscountValue giftLineItemCartDiscountValue = + GiftLineItemCartDiscountValue.of( + Reference.of(Product.referenceTypeId(), "productId"), 1, null, null); + + final GiftLineItemCartDiscountValue giftLineItemCartDiscountValue2 = + GiftLineItemCartDiscountValue.of(ResourceIdentifier.ofId("productId"), 1, null, null); + + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(giftLineItemCartDiscountValue); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()).thenReturn(giftLineItemCartDiscountValue2); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).isNotPresent(); + } + + @Test + void buildChangeValueUpdateAction_WithNullNewAbsoluteValue_ShouldBuildUpdateAction() { + final CartDiscountValue values = + CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); + + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(values); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()).thenReturn(null); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).contains(ChangeValue.of(null)); + } + + @Test + void buildChangeValueUpdateAction_WithNullNewAbsoluteAmounts_ShouldBuildUpdateAction() { + final CartDiscountValue value = + CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); + final List newAmounts = null; + final CartDiscountValue value2 = CartDiscountValue.ofAbsolute(newAmounts); + + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(value); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()).thenReturn(value2); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).contains(ChangeValue.of(value2)); + } + + @Test + void buildChangeValueUpdateAction_WithSameAbsoluteValues_ShouldNotBuildUpdateAction() { + final CartDiscountValue values = + CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); + final CartDiscountValue values2 = + CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); + + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(values); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()).thenReturn(values2); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).isNotPresent(); + } + + @Test + void + buildChangeValueUpdateAction_WithSameValuesUsingDifferentMonetaryAmount_ShouldNotBuildUpdateAction() { + final CartDiscountValue values = + CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); + final CartDiscountValue values2 = + CartDiscountValue.ofAbsolute(asList(Money.of(10, EUR), Money.of(10, USD))); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()).thenReturn(giftLineItemCartDiscountValue2); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()).thenReturn(values); - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()).thenReturn(values2); - assertThat(changeValueUpdateAction).isNotPresent(); - } + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeValueUpdateAction_WithNullNewAbsoluteValue_ShouldBuildUpdateAction() { - final CartDiscountValue values = - CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); + assertThat(changeValueUpdateAction).isNotPresent(); + } - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(values); + @Test + void buildChangeValueUpdateAction_WithDifferentAbsoluteValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()) + .thenReturn(CartDiscountValue.ofAbsolute(MoneyImpl.of(10, EUR))); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()).thenReturn(null); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final CartDiscountValue fiftyEuro = CartDiscountValue.ofAbsolute(MoneyImpl.of(50, EUR)); + when(newCartDiscountDraft.getValue()).thenReturn(fiftyEuro); - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - assertThat(changeValueUpdateAction).contains(ChangeValue.of(null)); - } + assertThat(changeValueUpdateAction).contains(ChangeValue.of(fiftyEuro)); + } - @Test - void buildChangeValueUpdateAction_WithNullNewAbsoluteAmounts_ShouldBuildUpdateAction() { - final CartDiscountValue value = - CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); - final List newAmounts = null; - final CartDiscountValue value2 = CartDiscountValue.ofAbsolute(newAmounts); + @Test + void buildChangeValueUpdateAction_WithNewDuplicatesWithMissingAmount_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()) + .thenReturn( + CartDiscountValue.ofAbsolute( + asList(MoneyImpl.of(10, EUR), MoneyImpl.of(50, EUR), MoneyImpl.of(30, EUR)))); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(value); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()).thenReturn(value2); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(value2)); - } - - @Test - void buildChangeValueUpdateAction_WithSameAbsoluteValues_ShouldNotBuildUpdateAction() { - final CartDiscountValue values = - CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); - final CartDiscountValue values2 = - CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(values); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()).thenReturn(values2); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).isNotPresent(); - } - - @Test - void buildChangeValueUpdateAction_WithSameValuesUsingDifferentMonetaryAmount_ShouldNotBuildUpdateAction() { - final CartDiscountValue values = - CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))); - final CartDiscountValue values2 = - CartDiscountValue.ofAbsolute(asList(Money.of(10, EUR), Money.of(10, USD))); - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(values); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()).thenReturn(values2); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).isNotPresent(); - } - - @Test - void buildChangeValueUpdateAction_WithDifferentAbsoluteValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(CartDiscountValue.ofAbsolute(MoneyImpl.of(10, EUR))); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final CartDiscountValue fiftyEuro = CartDiscountValue.ofAbsolute(MoneyImpl.of(50, EUR)); - when(newCartDiscountDraft.getValue()).thenReturn(fiftyEuro); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(fiftyEuro)); - } - - @Test - void buildChangeValueUpdateAction_WithNewDuplicatesWithMissingAmount_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(CartDiscountValue.ofAbsolute( - asList(MoneyImpl.of(10, EUR), MoneyImpl.of(50, EUR), MoneyImpl.of(30, EUR)))); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final CartDiscountValue newValue = CartDiscountValue.ofAbsolute( + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final CartDiscountValue newValue = + CartDiscountValue.ofAbsolute( asList(MoneyImpl.of(10, EUR), MoneyImpl.of(50, EUR), MoneyImpl.of(50, EUR))); - when(newCartDiscountDraft.getValue()).thenReturn(newValue); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(newValue)); - } - - @Test - void buildChangeValueUpdateAction_WithNewDuplicatesWithExtraAndMissingAmount_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn(CartDiscountValue.ofAbsolute( - asList(MoneyImpl.of(20, EUR), MoneyImpl.of(50, EUR), MoneyImpl.of(30, EUR)))); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final CartDiscountValue newValue = CartDiscountValue.ofAbsolute( + when(newCartDiscountDraft.getValue()).thenReturn(newValue); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction).contains(ChangeValue.of(newValue)); + } + + @Test + void + buildChangeValueUpdateAction_WithNewDuplicatesWithExtraAndMissingAmount_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()) + .thenReturn( + CartDiscountValue.ofAbsolute( + asList(MoneyImpl.of(20, EUR), MoneyImpl.of(50, EUR), MoneyImpl.of(30, EUR)))); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final CartDiscountValue newValue = + CartDiscountValue.ofAbsolute( asList(MoneyImpl.of(10, EUR), MoneyImpl.of(50, EUR), MoneyImpl.of(50, EUR))); - when(newCartDiscountDraft.getValue()).thenReturn(newValue); + when(newCartDiscountDraft.getValue()).thenReturn(newValue); - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - assertThat(changeValueUpdateAction).contains(ChangeValue.of(newValue)); - } + assertThat(changeValueUpdateAction).contains(ChangeValue.of(newValue)); + } - @Test - void buildChangeValueUpdateAction_WithSameAbsoluteAmountsWithDifferentOrder_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn( - CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD)))); + @Test + void + buildChangeValueUpdateAction_WithSameAbsoluteAmountsWithDifferentOrder_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()) + .thenReturn( + CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD)))); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final CartDiscountValue sameValuesWithDifferentOrder = - CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, USD), MoneyImpl.of(10, EUR))); - when(newCartDiscountDraft.getValue()).thenReturn(sameValuesWithDifferentOrder); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final CartDiscountValue sameValuesWithDifferentOrder = + CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, USD), MoneyImpl.of(10, EUR))); + when(newCartDiscountDraft.getValue()).thenReturn(sameValuesWithDifferentOrder); - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - assertThat(changeValueUpdateAction).isNotPresent(); - } + assertThat(changeValueUpdateAction).isNotPresent(); + } - @Test - void buildChangeValueUpdateAction_WithFewerAbsoluteAmounts_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn( - CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD)))); + @Test + void buildChangeValueUpdateAction_WithFewerAbsoluteAmounts_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()) + .thenReturn( + CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD)))); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()) - .thenReturn(CartDiscountValue.ofAbsolute(singletonList(MoneyImpl.of(10, EUR)))); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()) + .thenReturn(CartDiscountValue.ofAbsolute(singletonList(MoneyImpl.of(10, EUR)))); - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - assertThat(changeValueUpdateAction).contains( + assertThat(changeValueUpdateAction) + .contains( ChangeValue.of(CartDiscountValue.ofAbsolute(singletonList(MoneyImpl.of(10, EUR))))); - } - - @Test - void buildChangeValueUpdateAction_WithAdditionalAbsoluteAmounts_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn( - CartDiscountValue.ofAbsolute(singletonList(MoneyImpl.of(10, EUR)))); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()) - .thenReturn(CartDiscountValue.ofAbsolute( - asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD)))); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains(ChangeValue.of(CartDiscountValue.ofAbsolute( - asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))))); - } - - @Test - void buildChangeValueUpdateAction_WithSomeNullNewAbsoluteAmounts_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValue()).thenReturn( - CartDiscountValue.ofAbsolute(singletonList(MoneyImpl.of(10, USD)))); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValue()) - .thenReturn(CartDiscountValue.ofAbsolute( - asList(MoneyImpl.of(10, USD), null))); - - final Optional> changeValueUpdateAction = - buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeValueUpdateAction).contains( + } + + @Test + void buildChangeValueUpdateAction_WithAdditionalAbsoluteAmounts_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()) + .thenReturn(CartDiscountValue.ofAbsolute(singletonList(MoneyImpl.of(10, EUR)))); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()) + .thenReturn( + CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD)))); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction) + .contains( + ChangeValue.of( + CartDiscountValue.ofAbsolute( + asList(MoneyImpl.of(10, EUR), MoneyImpl.of(10, USD))))); + } + + @Test + void buildChangeValueUpdateAction_WithSomeNullNewAbsoluteAmounts_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValue()) + .thenReturn(CartDiscountValue.ofAbsolute(singletonList(MoneyImpl.of(10, USD)))); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValue()) + .thenReturn(CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, USD), null))); + + final Optional> changeValueUpdateAction = + buildChangeValueUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeValueUpdateAction) + .contains( ChangeValue.of(CartDiscountValue.ofAbsolute(asList(MoneyImpl.of(10, USD), null)))); - } - - @Test - void buildChangeCartPredicateUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getCartPredicate()) - .thenReturn("totalPrice = \"10.00 EUR\" and (shippingInfo.shippingMethodName = \"FEDEX\")"); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final String newCartPredicate = "1 = 1"; - when(newCartDiscountDraft.getCartPredicate()).thenReturn(newCartPredicate); - - final Optional> changeCartPredicateUpdateAction = - buildChangeCartPredicateUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeCartPredicateUpdateAction).contains(ChangeCartPredicate.of(newCartPredicate)); - } - - @Test - void buildChangeCartPredicateUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final String cartPredicate = "totalPrice = \"10.00 EUR\" and (shippingInfo.shippingMethodName = \"FEDEX\")"; - final String cartPredicate2 = "totalPrice = \"10.00 EUR\" and (shippingInfo.shippingMethodName = \"FEDEX\")"; - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getCartPredicate()).thenReturn(cartPredicate); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getCartPredicate()).thenReturn(cartPredicate2); - - final Optional> changePredicateUpdateAction = - buildChangeCartPredicateUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changePredicateUpdateAction).isNotPresent(); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentLineItemTargetValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()).thenReturn(LineItemsTarget.ofAll()); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final CartDiscountTarget cartDiscountTarget = LineItemsTarget.of("quantity > 0"); - when(newCartDiscountDraft.getTarget()).thenReturn(cartDiscountTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(cartDiscountTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithSameLineItemTargetValues_ShouldNotBuildUpdateAction() { - final CartDiscountTarget cartDiscountTarget = LineItemsTarget.of("quantity > 0"); - final CartDiscountTarget cartDiscountTarget2 = LineItemsTarget.of("quantity > 0"); - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()).thenReturn(cartDiscountTarget); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getTarget()).thenReturn(cartDiscountTarget2); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).isNotPresent(); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentCustomLineItemValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()).thenReturn(CustomLineItemsTarget.of("money = \"100 EUR\"")); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final CartDiscountTarget cartDiscountTarget = CustomLineItemsTarget.of("1 = 1"); - when(newCartDiscountDraft.getTarget()).thenReturn(cartDiscountTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(cartDiscountTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithSameCustomLineItemValues_ShouldNotBuildUpdateAction() { - final CustomLineItemsTarget customLineItemsTarget = CustomLineItemsTarget.of("money = \"100 EUR\""); - final CustomLineItemsTarget customLineItemsTarget2 = CustomLineItemsTarget.of("money = \"100 EUR\""); - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()).thenReturn(customLineItemsTarget); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getTarget()).thenReturn(customLineItemsTarget2); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).isNotPresent(); - } - - @Test - void buildChangeTargetUpdateAction_WithLineItemAndShippingTargetValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()).thenReturn(LineItemsTarget.ofAll()); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final CartDiscountTarget cartDiscountTarget = ShippingCostTarget.of(); - when(newCartDiscountDraft.getTarget()).thenReturn(cartDiscountTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(cartDiscountTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithBothShippingTargetValues_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()).thenReturn(ShippingCostTarget.of()); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getTarget()).thenReturn(ShippingCostTarget.of()); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).isNotPresent(); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentMultiBuyLineItemsTargetValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyLineItemsTarget.of("quantity > 0", 6L, 2L, SelectionMode.CHEAPEST)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyLineItemsTarget newTarget = - MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentTargetPredicate_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyLineItemsTarget.of("quantity > 1", 6L, 3L, SelectionMode.MOST_EXPENSIVE)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyLineItemsTarget newTarget = - MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentTriggerQuantity_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyLineItemsTarget.of("quantity > 0", 5L, 3L, SelectionMode.MOST_EXPENSIVE)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyLineItemsTarget newTarget = - MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + } - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } + @Test + void buildChangeCartPredicateUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getCartPredicate()) + .thenReturn("totalPrice = \"10.00 EUR\" and (shippingInfo.shippingMethodName = \"FEDEX\")"); - @Test - void buildChangeTargetUpdateAction_WithDifferentDiscountedQuantity_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyLineItemsTarget.of("quantity > 0", 6L, 2L, SelectionMode.MOST_EXPENSIVE)); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final String newCartPredicate = "1 = 1"; + when(newCartDiscountDraft.getCartPredicate()).thenReturn(newCartPredicate); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyLineItemsTarget newTarget = - MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + final Optional> changeCartPredicateUpdateAction = + buildChangeCartPredicateUpdateAction(oldCartDiscount, newCartDiscountDraft); - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentSelectionMode_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.CHEAPEST)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyLineItemsTarget newTarget = - MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentMaxOccurrence_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE, 1L)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyLineItemsTarget newTarget = - MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE, 2L); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithSameMultiBuyLineItemsTargetValues_ShouldNotBuildUpdateAction() { - final MultiBuyLineItemsTarget target = - MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - final MultiBuyLineItemsTarget target2 = - MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()).thenReturn(target); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getTarget()).thenReturn(target2); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).isNotPresent(); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentMultiBuyCustomLineItemsTargetValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 2L, SelectionMode.CHEAPEST)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyCustomLineItemsTarget newTarget = - MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentCustomTargetPredicate_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyCustomLineItemsTarget.of("quantity > 1", 6L, 3L, SelectionMode.MOST_EXPENSIVE)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyCustomLineItemsTarget newTarget = - MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } - - @Test - void buildChangeTargetUpdateAction_WithDifferentCustomTriggerQuantity_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyCustomLineItemsTarget.of("quantity > 0", 5L, 3L, SelectionMode.MOST_EXPENSIVE)); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyCustomLineItemsTarget newTarget = - MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); - - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } + assertThat(changeCartPredicateUpdateAction).contains(ChangeCartPredicate.of(newCartPredicate)); + } - @Test - void buildChangeTargetUpdateAction_WithDifferentCustomDiscountedQuantity_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 2L, SelectionMode.MOST_EXPENSIVE)); + @Test + void buildChangeCartPredicateUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final String cartPredicate = + "totalPrice = \"10.00 EUR\" and (shippingInfo.shippingMethodName = \"FEDEX\")"; + final String cartPredicate2 = + "totalPrice = \"10.00 EUR\" and (shippingInfo.shippingMethodName = \"FEDEX\")"; - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyCustomLineItemsTarget newTarget = - MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getCartPredicate()).thenReturn(cartPredicate); - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getCartPredicate()).thenReturn(cartPredicate2); - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } + final Optional> changePredicateUpdateAction = + buildChangeCartPredicateUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeTargetUpdateAction_WithDifferentCustomSelectionMode_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.CHEAPEST)); + assertThat(changePredicateUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyCustomLineItemsTarget newTarget = - MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + @Test + void buildChangeTargetUpdateAction_WithDifferentLineItemTargetValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()).thenReturn(LineItemsTarget.ofAll()); - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final CartDiscountTarget cartDiscountTarget = LineItemsTarget.of("quantity > 0"); + when(newCartDiscountDraft.getTarget()).thenReturn(cartDiscountTarget); - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeTargetUpdateAction_WithDifferentCustomMaxOccurrence_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()) - .thenReturn(MultiBuyCustomLineItemsTarget - .of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE, 1L)); + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(cartDiscountTarget)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final MultiBuyCustomLineItemsTarget newTarget = - MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE, 2L); - when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + @Test + void buildChangeTargetUpdateAction_WithSameLineItemTargetValues_ShouldNotBuildUpdateAction() { + final CartDiscountTarget cartDiscountTarget = LineItemsTarget.of("quantity > 0"); + final CartDiscountTarget cartDiscountTarget2 = LineItemsTarget.of("quantity > 0"); - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()).thenReturn(cartDiscountTarget); - assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); - } + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getTarget()).thenReturn(cartDiscountTarget2); - @Test - void buildChangeTargetUpdateAction_WithSameMultiBuyCustomLineItemsTargetValues_ShouldNotBuildUpdateAction() { - final MultiBuyCustomLineItemsTarget target = - MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - final MultiBuyCustomLineItemsTarget target2 = - MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()).thenReturn(target); + assertThat(changeTargetUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getTarget()).thenReturn(target2); + @Test + void buildChangeTargetUpdateAction_WithDifferentCustomLineItemValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()).thenReturn(CustomLineItemsTarget.of("money = \"100 EUR\"")); - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final CartDiscountTarget cartDiscountTarget = CustomLineItemsTarget.of("1 = 1"); + when(newCartDiscountDraft.getTarget()).thenReturn(cartDiscountTarget); - assertThat(changeTargetUpdateAction).isNotPresent(); - } + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeTargetUpdateAction_WithBothNullTargetValues_ShouldNotBuildUpdateAction() { - //this just can be happen if the value type is gift line item - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getTarget()).thenReturn(null); + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(cartDiscountTarget)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getTarget()).thenReturn(null); + @Test + void buildChangeTargetUpdateAction_WithSameCustomLineItemValues_ShouldNotBuildUpdateAction() { + final CustomLineItemsTarget customLineItemsTarget = + CustomLineItemsTarget.of("money = \"100 EUR\""); + final CustomLineItemsTarget customLineItemsTarget2 = + CustomLineItemsTarget.of("money = \"100 EUR\""); - final Optional> changeTargetUpdateAction = - buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()).thenReturn(customLineItemsTarget); - assertThat(changeTargetUpdateAction).isNotPresent(); - } + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getTarget()).thenReturn(customLineItemsTarget2); - @Test - void buildChangeIsActiveUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.isActive()).thenReturn(false); + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.isActive()).thenReturn(true); + assertThat(changeTargetUpdateAction).isNotPresent(); + } - final Optional> changeIsActiveUpdateAction = - buildChangeIsActiveUpdateAction(oldCartDiscount, newCartDiscountDraft); + @Test + void buildChangeTargetUpdateAction_WithLineItemAndShippingTargetValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()).thenReturn(LineItemsTarget.ofAll()); - assertThat(changeIsActiveUpdateAction).contains(ChangeIsActive.of(true)); - } + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final CartDiscountTarget cartDiscountTarget = ShippingCostTarget.of(); + when(newCartDiscountDraft.getTarget()).thenReturn(cartDiscountTarget); - @Test - void buildChangeIsActiveUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.isActive()).thenReturn(false); + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.isActive()).thenReturn(false); + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(cartDiscountTarget)); + } - final Optional> changeIsActiveUpdateAction = - buildChangeIsActiveUpdateAction(oldCartDiscount, newCartDiscountDraft); + @Test + void buildChangeTargetUpdateAction_WithBothShippingTargetValues_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()).thenReturn(ShippingCostTarget.of()); - assertThat(changeIsActiveUpdateAction).isNotPresent(); - } + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getTarget()).thenReturn(ShippingCostTarget.of()); - @Test - void buildChangeIsActiveUpdateAction_WithOnlyNullNewIsActiveValues_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.isActive()).thenReturn(true); + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.isActive()).thenReturn(null); + assertThat(changeTargetUpdateAction).isNotPresent(); + } + + @Test + void + buildChangeTargetUpdateAction_WithDifferentMultiBuyLineItemsTargetValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn(MultiBuyLineItemsTarget.of("quantity > 0", 6L, 2L, SelectionMode.CHEAPEST)); - final Optional> changeIsActiveUpdateAction = - buildChangeIsActiveUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyLineItemsTarget newTarget = + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void buildChangeTargetUpdateAction_WithDifferentTargetPredicate_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyLineItemsTarget.of("quantity > 1", 6L, 3L, SelectionMode.MOST_EXPENSIVE)); - assertThat(changeIsActiveUpdateAction).isNotPresent(); - } + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyLineItemsTarget newTarget = + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void buildChangeTargetUpdateAction_WithDifferentTriggerQuantity_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyLineItemsTarget.of("quantity > 0", 5L, 3L, SelectionMode.MOST_EXPENSIVE)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyLineItemsTarget newTarget = + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void buildChangeTargetUpdateAction_WithDifferentDiscountedQuantity_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 2L, SelectionMode.MOST_EXPENSIVE)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyLineItemsTarget newTarget = + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void buildChangeTargetUpdateAction_WithDifferentSelectionMode_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn(MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.CHEAPEST)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyLineItemsTarget newTarget = + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void buildChangeTargetUpdateAction_WithDifferentMaxOccurrence_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE, 1L)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyLineItemsTarget newTarget = + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE, 2L); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void + buildChangeTargetUpdateAction_WithSameMultiBuyLineItemsTargetValues_ShouldNotBuildUpdateAction() { + final MultiBuyLineItemsTarget target = + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + final MultiBuyLineItemsTarget target2 = + MultiBuyLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()).thenReturn(target); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getTarget()).thenReturn(target2); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).isNotPresent(); + } + + @Test + void + buildChangeTargetUpdateAction_WithDifferentMultiBuyCustomLineItemsTargetValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 2L, SelectionMode.CHEAPEST)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyCustomLineItemsTarget newTarget = + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void buildChangeTargetUpdateAction_WithDifferentCustomTargetPredicate_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyCustomLineItemsTarget.of("quantity > 1", 6L, 3L, SelectionMode.MOST_EXPENSIVE)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyCustomLineItemsTarget newTarget = + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void buildChangeTargetUpdateAction_WithDifferentCustomTriggerQuantity_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyCustomLineItemsTarget.of("quantity > 0", 5L, 3L, SelectionMode.MOST_EXPENSIVE)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyCustomLineItemsTarget newTarget = + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void + buildChangeTargetUpdateAction_WithDifferentCustomDiscountedQuantity_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 2L, SelectionMode.MOST_EXPENSIVE)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyCustomLineItemsTarget newTarget = + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void buildChangeTargetUpdateAction_WithDifferentCustomSelectionMode_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.CHEAPEST)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyCustomLineItemsTarget newTarget = + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void buildChangeTargetUpdateAction_WithDifferentCustomMaxOccurrence_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()) + .thenReturn( + MultiBuyCustomLineItemsTarget.of( + "quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE, 1L)); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final MultiBuyCustomLineItemsTarget newTarget = + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE, 2L); + when(newCartDiscountDraft.getTarget()).thenReturn(newTarget); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).contains(ChangeTarget.of(newTarget)); + } + + @Test + void + buildChangeTargetUpdateAction_WithSameMultiBuyCustomLineItemsTargetValues_ShouldNotBuildUpdateAction() { + final MultiBuyCustomLineItemsTarget target = + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); + final MultiBuyCustomLineItemsTarget target2 = + MultiBuyCustomLineItemsTarget.of("quantity > 0", 6L, 3L, SelectionMode.MOST_EXPENSIVE); - @Test - void buildChangeIsActiveUpdateAction_WithOnlyNullNewIsActiveAndFalseOldIsActive_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.isActive()).thenReturn(false); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()).thenReturn(target); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getTarget()).thenReturn(target2); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.isActive()).thenReturn(null); + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(changeTargetUpdateAction).isNotPresent(); + } - final Optional> changeIsActiveUpdateAction = - buildChangeIsActiveUpdateAction(oldCartDiscount, newCartDiscountDraft); + @Test + void buildChangeTargetUpdateAction_WithBothNullTargetValues_ShouldNotBuildUpdateAction() { + // this just can be happen if the value type is gift line item + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getTarget()).thenReturn(null); - assertThat(changeIsActiveUpdateAction).contains(ChangeIsActive.of(true)); - } + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getTarget()).thenReturn(null); + + final Optional> changeTargetUpdateAction = + buildChangeTargetUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getName()).thenReturn(LocalizedString.of(Locale.ENGLISH, "cart-discount-1")); + assertThat(changeTargetUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final LocalizedString name = LocalizedString.of(Locale.ENGLISH, "newName"); - when(newCartDiscountDraft.getName()).thenReturn(name); + @Test + void buildChangeIsActiveUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.isActive()).thenReturn(false); - final Optional> changeNameUpdateAction = - buildChangeNameUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.isActive()).thenReturn(true); - assertThat(changeNameUpdateAction).contains(ChangeName.of(name)); - } + final Optional> changeIsActiveUpdateAction = + buildChangeIsActiveUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final LocalizedString name = LocalizedString.of(Locale.ENGLISH, "name"); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getName()).thenReturn(name); + assertThat(changeIsActiveUpdateAction).contains(ChangeIsActive.of(true)); + } - final LocalizedString name2 = LocalizedString.of(Locale.ENGLISH, "name"); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getName()).thenReturn(name2); + @Test + void buildChangeIsActiveUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.isActive()).thenReturn(false); - final Optional> changeNameUpdateAction = - buildChangeNameUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.isActive()).thenReturn(false); - assertThat(changeNameUpdateAction).isNotPresent(); - } + final Optional> changeIsActiveUpdateAction = + buildChangeIsActiveUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getDescription()) - .thenReturn(LocalizedString.of(Locale.ENGLISH, "cart-discount-1-desc")); + assertThat(changeIsActiveUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final LocalizedString description = LocalizedString.of(Locale.ENGLISH, "new-description"); - when(newCartDiscountDraft.getDescription()).thenReturn(description); + @Test + void buildChangeIsActiveUpdateAction_WithOnlyNullNewIsActiveValues_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.isActive()).thenReturn(true); - final Optional> setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.isActive()).thenReturn(null); - assertThat(setDescriptionUpdateAction).contains(SetDescription.of(description)); - } + final Optional> changeIsActiveUpdateAction = + buildChangeIsActiveUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final LocalizedString description = LocalizedString.of(Locale.ENGLISH, "cart-discount-1-desc"); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getDescription()).thenReturn(description); + assertThat(changeIsActiveUpdateAction).isNotPresent(); + } - final LocalizedString description2 = LocalizedString.of(Locale.ENGLISH, "cart-discount-1-desc"); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getDescription()).thenReturn(description2); + @Test + void + buildChangeIsActiveUpdateAction_WithOnlyNullNewIsActiveAndFalseOldIsActive_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.isActive()).thenReturn(false); - final Optional> setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.isActive()).thenReturn(null); - assertThat(setDescriptionUpdateAction).isNotPresent(); - } + final Optional> changeIsActiveUpdateAction = + buildChangeIsActiveUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetDescriptionUpdateAction_WithOnlyNullNewDescription_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getDescription()) - .thenReturn(LocalizedString.of(Locale.ENGLISH, "cart-discount-1-desc")); + assertThat(changeIsActiveUpdateAction).contains(ChangeIsActive.of(true)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getDescription()).thenReturn(null); + @Test + void buildChangeNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getName()) + .thenReturn(LocalizedString.of(Locale.ENGLISH, "cart-discount-1")); - final Optional> setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final LocalizedString name = LocalizedString.of(Locale.ENGLISH, "newName"); + when(newCartDiscountDraft.getName()).thenReturn(name); - assertThat(setDescriptionUpdateAction).contains(SetDescription.of(null)); - } + final Optional> changeNameUpdateAction = + buildChangeNameUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetDescriptionUpdateAction_WithOnlyNullOldDescription_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getDescription()).thenReturn(null); + assertThat(changeNameUpdateAction).contains(ChangeName.of(name)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final LocalizedString newDesc = LocalizedString.of(Locale.ENGLISH, "new-desc"); - when(newCartDiscountDraft.getDescription()).thenReturn(newDesc); + @Test + void buildChangeNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final LocalizedString name = LocalizedString.of(Locale.ENGLISH, "name"); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getName()).thenReturn(name); - final Optional> setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); + final LocalizedString name2 = LocalizedString.of(Locale.ENGLISH, "name"); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getName()).thenReturn(name2); - assertThat(setDescriptionUpdateAction).contains(SetDescription.of(newDesc)); - } + final Optional> changeNameUpdateAction = + buildChangeNameUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetDescriptionUpdateAction_WithBothNullDescriptionValues_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getDescription()).thenReturn(null); + assertThat(changeNameUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getDescription()).thenReturn(null); + @Test + void buildSetDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getDescription()) + .thenReturn(LocalizedString.of(Locale.ENGLISH, "cart-discount-1-desc")); - final Optional> setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final LocalizedString description = LocalizedString.of(Locale.ENGLISH, "new-description"); + when(newCartDiscountDraft.getDescription()).thenReturn(description); - assertThat(setDescriptionUpdateAction).isNotPresent(); - } + final Optional> setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeSortOrderUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getSortOrder()).thenReturn("0.1"); + assertThat(setDescriptionUpdateAction).contains(SetDescription.of(description)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final String sortOrder = "0.3"; - when(newCartDiscountDraft.getSortOrder()).thenReturn(sortOrder); + @Test + void buildSetDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final LocalizedString description = LocalizedString.of(Locale.ENGLISH, "cart-discount-1-desc"); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getDescription()).thenReturn(description); - final Optional> changeChangeSortOrderUpdateAction = - buildChangeSortOrderUpdateAction(oldCartDiscount, newCartDiscountDraft); + final LocalizedString description2 = LocalizedString.of(Locale.ENGLISH, "cart-discount-1-desc"); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getDescription()).thenReturn(description2); - assertThat(changeChangeSortOrderUpdateAction).contains(ChangeSortOrder.of(sortOrder)); - } + final Optional> setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeSortOrderUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final String sortOrder = "0.1"; - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getSortOrder()).thenReturn(sortOrder); + assertThat(setDescriptionUpdateAction).isNotPresent(); + } - final String sortOrder2 = "0.1"; - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getSortOrder()).thenReturn(sortOrder2); + @Test + void buildSetDescriptionUpdateAction_WithOnlyNullNewDescription_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getDescription()) + .thenReturn(LocalizedString.of(Locale.ENGLISH, "cart-discount-1-desc")); - final Optional> changeChangeSortOrderUpdateAction = - buildChangeSortOrderUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getDescription()).thenReturn(null); - assertThat(changeChangeSortOrderUpdateAction).isNotPresent(); - } + final Optional> setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeRequiresDiscountCodeUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.isRequiringDiscountCode()).thenReturn(true); + assertThat(setDescriptionUpdateAction).contains(SetDescription.of(null)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.isRequiresDiscountCode()).thenReturn(false); + @Test + void buildSetDescriptionUpdateAction_WithOnlyNullOldDescription_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getDescription()).thenReturn(null); - final Optional> changeRequiresDiscountCodeUpdateAction = - buildChangeRequiresDiscountCodeUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final LocalizedString newDesc = LocalizedString.of(Locale.ENGLISH, "new-desc"); + when(newCartDiscountDraft.getDescription()).thenReturn(newDesc); - assertThat(changeRequiresDiscountCodeUpdateAction).contains(ChangeRequiresDiscountCode.of(false)); - } + final Optional> setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeRequiresDiscountCodeUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.isRequiringDiscountCode()).thenReturn(true); + assertThat(setDescriptionUpdateAction).contains(SetDescription.of(newDesc)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.isRequiresDiscountCode()).thenReturn(true); + @Test + void buildSetDescriptionUpdateAction_WithBothNullDescriptionValues_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getDescription()).thenReturn(null); - final Optional> changeRequiresDiscountCodeUpdateAction = - buildChangeRequiresDiscountCodeUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getDescription()).thenReturn(null); - assertThat(changeRequiresDiscountCodeUpdateAction).isNotPresent(); - } + final Optional> setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeRequiresDiscountCodeUpdateAction_WithOnlyNullNewRequiresDiscountCode_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.isRequiringDiscountCode()).thenReturn(false); + assertThat(setDescriptionUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.isRequiresDiscountCode()).thenReturn(null); + @Test + void buildChangeSortOrderUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getSortOrder()).thenReturn("0.1"); - final Optional> changeRequiresDiscountCodeUpdateAction = - buildChangeRequiresDiscountCodeUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final String sortOrder = "0.3"; + when(newCartDiscountDraft.getSortOrder()).thenReturn(sortOrder); - assertThat(changeRequiresDiscountCodeUpdateAction).isNotPresent(); - } + final Optional> changeChangeSortOrderUpdateAction = + buildChangeSortOrderUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeRequiresDiscountCodeUpdateAction_WithOnlyNullNewReqDisCodeAndTrueOldVal_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.isRequiringDiscountCode()).thenReturn(true); + assertThat(changeChangeSortOrderUpdateAction).contains(ChangeSortOrder.of(sortOrder)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.isRequiresDiscountCode()).thenReturn(null); + @Test + void buildChangeSortOrderUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final String sortOrder = "0.1"; + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getSortOrder()).thenReturn(sortOrder); - final Optional> changeRequiresDiscountCodeUpdateAction = - buildChangeRequiresDiscountCodeUpdateAction(oldCartDiscount, newCartDiscountDraft); + final String sortOrder2 = "0.1"; + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getSortOrder()).thenReturn(sortOrder2); - assertThat(changeRequiresDiscountCodeUpdateAction).contains(ChangeRequiresDiscountCode.of(false)); - } + final Optional> changeChangeSortOrderUpdateAction = + buildChangeSortOrderUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeStackingModeUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getStackingMode()).thenReturn(StackingMode.STOP_AFTER_THIS_DISCOUNT); + assertThat(changeChangeSortOrderUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getStackingMode()).thenReturn(StackingMode.STACKING); + @Test + void buildChangeRequiresDiscountCodeUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.isRequiringDiscountCode()).thenReturn(true); - final Optional> changeStackingModeUpdateAction = - buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.isRequiresDiscountCode()).thenReturn(false); - assertThat(changeStackingModeUpdateAction) - .contains(ChangeStackingMode.of(StackingMode.STACKING)); - } + final Optional> changeRequiresDiscountCodeUpdateAction = + buildChangeRequiresDiscountCodeUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeStackingModeUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getStackingMode()).thenReturn(StackingMode.STOP_AFTER_THIS_DISCOUNT); + assertThat(changeRequiresDiscountCodeUpdateAction) + .contains(ChangeRequiresDiscountCode.of(false)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getStackingMode()).thenReturn(StackingMode.STOP_AFTER_THIS_DISCOUNT); + @Test + void buildChangeRequiresDiscountCodeUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.isRequiringDiscountCode()).thenReturn(true); - final Optional> changeStackingModeUpdateAction = - buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.isRequiresDiscountCode()).thenReturn(true); - assertThat(changeStackingModeUpdateAction).isNotPresent(); - } + final Optional> changeRequiresDiscountCodeUpdateAction = + buildChangeRequiresDiscountCodeUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeStackingModeUpdateAction_WithOnlyNullNewStackingMode_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getStackingMode()).thenReturn(StackingMode.STACKING); + assertThat(changeRequiresDiscountCodeUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getStackingMode()).thenReturn(null); + @Test + void + buildChangeRequiresDiscountCodeUpdateAction_WithOnlyNullNewRequiresDiscountCode_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.isRequiringDiscountCode()).thenReturn(false); - final Optional> changeStackingModeUpdateAction = - buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.isRequiresDiscountCode()).thenReturn(null); - assertThat(changeStackingModeUpdateAction).isNotPresent(); - } + final Optional> changeRequiresDiscountCodeUpdateAction = + buildChangeRequiresDiscountCodeUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildChangeStackingModeUpdateAction_WithOnlyNullNewModeAndStopAfterDiscountOldMode_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getStackingMode()).thenReturn(StackingMode.STOP_AFTER_THIS_DISCOUNT); + assertThat(changeRequiresDiscountCodeUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getStackingMode()).thenReturn(null); + @Test + void + buildChangeRequiresDiscountCodeUpdateAction_WithOnlyNullNewReqDisCodeAndTrueOldVal_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.isRequiringDiscountCode()).thenReturn(true); - final Optional> changeStackingModeUpdateAction = - buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.isRequiresDiscountCode()).thenReturn(null); - assertThat(changeStackingModeUpdateAction).contains(ChangeStackingMode.of(StackingMode.STACKING)); - } + final Optional> changeRequiresDiscountCodeUpdateAction = + buildChangeRequiresDiscountCodeUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidFromUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); + assertThat(changeRequiresDiscountCodeUpdateAction) + .contains(ChangeRequiresDiscountCode.of(false)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final ZonedDateTime now = ZonedDateTime.now(); - when(newCartDiscountDraft.getValidFrom()).thenReturn(now); + @Test + void buildChangeStackingModeUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getStackingMode()).thenReturn(StackingMode.STOP_AFTER_THIS_DISCOUNT); - final Optional> setValidFromUpdateAction = - buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getStackingMode()).thenReturn(StackingMode.STACKING); - assertThat(setValidFromUpdateAction).contains(SetValidFrom.of(now)); - } + final Optional> changeStackingModeUpdateAction = + buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidFromUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final ZonedDateTime validFrom = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(validFrom); + assertThat(changeStackingModeUpdateAction) + .contains(ChangeStackingMode.of(StackingMode.STACKING)); + } - final ZonedDateTime validFrom2 = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidFrom()).thenReturn(validFrom2); + @Test + void buildChangeStackingModeUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getStackingMode()).thenReturn(StackingMode.STOP_AFTER_THIS_DISCOUNT); - final Optional> setValidFromUpdateAction = - buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getStackingMode()).thenReturn(StackingMode.STOP_AFTER_THIS_DISCOUNT); - assertThat(setValidFromUpdateAction).isNotPresent(); - } + final Optional> changeStackingModeUpdateAction = + buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidFromUpdateAction_WithOnlyNullNewSetValidFromDate_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); + assertThat(changeStackingModeUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidFrom()).thenReturn(null); + @Test + void + buildChangeStackingModeUpdateAction_WithOnlyNullNewStackingMode_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getStackingMode()).thenReturn(StackingMode.STACKING); - final Optional> setValidFromUpdateAction = - buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getStackingMode()).thenReturn(null); - assertThat(setValidFromUpdateAction).contains(SetValidFrom.of(null)); - } + final Optional> changeStackingModeUpdateAction = + buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidFromUpdateAction_WithOnlyNullOldSetValidFromDate_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(null); + assertThat(changeStackingModeUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidFrom()).thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); + @Test + void + buildChangeStackingModeUpdateAction_WithOnlyNullNewModeAndStopAfterDiscountOldMode_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getStackingMode()).thenReturn(StackingMode.STOP_AFTER_THIS_DISCOUNT); - final Optional> setValidFromUpdateAction = - buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getStackingMode()).thenReturn(null); - assertThat(setValidFromUpdateAction).contains(SetValidFrom.of(ZonedDateTime.parse("2019-04-30T22:00:00.000Z"))); - } + final Optional> changeStackingModeUpdateAction = + buildChangeStackingModeUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidFromUpdateAction_WithBothNullSetValidFromDates_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(null); + assertThat(changeStackingModeUpdateAction) + .contains(ChangeStackingMode.of(StackingMode.STACKING)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidFrom()).thenReturn(null); + @Test + void buildSetValidFromUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()) + .thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); - final Optional> setValidFromUpdateAction = - buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final ZonedDateTime now = ZonedDateTime.now(); + when(newCartDiscountDraft.getValidFrom()).thenReturn(now); - assertThat(setValidFromUpdateAction).isNotPresent(); - } + final Optional> setValidFromUpdateAction = + buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidUntilUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + assertThat(setValidFromUpdateAction).contains(SetValidFrom.of(now)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final ZonedDateTime now = ZonedDateTime.now(); - when(newCartDiscountDraft.getValidUntil()).thenReturn(now); + @Test + void buildSetValidFromUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final ZonedDateTime validFrom = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()).thenReturn(validFrom); - final Optional> setValidUntilUpdateAction = - buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); + final ZonedDateTime validFrom2 = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidFrom()).thenReturn(validFrom2); - assertThat(setValidUntilUpdateAction).contains(SetValidUntil.of(now)); - } + final Optional> setValidFromUpdateAction = + buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidUntilUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final ZonedDateTime validUntil = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidUntil()).thenReturn(validUntil); + assertThat(setValidFromUpdateAction).isNotPresent(); + } - final ZonedDateTime validUntil2 = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidUntil()).thenReturn(validUntil2); + @Test + void buildSetValidFromUpdateAction_WithOnlyNullNewSetValidFromDate_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()) + .thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); - final Optional> setValidUntilUpdateAction = - buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidFrom()).thenReturn(null); - assertThat(setValidUntilUpdateAction).isNotPresent(); - } + final Optional> setValidFromUpdateAction = + buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidUntilUpdateAction_WithOnlyNullNewSetValidUntilDate_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidUntil()).thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + assertThat(setValidFromUpdateAction).contains(SetValidFrom.of(null)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidUntil()).thenReturn(null); + @Test + void buildSetValidFromUpdateAction_WithOnlyNullOldSetValidFromDate_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()).thenReturn(null); - final Optional> setValidUntilUpdateAction = - buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidFrom()) + .thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); - assertThat(setValidUntilUpdateAction).contains(SetValidUntil.of(null)); - } + final Optional> setValidFromUpdateAction = + buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidUntilUpdateAction_WithOnlyNullOldSetValidUntilDate_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidUntil()).thenReturn(null); + assertThat(setValidFromUpdateAction) + .contains(SetValidFrom.of(ZonedDateTime.parse("2019-04-30T22:00:00.000Z"))); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidUntil()).thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + @Test + void buildSetValidFromUpdateAction_WithBothNullSetValidFromDates_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()).thenReturn(null); - final Optional> setValidUntilUpdateAction = - buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidFrom()).thenReturn(null); - assertThat(setValidUntilUpdateAction) - .contains(SetValidUntil.of(ZonedDateTime.parse("2019-05-30T22:00:00.000Z"))); - } + final Optional> setValidFromUpdateAction = + buildSetValidFromUpdateAction(oldCartDiscount, newCartDiscountDraft); - @Test - void buildSetValidUntilUpdateAction_WithBothNullSetValidUntilDates_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidUntil()).thenReturn(null); + assertThat(setValidFromUpdateAction).isNotPresent(); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidUntil()).thenReturn(null); + @Test + void buildSetValidUntilUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()) + .thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final ZonedDateTime now = ZonedDateTime.now(); + when(newCartDiscountDraft.getValidUntil()).thenReturn(now); + + final Optional> setValidUntilUpdateAction = + buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); - final Optional> setValidUntilUpdateAction = - buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(setValidUntilUpdateAction).isNotPresent(); - } - - @Test - void buildSetValidDatesUpdateAction_WithDifferentValidFromDate_ShouldBuildSetValidFromUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); - when(oldCartDiscount.getValidUntil()).thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + assertThat(setValidUntilUpdateAction).contains(SetValidUntil.of(now)); + } - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final ZonedDateTime differentValidFromDate = ZonedDateTime.now(); - when(newCartDiscountDraft.getValidFrom()).thenReturn(differentValidFromDate); - when(newCartDiscountDraft.getValidUntil()).thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); - - final Optional> setValidFromUpdateAction = - buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(setValidFromUpdateAction).contains(SetValidFrom.of(differentValidFromDate)); - } - - @Test - void buildSetValidDatesUpdateAction_WithDifferentValidUntilDate_ShouldBuildSetValidUntilUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); - when(oldCartDiscount.getValidUntil()).thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidFrom()).thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); - final ZonedDateTime differentValidUntilDate = ZonedDateTime.now(); - when(newCartDiscountDraft.getValidUntil()).thenReturn(differentValidUntilDate); - - final Optional> setValidUntilUpdateAction = - buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(setValidUntilUpdateAction).contains(SetValidUntil.of(differentValidUntilDate)); - } - - @Test - void buildSetValidDatesUpdateAction_WithDifferentDates_ShouldBuildSetValidFromAndUntilUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); - when(oldCartDiscount.getValidUntil()).thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - final ZonedDateTime differentValidFromDate = ZonedDateTime.now(); - when(newCartDiscountDraft.getValidFrom()).thenReturn(differentValidFromDate); - final ZonedDateTime differentValidUntilDate = ZonedDateTime.now(); - when(newCartDiscountDraft.getValidUntil()).thenReturn(differentValidUntilDate); - - final Optional> setValidUntilUpdateAction = - buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(setValidUntilUpdateAction) - .contains(SetValidFromAndUntil.of(differentValidFromDate, differentValidUntilDate)); - } - - @Test - void buildSetValidDatesUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final ZonedDateTime validFrom = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); - final ZonedDateTime validUntil = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidFrom()).thenReturn(validFrom); - when(oldCartDiscount.getValidUntil()).thenReturn(validUntil); - - final ZonedDateTime validFrom2 = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); - final ZonedDateTime validUntil2 = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidFrom()).thenReturn(validFrom2); - when(newCartDiscountDraft.getValidUntil()).thenReturn(validUntil2); - - final Optional> setValidDatesUpdateAction = - buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(setValidDatesUpdateAction).isNotPresent(); - } - - @Test - void buildSetValidDatesUpdateAction_WithOnlyNullOldDates_ShouldBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidUntil()).thenReturn(null); - when(oldCartDiscount.getValidFrom()).thenReturn(null); - - final ZonedDateTime validFrom = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); - final ZonedDateTime validUntil = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidFrom()).thenReturn(validFrom); - when(newCartDiscountDraft.getValidUntil()).thenReturn(validUntil); - - final Optional> setValidUntilUpdateAction = - buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(setValidUntilUpdateAction).contains(SetValidFromAndUntil.of(validFrom, validUntil)); - } - - @Test - void buildSetValidDatesUpdateAction_WithOnlyNullNewDates_ShouldBuildUpdateAction() { - final ZonedDateTime validFrom = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); - final ZonedDateTime validUntil = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidUntil()).thenReturn(validFrom); - when(oldCartDiscount.getValidFrom()).thenReturn(validUntil); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidUntil()).thenReturn(null); - when(newCartDiscountDraft.getValidFrom()).thenReturn(null); - - final Optional> setValidUntilUpdateAction = - buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(setValidUntilUpdateAction).contains(SetValidFromAndUntil.of(null, null)); - } - - @Test - void buildSetValidDatesUpdateAction_WithBothNullValidUntilAndFromDates_ShouldNotBuildUpdateAction() { - final CartDiscount oldCartDiscount = mock(CartDiscount.class); - when(oldCartDiscount.getValidUntil()).thenReturn(null); - when(oldCartDiscount.getValidFrom()).thenReturn(null); - - final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); - when(newCartDiscountDraft.getValidUntil()).thenReturn(null); - when(newCartDiscountDraft.getValidFrom()).thenReturn(null); - - final Optional> setValidUntilUpdateAction = - buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); - - assertThat(setValidUntilUpdateAction).isNotPresent(); - } + @Test + void buildSetValidUntilUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final ZonedDateTime validUntil = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidUntil()).thenReturn(validUntil); + + final ZonedDateTime validUntil2 = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidUntil()).thenReturn(validUntil2); + + final Optional> setValidUntilUpdateAction = + buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidUntilUpdateAction).isNotPresent(); + } + + @Test + void buildSetValidUntilUpdateAction_WithOnlyNullNewSetValidUntilDate_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidUntil()) + .thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidUntil()).thenReturn(null); + + final Optional> setValidUntilUpdateAction = + buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidUntilUpdateAction).contains(SetValidUntil.of(null)); + } + + @Test + void buildSetValidUntilUpdateAction_WithOnlyNullOldSetValidUntilDate_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidUntil()).thenReturn(null); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidUntil()) + .thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + + final Optional> setValidUntilUpdateAction = + buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidUntilUpdateAction) + .contains(SetValidUntil.of(ZonedDateTime.parse("2019-05-30T22:00:00.000Z"))); + } + + @Test + void buildSetValidUntilUpdateAction_WithBothNullSetValidUntilDates_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidUntil()).thenReturn(null); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidUntil()).thenReturn(null); + + final Optional> setValidUntilUpdateAction = + buildSetValidUntilUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidUntilUpdateAction).isNotPresent(); + } + + @Test + void + buildSetValidDatesUpdateAction_WithDifferentValidFromDate_ShouldBuildSetValidFromUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()) + .thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); + when(oldCartDiscount.getValidUntil()) + .thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final ZonedDateTime differentValidFromDate = ZonedDateTime.now(); + when(newCartDiscountDraft.getValidFrom()).thenReturn(differentValidFromDate); + when(newCartDiscountDraft.getValidUntil()) + .thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + + final Optional> setValidFromUpdateAction = + buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidFromUpdateAction).contains(SetValidFrom.of(differentValidFromDate)); + } + + @Test + void + buildSetValidDatesUpdateAction_WithDifferentValidUntilDate_ShouldBuildSetValidUntilUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()) + .thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); + when(oldCartDiscount.getValidUntil()) + .thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidFrom()) + .thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); + final ZonedDateTime differentValidUntilDate = ZonedDateTime.now(); + when(newCartDiscountDraft.getValidUntil()).thenReturn(differentValidUntilDate); + + final Optional> setValidUntilUpdateAction = + buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidUntilUpdateAction).contains(SetValidUntil.of(differentValidUntilDate)); + } + + @Test + void + buildSetValidDatesUpdateAction_WithDifferentDates_ShouldBuildSetValidFromAndUntilUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()) + .thenReturn(ZonedDateTime.parse("2019-04-30T22:00:00.000Z")); + when(oldCartDiscount.getValidUntil()) + .thenReturn(ZonedDateTime.parse("2019-05-30T22:00:00.000Z")); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + final ZonedDateTime differentValidFromDate = ZonedDateTime.now(); + when(newCartDiscountDraft.getValidFrom()).thenReturn(differentValidFromDate); + final ZonedDateTime differentValidUntilDate = ZonedDateTime.now(); + when(newCartDiscountDraft.getValidUntil()).thenReturn(differentValidUntilDate); + + final Optional> setValidUntilUpdateAction = + buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidUntilUpdateAction) + .contains(SetValidFromAndUntil.of(differentValidFromDate, differentValidUntilDate)); + } + + @Test + void buildSetValidDatesUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final ZonedDateTime validFrom = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); + final ZonedDateTime validUntil = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidFrom()).thenReturn(validFrom); + when(oldCartDiscount.getValidUntil()).thenReturn(validUntil); + + final ZonedDateTime validFrom2 = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); + final ZonedDateTime validUntil2 = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidFrom()).thenReturn(validFrom2); + when(newCartDiscountDraft.getValidUntil()).thenReturn(validUntil2); + + final Optional> setValidDatesUpdateAction = + buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidDatesUpdateAction).isNotPresent(); + } + + @Test + void buildSetValidDatesUpdateAction_WithOnlyNullOldDates_ShouldBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidUntil()).thenReturn(null); + when(oldCartDiscount.getValidFrom()).thenReturn(null); + + final ZonedDateTime validFrom = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); + final ZonedDateTime validUntil = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidFrom()).thenReturn(validFrom); + when(newCartDiscountDraft.getValidUntil()).thenReturn(validUntil); + + final Optional> setValidUntilUpdateAction = + buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidUntilUpdateAction).contains(SetValidFromAndUntil.of(validFrom, validUntil)); + } + + @Test + void buildSetValidDatesUpdateAction_WithOnlyNullNewDates_ShouldBuildUpdateAction() { + final ZonedDateTime validFrom = ZonedDateTime.parse("2019-04-30T22:00:00.000Z"); + final ZonedDateTime validUntil = ZonedDateTime.parse("2019-05-30T22:00:00.000Z"); + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidUntil()).thenReturn(validFrom); + when(oldCartDiscount.getValidFrom()).thenReturn(validUntil); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidUntil()).thenReturn(null); + when(newCartDiscountDraft.getValidFrom()).thenReturn(null); + + final Optional> setValidUntilUpdateAction = + buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidUntilUpdateAction).contains(SetValidFromAndUntil.of(null, null)); + } + + @Test + void + buildSetValidDatesUpdateAction_WithBothNullValidUntilAndFromDates_ShouldNotBuildUpdateAction() { + final CartDiscount oldCartDiscount = mock(CartDiscount.class); + when(oldCartDiscount.getValidUntil()).thenReturn(null); + when(oldCartDiscount.getValidFrom()).thenReturn(null); + + final CartDiscountDraft newCartDiscountDraft = mock(CartDiscountDraft.class); + when(newCartDiscountDraft.getValidUntil()).thenReturn(null); + when(newCartDiscountDraft.getValidFrom()).thenReturn(null); + + final Optional> setValidUntilUpdateAction = + buildSetValidDatesUpdateAction(oldCartDiscount, newCartDiscountDraft); + + assertThat(setValidUntilUpdateAction).isNotPresent(); + } } diff --git a/src/test/java/com/commercetools/sync/categories/CategorySyncMockUtils.java b/src/test/java/com/commercetools/sync/categories/CategorySyncMockUtils.java index 2b2ee820bd..e2037c9aab 100644 --- a/src/test/java/com/commercetools/sync/categories/CategorySyncMockUtils.java +++ b/src/test/java/com/commercetools/sync/categories/CategorySyncMockUtils.java @@ -1,5 +1,8 @@ package com.commercetools.sync.categories; +import static com.commercetools.sync.commons.MockUtils.getMockCustomFieldsDraft; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.categories.Category; @@ -8,223 +11,225 @@ import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.types.CustomFieldsDraft; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; - -import static com.commercetools.sync.commons.MockUtils.getMockCustomFieldsDraft; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class CategorySyncMockUtils { - /** - * Given a {@code locale}, {@code name}, {@code slug}, {@code externalId}, {@code description}, - * {@code metaDescription}, {@code metaTitle}, {@code metaKeywords}, {@code orderHint} and - * {@code parentId}; this method creates a mock of {@link Category} with all those supplied fields. All the supplied - * arguments are given as {@link String} and the method internally converts them to their required types. - * For example, for all the fields that require a {@link LocalizedString} as a value type; the method creates an - * instance of a {@link LocalizedString} with the given {@link String} and {@link Locale}. - * - * @param locale the locale to create with all the {@link LocalizedString} instances. - * @param name the name of the category. - * @param slug the slug of the category. - * @param key the key of the category. - * @param externalId the external id of the category. - * @param description the description of the category. - * @param metaDescription the metadescription of the category. - * @param metaTitle the metatitle of the category. - * @param metaKeywords the metakeywords of the category. - * @param orderHint the orderhint of the category. - * @param parentId the parentId of the category. - * @return an instance {@link Category} with all the given fields set in the given {@link Locale}. - */ - public static Category getMockCategory(@Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final String slug, - @Nonnull final String key, - @Nonnull final String externalId, - @Nonnull final String description, - @Nonnull final String metaDescription, - @Nonnull final String metaTitle, - @Nonnull final String metaKeywords, - @Nonnull final String orderHint, - @Nullable final String parentId) { - final Category category = mock(Category.class); - when(category.getName()).thenReturn(LocalizedString.of(locale, name)); - when(category.getSlug()).thenReturn(LocalizedString.of(locale, slug)); - when(category.getKey()).thenReturn(key); - when(category.getId()).thenReturn(externalId); - when(category.getExternalId()).thenReturn(externalId); - when(category.getDescription()).thenReturn(LocalizedString.of(locale, description)); - when(category.getMetaDescription()).thenReturn(LocalizedString.of(locale, metaDescription)); - when(category.getMetaTitle()).thenReturn(LocalizedString.of(locale, metaTitle)); - when(category.getMetaKeywords()).thenReturn(LocalizedString.of(locale, metaKeywords)); - when(category.getOrderHint()).thenReturn(orderHint); - when(category.getParent()).thenReturn(Category.referenceOfId(parentId)); - when(category.toReference()).thenReturn(Category.referenceOfId(UUID.randomUUID().toString())); - return category; - } - - public static Category getMockCategory(@Nonnull final String id, @Nonnull final String key) { - final Category category = mock(Category.class); - when(category.getKey()).thenReturn(key); - when(category.getId()).thenReturn(id); - when(category.toReference()).thenReturn(Category.referenceOfId(id)); - return category; - } + /** + * Given a {@code locale}, {@code name}, {@code slug}, {@code externalId}, {@code description}, + * {@code metaDescription}, {@code metaTitle}, {@code metaKeywords}, {@code orderHint} and {@code + * parentId}; this method creates a mock of {@link Category} with all those supplied fields. All + * the supplied arguments are given as {@link String} and the method internally converts them to + * their required types. For example, for all the fields that require a {@link LocalizedString} as + * a value type; the method creates an instance of a {@link LocalizedString} with the given {@link + * String} and {@link Locale}. + * + * @param locale the locale to create with all the {@link LocalizedString} instances. + * @param name the name of the category. + * @param slug the slug of the category. + * @param key the key of the category. + * @param externalId the external id of the category. + * @param description the description of the category. + * @param metaDescription the metadescription of the category. + * @param metaTitle the metatitle of the category. + * @param metaKeywords the metakeywords of the category. + * @param orderHint the orderhint of the category. + * @param parentId the parentId of the category. + * @return an instance {@link Category} with all the given fields set in the given {@link Locale}. + */ + public static Category getMockCategory( + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final String slug, + @Nonnull final String key, + @Nonnull final String externalId, + @Nonnull final String description, + @Nonnull final String metaDescription, + @Nonnull final String metaTitle, + @Nonnull final String metaKeywords, + @Nonnull final String orderHint, + @Nullable final String parentId) { + final Category category = mock(Category.class); + when(category.getName()).thenReturn(LocalizedString.of(locale, name)); + when(category.getSlug()).thenReturn(LocalizedString.of(locale, slug)); + when(category.getKey()).thenReturn(key); + when(category.getId()).thenReturn(externalId); + when(category.getExternalId()).thenReturn(externalId); + when(category.getDescription()).thenReturn(LocalizedString.of(locale, description)); + when(category.getMetaDescription()).thenReturn(LocalizedString.of(locale, metaDescription)); + when(category.getMetaTitle()).thenReturn(LocalizedString.of(locale, metaTitle)); + when(category.getMetaKeywords()).thenReturn(LocalizedString.of(locale, metaKeywords)); + when(category.getOrderHint()).thenReturn(orderHint); + when(category.getParent()).thenReturn(Category.referenceOfId(parentId)); + when(category.toReference()).thenReturn(Category.referenceOfId(UUID.randomUUID().toString())); + return category; + } - public static Category mockRoot() { - return getMockCategory(Locale.GERMAN, - "root", - "root", - "root", - "root", - "root", - "root", - "root", - "root", - "100", - null); - } + public static Category getMockCategory(@Nonnull final String id, @Nonnull final String key) { + final Category category = mock(Category.class); + when(category.getKey()).thenReturn(key); + when(category.getId()).thenReturn(id); + when(category.toReference()).thenReturn(Category.referenceOfId(id)); + return category; + } - /** - * Creates a list of 2 {@link CategoryDraft}s; the first category draft has the following fields: - *
    - *
  • name: {"de": "draft1"}
  • - *
  • slug: {"de": "slug1"}
  • - *
  • key: "SH663881"
  • - *
- * - *

and the other category draft has the following fields: - *

    - *
  • name: {"de": "draft2"}
  • - *
  • slug: {"de": "slug2"}
  • - *
  • key: "SH604972"
  • - *
- * - * @return a list of the of the 2 mocked category drafts. - */ - public static List getMockCategoryDrafts() { - final List categoryDrafts = new ArrayList<>(); - CategoryDraft categoryDraft1 = getMockCategoryDraft(Locale.GERMAN, "draft1", "slug1", "SH663881"); - CategoryDraft categoryDraft2 = getMockCategoryDraft(Locale.GERMAN, "draft2", "slug2", "SH604972"); - categoryDrafts.add(categoryDraft1); - categoryDrafts.add(categoryDraft2); - return categoryDrafts; - } + public static Category mockRoot() { + return getMockCategory( + Locale.GERMAN, "root", "root", "root", "root", "root", "root", "root", "root", "100", null); + } - /** - * Given a {@code locale}, {@code name}, {@code slug}, {@code key}, {@code externalId}, {@code description}, - * {@code metaDescription}, {@code metaTitle}, {@code metaKeywords}, {@code orderHint} and - * {@code parentId}; this method creates a mock of {@link CategoryDraft} with all those supplied fields. All the - * supplied arguments are given as {@link String} and the method internally converts them to their required types. - * For example, for all the fields that require a {@link LocalizedString} as a value type; the method creates an - * instance of a {@link LocalizedString} with the given {@link String} and {@link Locale}. - * - * @param locale the locale to create with all the {@link LocalizedString} instances. - * @param name the name of the category. - * @param slug the slug of the category. - * @param key the key of the category. - * @param externalId the external id of the category. - * @param description the description of the category. - * @param metaDescription the metadescription of the category. - * @param metaTitle the metatitle of the category. - * @param metaKeywords the metakeywords of the category. - * @param orderHint the orderhint of the category. - * @param parentId the parentId of the category. - * @return an instance {@link CategoryDraft} with all the given fields set in the given {@link Locale}. - */ - public static CategoryDraft getMockCategoryDraft(@Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final String slug, - @Nonnull final String key, - @Nonnull final String externalId, - @Nonnull final String description, - @Nonnull final String metaDescription, - @Nonnull final String metaTitle, - @Nonnull final String metaKeywords, - @Nonnull final String orderHint, - @Nonnull final String parentId) { - final CategoryDraft categoryDraft = mock(CategoryDraft.class); - when(categoryDraft.getName()).thenReturn(LocalizedString.of(locale, name)); - when(categoryDraft.getSlug()).thenReturn(LocalizedString.of(locale, slug)); - when(categoryDraft.getKey()).thenReturn(key); - when(categoryDraft.getExternalId()).thenReturn(externalId); - when(categoryDraft.getDescription()).thenReturn(LocalizedString.of(locale, description)); - when(categoryDraft.getMetaDescription()).thenReturn(LocalizedString.of(locale, metaDescription)); - when(categoryDraft.getMetaTitle()).thenReturn(LocalizedString.of(locale, metaTitle)); - when(categoryDraft.getMetaKeywords()).thenReturn(LocalizedString.of(locale, metaKeywords)); - when(categoryDraft.getOrderHint()).thenReturn(orderHint); - when(categoryDraft.getParent()).thenReturn(Category.referenceOfId(parentId)); - return categoryDraft; - } + /** + * Creates a list of 2 {@link CategoryDraft}s; the first category draft has the following fields: + * + *
    + *
  • name: {"de": "draft1"} + *
  • slug: {"de": "slug1"} + *
  • key: "SH663881" + *
+ * + *

and the other category draft has the following fields: + * + *

    + *
  • name: {"de": "draft2"} + *
  • slug: {"de": "slug2"} + *
  • key: "SH604972" + *
+ * + * @return a list of the of the 2 mocked category drafts. + */ + public static List getMockCategoryDrafts() { + final List categoryDrafts = new ArrayList<>(); + CategoryDraft categoryDraft1 = + getMockCategoryDraft(Locale.GERMAN, "draft1", "slug1", "SH663881"); + CategoryDraft categoryDraft2 = + getMockCategoryDraft(Locale.GERMAN, "draft2", "slug2", "SH604972"); + categoryDrafts.add(categoryDraft1); + categoryDrafts.add(categoryDraft2); + return categoryDrafts; + } - public static CategoryDraft getMockCategoryDraft(@Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final String key, - @Nullable final String parentKey, - @Nonnull final String customTypeKey, - @Nonnull final Map customFields) { - return getMockCategoryDraftBuilder(locale, name, key, parentKey, customTypeKey, customFields).build(); - } + /** + * Given a {@code locale}, {@code name}, {@code slug}, {@code key}, {@code externalId}, {@code + * description}, {@code metaDescription}, {@code metaTitle}, {@code metaKeywords}, {@code + * orderHint} and {@code parentId}; this method creates a mock of {@link CategoryDraft} with all + * those supplied fields. All the supplied arguments are given as {@link String} and the method + * internally converts them to their required types. For example, for all the fields that require + * a {@link LocalizedString} as a value type; the method creates an instance of a {@link + * LocalizedString} with the given {@link String} and {@link Locale}. + * + * @param locale the locale to create with all the {@link LocalizedString} instances. + * @param name the name of the category. + * @param slug the slug of the category. + * @param key the key of the category. + * @param externalId the external id of the category. + * @param description the description of the category. + * @param metaDescription the metadescription of the category. + * @param metaTitle the metatitle of the category. + * @param metaKeywords the metakeywords of the category. + * @param orderHint the orderhint of the category. + * @param parentId the parentId of the category. + * @return an instance {@link CategoryDraft} with all the given fields set in the given {@link + * Locale}. + */ + public static CategoryDraft getMockCategoryDraft( + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final String slug, + @Nonnull final String key, + @Nonnull final String externalId, + @Nonnull final String description, + @Nonnull final String metaDescription, + @Nonnull final String metaTitle, + @Nonnull final String metaKeywords, + @Nonnull final String orderHint, + @Nonnull final String parentId) { + final CategoryDraft categoryDraft = mock(CategoryDraft.class); + when(categoryDraft.getName()).thenReturn(LocalizedString.of(locale, name)); + when(categoryDraft.getSlug()).thenReturn(LocalizedString.of(locale, slug)); + when(categoryDraft.getKey()).thenReturn(key); + when(categoryDraft.getExternalId()).thenReturn(externalId); + when(categoryDraft.getDescription()).thenReturn(LocalizedString.of(locale, description)); + when(categoryDraft.getMetaDescription()) + .thenReturn(LocalizedString.of(locale, metaDescription)); + when(categoryDraft.getMetaTitle()).thenReturn(LocalizedString.of(locale, metaTitle)); + when(categoryDraft.getMetaKeywords()).thenReturn(LocalizedString.of(locale, metaKeywords)); + when(categoryDraft.getOrderHint()).thenReturn(orderHint); + when(categoryDraft.getParent()).thenReturn(Category.referenceOfId(parentId)); + return categoryDraft; + } + public static CategoryDraft getMockCategoryDraft( + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final String key, + @Nullable final String parentKey, + @Nonnull final String customTypeKey, + @Nonnull final Map customFields) { + return getMockCategoryDraftBuilder(locale, name, key, parentKey, customTypeKey, customFields) + .build(); + } - /** - * Given a {@code locale}, {@code name}, {@code slug} and {@code key}; this method creates a mock of - * {@link CategoryDraft} with all those supplied fields. All the supplied arguments are given as {@link String} and - * the method internally converts them to their required types. For example, for all the fields that require a - * {@link LocalizedString} as a value type; the method creates an instance of a {@link LocalizedString} with - * the given {@link String} and {@link Locale}. - * - * @param locale the locale to create with all the {@link LocalizedString} instances. - * @param name the name of the category. - * @param slug the slug of the category. - * @param key the key of the category. - * @return an instance {@link CategoryDraft} with all the given fields set in the given {@link Locale}. - */ - public static CategoryDraft getMockCategoryDraft(@Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final String slug, - @Nullable final String key) { - final CategoryDraft mockCategoryDraft = mock(CategoryDraft.class); - when(mockCategoryDraft.getName()).thenReturn(LocalizedString.of(locale, name)); - when(mockCategoryDraft.getSlug()).thenReturn(LocalizedString.of(locale, slug)); - when(mockCategoryDraft.getKey()).thenReturn(key); - when(mockCategoryDraft.getCustom()).thenReturn(getMockCustomFieldsDraft()); - return mockCategoryDraft; - } + /** + * Given a {@code locale}, {@code name}, {@code slug} and {@code key}; this method creates a mock + * of {@link CategoryDraft} with all those supplied fields. All the supplied arguments are given + * as {@link String} and the method internally converts them to their required types. For example, + * for all the fields that require a {@link LocalizedString} as a value type; the method creates + * an instance of a {@link LocalizedString} with the given {@link String} and {@link Locale}. + * + * @param locale the locale to create with all the {@link LocalizedString} instances. + * @param name the name of the category. + * @param slug the slug of the category. + * @param key the key of the category. + * @return an instance {@link CategoryDraft} with all the given fields set in the given {@link + * Locale}. + */ + public static CategoryDraft getMockCategoryDraft( + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final String slug, + @Nullable final String key) { + final CategoryDraft mockCategoryDraft = mock(CategoryDraft.class); + when(mockCategoryDraft.getName()).thenReturn(LocalizedString.of(locale, name)); + when(mockCategoryDraft.getSlug()).thenReturn(LocalizedString.of(locale, slug)); + when(mockCategoryDraft.getKey()).thenReturn(key); + when(mockCategoryDraft.getCustom()).thenReturn(getMockCustomFieldsDraft()); + return mockCategoryDraft; + } - /** - * Given a {@code locale}, {@code name}, {@code slug}, {@code key}, {@code description}, - * {@code metaDescription}, {@code metaTitle}, {@code metaKeywords}, {@code orderHint} and - * {@code parentId}; this method creates a {@link CategoryDraftBuilder} with mocked all those supplied fields. - * All the supplied arguments are given as {@link String} and the method internally converts them - * to their required types. - * For example, for all the fields that require a {@link LocalizedString} as a value type; the method creates an - * instance of a {@link LocalizedString} with the given {@link String} and {@link Locale}. - * - * @param locale the locale to create with all the {@link LocalizedString} instances. - * @param name the name of the category. - * @param key the key id of the category. - * @param parentKey the key of the parent category. - * @param customTypeKey the key of the custom type of category. - * @param customFields the custom fields of the category. - * @return an instance {@link CategoryDraftBuilder} with all the given fields set in the given {@link Locale}. - */ - public static CategoryDraftBuilder getMockCategoryDraftBuilder(@Nonnull final Locale locale, - @Nonnull final String name, - @Nonnull final String key, - @Nullable final String parentKey, - @Nonnull final String customTypeKey, - @Nonnull final Map customFields) { - return CategoryDraftBuilder.of(LocalizedString.of(locale, name), LocalizedString.of(locale, "testSlug")) - .key(key) - .parent(ResourceIdentifier.ofKey(parentKey)) - .custom(CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, customFields)); - } + /** + * Given a {@code locale}, {@code name}, {@code slug}, {@code key}, {@code description}, {@code + * metaDescription}, {@code metaTitle}, {@code metaKeywords}, {@code orderHint} and {@code + * parentId}; this method creates a {@link CategoryDraftBuilder} with mocked all those supplied + * fields. All the supplied arguments are given as {@link String} and the method internally + * converts them to their required types. For example, for all the fields that require a {@link + * LocalizedString} as a value type; the method creates an instance of a {@link LocalizedString} + * with the given {@link String} and {@link Locale}. + * + * @param locale the locale to create with all the {@link LocalizedString} instances. + * @param name the name of the category. + * @param key the key id of the category. + * @param parentKey the key of the parent category. + * @param customTypeKey the key of the custom type of category. + * @param customFields the custom fields of the category. + * @return an instance {@link CategoryDraftBuilder} with all the given fields set in the given + * {@link Locale}. + */ + public static CategoryDraftBuilder getMockCategoryDraftBuilder( + @Nonnull final Locale locale, + @Nonnull final String name, + @Nonnull final String key, + @Nullable final String parentKey, + @Nonnull final String customTypeKey, + @Nonnull final Map customFields) { + return CategoryDraftBuilder.of( + LocalizedString.of(locale, name), LocalizedString.of(locale, "testSlug")) + .key(key) + .parent(ResourceIdentifier.ofKey(parentKey)) + .custom(CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, customFields)); + } } diff --git a/src/test/java/com/commercetools/sync/categories/CategorySyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/categories/CategorySyncOptionsBuilderTest.java index f892a9ae4b..f53d9d3c76 100644 --- a/src/test/java/com/commercetools/sync/categories/CategorySyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/categories/CategorySyncOptionsBuilderTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.categories; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -10,268 +20,263 @@ import io.sphere.sdk.categories.commands.updateactions.ChangeName; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - +import org.junit.jupiter.api.Test; class CategorySyncOptionsBuilderTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private CategorySyncOptionsBuilder categorySyncOptionsBuilder = CategorySyncOptionsBuilder.of(CTP_CLIENT); - - @Test - void of_WithClient_ShouldCreateCategorySyncOptionsBuilder() { - final CategorySyncOptionsBuilder builder = CategorySyncOptionsBuilder.of(CTP_CLIENT); - assertThat(builder).isNotNull(); - } - - @Test - void build_WithClient_ShouldBuildCategorySyncOptions() { - final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); - assertThat(categorySyncOptions).isNotNull(); - assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNull(); - assertThat(categorySyncOptions.getBeforeCreateCallback()).isNull(); - assertThat(categorySyncOptions.getErrorCallback()).isNull(); - assertThat(categorySyncOptions.getWarningCallback()).isNull(); - assertThat(categorySyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); - assertThat(categorySyncOptions.getBatchSize()).isEqualTo(CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(categorySyncOptions.getCacheSize()).isEqualTo(10_000); - } - - @Test - void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { - final TriFunction>, CategoryDraft, Category, List>> - beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> emptyList(); - categorySyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - - final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); - assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNotNull(); - } - - @Test - void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { - final Function draftFunction = categoryDraft -> null; - categorySyncOptionsBuilder.beforeCreateCallback(draftFunction); - - final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); - assertThat(categorySyncOptions.getBeforeCreateCallback()).isNotNull(); - } - - @Test - public void errorCallBack_WithCallBack_ShouldSetCallBack() { - final QuadConsumer, Optional, - List>> mockErrorCallback = (exception, newDraft, old, actions) -> { - }; - categorySyncOptionsBuilder.errorCallback(mockErrorCallback); - - final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); - assertThat(categorySyncOptions.getErrorCallback()).isNotNull(); - } - - @Test - public void warningCallBack_WithCallBack_ShouldSetCallBack() { - final TriConsumer, Optional> mockWarningCallBack = - (exception, newDraft, old) -> { }; - categorySyncOptionsBuilder.warningCallback(mockWarningCallBack); - - final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); - assertThat(categorySyncOptions.getWarningCallback()).isNotNull(); - } - - @Test - void getThis_ShouldReturnCorrectInstance() { - final CategorySyncOptionsBuilder instance = categorySyncOptionsBuilder.getThis(); - assertThat(instance).isNotNull(); - assertThat(instance).isInstanceOf(CategorySyncOptionsBuilder.class); - assertThat(instance).isEqualTo(categorySyncOptionsBuilder); - } - - @Test - void categorySyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder - .of(CTP_CLIENT) + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private CategorySyncOptionsBuilder categorySyncOptionsBuilder = + CategorySyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateCategorySyncOptionsBuilder() { + final CategorySyncOptionsBuilder builder = CategorySyncOptionsBuilder.of(CTP_CLIENT); + assertThat(builder).isNotNull(); + } + + @Test + void build_WithClient_ShouldBuildCategorySyncOptions() { + final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); + assertThat(categorySyncOptions).isNotNull(); + assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNull(); + assertThat(categorySyncOptions.getBeforeCreateCallback()).isNull(); + assertThat(categorySyncOptions.getErrorCallback()).isNull(); + assertThat(categorySyncOptions.getWarningCallback()).isNull(); + assertThat(categorySyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); + assertThat(categorySyncOptions.getBatchSize()) + .isEqualTo(CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); + assertThat(categorySyncOptions.getCacheSize()).isEqualTo(10_000); + } + + @Test + void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { + final TriFunction< + List>, CategoryDraft, Category, List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> emptyList(); + categorySyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); + + final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); + assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNotNull(); + } + + @Test + void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { + final Function draftFunction = categoryDraft -> null; + categorySyncOptionsBuilder.beforeCreateCallback(draftFunction); + + final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); + assertThat(categorySyncOptions.getBeforeCreateCallback()).isNotNull(); + } + + @Test + public void errorCallBack_WithCallBack_ShouldSetCallBack() { + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + mockErrorCallback = (exception, newDraft, old, actions) -> {}; + categorySyncOptionsBuilder.errorCallback(mockErrorCallback); + + final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); + assertThat(categorySyncOptions.getErrorCallback()).isNotNull(); + } + + @Test + public void warningCallBack_WithCallBack_ShouldSetCallBack() { + final TriConsumer, Optional> + mockWarningCallBack = (exception, newDraft, old) -> {}; + categorySyncOptionsBuilder.warningCallback(mockWarningCallBack); + + final CategorySyncOptions categorySyncOptions = categorySyncOptionsBuilder.build(); + assertThat(categorySyncOptions.getWarningCallback()).isNotNull(); + } + + @Test + void getThis_ShouldReturnCorrectInstance() { + final CategorySyncOptionsBuilder instance = categorySyncOptionsBuilder.getThis(); + assertThat(instance).isNotNull(); + assertThat(instance).isInstanceOf(CategorySyncOptionsBuilder.class); + assertThat(instance).isEqualTo(categorySyncOptionsBuilder); + } + + @Test + void categorySyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT) .batchSize(30) .beforeUpdateCallback((updateActions, newCategory, oldCategory) -> emptyList()) .beforeCreateCallback(newCategoryDraft -> null) .build(); - assertThat(categorySyncOptions).isNotNull(); - } - - @Test - void batchSize_WithPositiveValue_ShouldSetBatchSize() { - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(10) - .build(); - assertThat(categorySyncOptions.getBatchSize()).isEqualTo(10); - } - - @Test - void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final CategorySyncOptions categorySyncOptionsWithZeroBatchSize = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(0) - .build(); - assertThat(categorySyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); - - final CategorySyncOptions categorySyncOptionsWithNegativeBatchSize = CategorySyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(-100) + assertThat(categorySyncOptions).isNotNull(); + } + + @Test + void batchSize_WithPositiveValue_ShouldSetBatchSize() { + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); + assertThat(categorySyncOptions.getBatchSize()).isEqualTo(10); + } + + @Test + void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final CategorySyncOptions categorySyncOptionsWithZeroBatchSize = + CategorySyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); + assertThat(categorySyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); + + final CategorySyncOptions categorySyncOptionsWithNegativeBatchSize = + CategorySyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); + assertThat(categorySyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + + @Test + void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNull(); + + final List> updateActions = + singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + categorySyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CategoryDraft.class), mock(Category.class)); + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { + final TriFunction< + List>, CategoryDraft, Category, List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> emptyList(); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + categorySyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CategoryDraft.class), mock(Category.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { + final TriFunction< + List>, CategoryDraft, Category, List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) .build(); - assertThat(categorySyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } - - @Test - void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .build(); - assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = categorySyncOptions - .applyBeforeUpdateCallback(updateActions, mock(CategoryDraft.class), mock(Category.class)); - assertThat(filteredList).isSameAs(updateActions); - } - - @Test - void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { - final TriFunction>, CategoryDraft, Category, List>> - beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> emptyList(); - - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = categorySyncOptions - .applyBeforeUpdateCallback(updateActions, mock(CategoryDraft.class), mock(Category.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { - final TriFunction>, CategoryDraft, Category, List>> - beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; - - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = categorySyncOptions - .applyBeforeUpdateCallback(updateActions, mock(CategoryDraft.class), mock(Category.class)); - - assertThat(filteredList).isEmpty(); - } - - private interface MockTriFunction extends - TriFunction>, CategoryDraft, Category, List>> { - } - - @Test - void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); - - final CategorySyncOptions categorySyncOptions = - CategorySyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = emptyList(); - final List> filteredList = categorySyncOptions - .applyBeforeUpdateCallback(updateActions, mock(CategoryDraft.class), mock(Category.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - @Test - void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraft() { - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .build(); - assertThat(categorySyncOptions.getBeforeCreateCallback()).isNull(); - - final CategoryDraft resourceDraft = mock(CategoryDraft.class); - final Optional filteredDraft = categorySyncOptions.applyBeforeCreateCallback(resourceDraft); - assertThat(filteredDraft).containsSame(resourceDraft); - } - - @Test - void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredList() { - final Function draftFunction = categoryDraft -> - CategoryDraftBuilder.of(categoryDraft) - .key(categoryDraft.getKey() + "_filterPostFix") - .build(); - - final CategorySyncOptions syncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - assertThat(syncOptions.getBeforeCreateCallback()).isNotNull(); - - final CategoryDraft resourceDraft = mock(CategoryDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); - - - final Optional filteredDraft = syncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isNotEmpty(); - assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filterPostFix"); - } - - @Test - void applyBeforeCreateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { - final Function draftFunction = categoryDraft -> null; - - final CategorySyncOptions syncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - assertThat(syncOptions.getBeforeCreateCallback()).isNotNull(); - - final CategoryDraft resourceDraft = mock(CategoryDraft.class); - final Optional filteredDraft = syncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isEmpty(); - } - - @Test - void cacheSize_WithPositiveValue_ShouldSetCacheSize() { - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(10) - .build(); - assertThat(categorySyncOptions.getCacheSize()).isEqualTo(10); - } - - @Test - void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final CategorySyncOptions categorySyncOptionsWithZeroCacheSize = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(0) - .build(); - assertThat(categorySyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - - final CategorySyncOptions categorySyncOptionsWithNegativeCacheSize = CategorySyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(-100) + assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + categorySyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CategoryDraft.class), mock(Category.class)); + + assertThat(filteredList).isEmpty(); + } + + private interface MockTriFunction + extends TriFunction< + List>, CategoryDraft, Category, List>> {} + + @Test + void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) .build(); - assertThat(categorySyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } + assertThat(categorySyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = emptyList(); + final List> filteredList = + categorySyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CategoryDraft.class), mock(Category.class)); + + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraft() { + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(categorySyncOptions.getBeforeCreateCallback()).isNull(); + + final CategoryDraft resourceDraft = mock(CategoryDraft.class); + final Optional filteredDraft = + categorySyncOptions.applyBeforeCreateCallback(resourceDraft); + assertThat(filteredDraft).containsSame(resourceDraft); + } + + @Test + void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredList() { + final Function draftFunction = + categoryDraft -> + CategoryDraftBuilder.of(categoryDraft) + .key(categoryDraft.getKey() + "_filterPostFix") + .build(); + + final CategorySyncOptions syncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(syncOptions.getBeforeCreateCallback()).isNotNull(); + + final CategoryDraft resourceDraft = mock(CategoryDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); + + final Optional filteredDraft = + syncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isNotEmpty(); + assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filterPostFix"); + } + + @Test + void applyBeforeCreateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { + final Function draftFunction = categoryDraft -> null; + + final CategorySyncOptions syncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(syncOptions.getBeforeCreateCallback()).isNotNull(); + + final CategoryDraft resourceDraft = mock(CategoryDraft.class); + final Optional filteredDraft = + syncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isEmpty(); + } + + @Test + void cacheSize_WithPositiveValue_ShouldSetCacheSize() { + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); + assertThat(categorySyncOptions.getCacheSize()).isEqualTo(10); + } + + @Test + void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final CategorySyncOptions categorySyncOptionsWithZeroCacheSize = + CategorySyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); + assertThat(categorySyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); + + final CategorySyncOptions categorySyncOptionsWithNegativeCacheSize = + CategorySyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); + assertThat(categorySyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/categories/CategorySyncOptionsTest.java b/src/test/java/com/commercetools/sync/categories/CategorySyncOptionsTest.java index fb94351063..d21f91fb92 100644 --- a/src/test/java/com/commercetools/sync/categories/CategorySyncOptionsTest.java +++ b/src/test/java/com/commercetools/sync/categories/CategorySyncOptionsTest.java @@ -1,5 +1,8 @@ package com.commercetools.sync.categories; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.commons.utils.TriFunction; import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.CategoryDraft; @@ -7,53 +10,51 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CategorySyncOptionsTest { - private CategorySyncOptionsBuilder categorySyncOptionsBuilder; - - /** - * Initializes an instance of {@link CategorySyncOptionsBuilder} to be used in the unit test methods of this test - * class. - */ - @BeforeEach - void setup() { - categorySyncOptionsBuilder = CategorySyncOptionsBuilder.of(mock(SphereClient.class)); - } - - @Test - void getUpdateActionsFilter_WithFilter_ShouldApplyFilterOnList() { - final TriFunction>, CategoryDraft, Category, List>> - clearListFilter = (updateActions, newCategory, oldCategory) -> Collections.emptyList(); - categorySyncOptionsBuilder.beforeUpdateCallback(clearListFilter); - final CategorySyncOptions syncOptions = categorySyncOptionsBuilder.build(); - - final List> updateActions = new ArrayList<>(); - updateActions.add(ChangeName.of(LocalizedString.of(Locale.ENGLISH, "name"))); - - assertThat(syncOptions.getBeforeUpdateCallback()).isNotNull(); - final List> resultantList = syncOptions.getBeforeUpdateCallback() - .apply(updateActions, mock(CategoryDraft.class), - mock(Category.class)); - - assertThat(updateActions).isNotEmpty(); - assertThat(resultantList).isEmpty(); - } - - @Test - void build_WithOnlyRequiredFieldsSet_ShouldReturnProperOptionsInstance() { - final CategorySyncOptions options = CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); - assertThat(options).isNotNull(); - assertThat(options.getBatchSize()).isEqualTo(CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } - + private CategorySyncOptionsBuilder categorySyncOptionsBuilder; + + /** + * Initializes an instance of {@link CategorySyncOptionsBuilder} to be used in the unit test + * methods of this test class. + */ + @BeforeEach + void setup() { + categorySyncOptionsBuilder = CategorySyncOptionsBuilder.of(mock(SphereClient.class)); + } + + @Test + void getUpdateActionsFilter_WithFilter_ShouldApplyFilterOnList() { + final TriFunction< + List>, CategoryDraft, Category, List>> + clearListFilter = (updateActions, newCategory, oldCategory) -> Collections.emptyList(); + categorySyncOptionsBuilder.beforeUpdateCallback(clearListFilter); + final CategorySyncOptions syncOptions = categorySyncOptionsBuilder.build(); + + final List> updateActions = new ArrayList<>(); + updateActions.add(ChangeName.of(LocalizedString.of(Locale.ENGLISH, "name"))); + + assertThat(syncOptions.getBeforeUpdateCallback()).isNotNull(); + final List> resultantList = + syncOptions + .getBeforeUpdateCallback() + .apply(updateActions, mock(CategoryDraft.class), mock(Category.class)); + + assertThat(updateActions).isNotEmpty(); + assertThat(resultantList).isEmpty(); + } + + @Test + void build_WithOnlyRequiredFieldsSet_ShouldReturnProperOptionsInstance() { + final CategorySyncOptions options = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); + assertThat(options).isNotNull(); + assertThat(options.getBatchSize()).isEqualTo(CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } } diff --git a/src/test/java/com/commercetools/sync/categories/CategorySyncTest.java b/src/test/java/com/commercetools/sync/categories/CategorySyncTest.java index 513f202061..dd494fc71c 100644 --- a/src/test/java/com/commercetools/sync/categories/CategorySyncTest.java +++ b/src/test/java/com/commercetools/sync/categories/CategorySyncTest.java @@ -1,5 +1,31 @@ package com.commercetools.sync.categories; +import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategory; +import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategoryDraft; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.MockUtils.mockCategoryService; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.products.ProductSyncMockUtils.CATEGORY_KEY_1_RESOURCE_PATH; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.helpers.CategorySyncStatistics; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; @@ -17,10 +43,6 @@ import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.models.SphereException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -31,543 +53,582 @@ import java.util.concurrent.CompletionStage; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategory; -import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategoryDraft; -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.MockUtils.mockCategoryService; -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.products.ProductSyncMockUtils.CATEGORY_KEY_1_RESOURCE_PATH; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.CompletableFuture.supplyAsync; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CategorySyncTest { - private CategorySyncOptions categorySyncOptions; - private List errorCallBackMessages; - private List errorCallBackExceptions; - - // protected method access helper - private static class CategorySyncMock extends CategorySync { - - CategorySyncMock(@Nonnull final CategorySyncOptions syncOptions, - @Nonnull final TypeService typeService, - @Nonnull final CategoryService categoryService) { - super(syncOptions, typeService, categoryService); - } - - @Override - protected CompletionStage syncBatches( - @Nonnull final List> batches, - @Nonnull final CompletionStage result) { - return super.syncBatches(batches, result); - } + private CategorySyncOptions categorySyncOptions; + private List errorCallBackMessages; + private List errorCallBackExceptions; + + // protected method access helper + private static class CategorySyncMock extends CategorySync { + + CategorySyncMock( + @Nonnull final CategorySyncOptions syncOptions, + @Nonnull final TypeService typeService, + @Nonnull final CategoryService categoryService) { + super(syncOptions, typeService, categoryService); } - /** - * Initializes instances of {@link CategorySyncOptions} and {@link CategorySync} which will be used by some of the - * unit test methods in this test class. - */ - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); - categorySyncOptions = CategorySyncOptionsBuilder.of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }).build(); + @Override + protected CompletionStage syncBatches( + @Nonnull final List> batches, + @Nonnull final CompletionStage result) { + return super.syncBatches(batches, result); } - - @Test - void sync_WithEmptyListOfDrafts_ShouldNotProcessAnyCategories() { - final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); - final CategorySync mockCategorySync = - new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); - - final CategorySyncStatistics syncStatistics = mockCategorySync.sync(emptyList()) - .toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(0, 0, 0, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - } - - @Test - void sync_WithANullDraft_ShouldBeCountedAsFailed() { - final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); - final CategorySync mockCategorySync = - new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); - - final CategorySyncStatistics syncStatistics = mockCategorySync.sync(singletonList(null)) - .toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo("CategoryDraft is null."); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); - } - - @Test - void sync_WithADraftWithNoSetKey_ShouldFailSync() { - final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); - final CategorySync mockCategorySync = - new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); - final List categoryDrafts = singletonList( - getMockCategoryDraft(Locale.ENGLISH, "noKeyDraft", "no-key-id-draft", null)); - - final CategorySyncStatistics syncStatistics = mockCategorySync.sync(categoryDrafts) - .toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo("CategoryDraft with name: " - + "LocalizedString(en -> noKeyDraft) doesn't have a key. Please make sure all category drafts have keys." - ); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); - } - - @Test - void sync_WithNoExistingCategory_ShouldCreateCategory() { - final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); - final CategoryService mockCategoryService = mockCategoryService(emptySet(), mockCategory); - final CategorySync mockCategorySync = - new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); - final List categoryDrafts = singletonList( - getMockCategoryDraft(Locale.ENGLISH, "name", "newKey", "parentKey", "customTypeId", new HashMap<>())); - - final CategorySyncStatistics syncStatistics = mockCategorySync.sync(categoryDrafts) - .toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 1, 0, 0); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - } - - @Test - void sync_WithExistingCategory_ShouldUpdateCategory() { - final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); - final CategoryService mockCategoryService = - mockCategoryService(singleton(mockCategory), null, mockCategory); - final CategorySync mockCategorySync = - new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); - final List categoryDrafts = singletonList( - getMockCategoryDraft(Locale.ENGLISH, "name", "key", "parentKey", "customTypeId", new HashMap<>())); - - final CategorySyncStatistics syncStatistics = mockCategorySync.sync(categoryDrafts) - .toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - } - - @Test - void sync_WithIdenticalExistingCategory_ShouldNotUpdateCategory() { - final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); - final CategoryDraft identicalCategoryDraft = CategoryDraftBuilder.of(mockCategory).build(); - final CategoryService mockCategoryService = - mockCategoryService(singleton(mockCategory), null, mockCategory); - final CategorySync mockCategorySync = - new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); - final List categoryDrafts = singletonList(identicalCategoryDraft); - - final CategorySyncStatistics syncStatistics = mockCategorySync.sync(categoryDrafts) - .toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 0, 0); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - } - - @Test - void sync_WithExistingCategoryButWithNullParentReference_ShouldFailSync() { - final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); - final CategoryService mockCategoryService = - mockCategoryService(singleton(mockCategory), null, mockCategory); - final CategorySync categorySync = new CategorySync(categorySyncOptions, getMockTypeService(), - mockCategoryService); - final List categoryDrafts = singletonList( - getMockCategoryDraft(Locale.ENGLISH, "name", "key", null, "customTypeId", new HashMap<>())); - - final CategorySyncStatistics syncStatistics = categorySync.sync(categoryDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format("Failed to process the CategoryDraft with" - + " key:'key'. Reason: %s: Failed to resolve parent reference on CategoryDraft" - + " with key:'key'. Reason: %s", - ReferenceResolutionException.class.getCanonicalName(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(ReferenceResolutionException.class); - } - - @Test - void sync_WithExistingCategoryButWithNoCustomType_ShouldSync() { - final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); - final CategoryService mockCategoryService = - mockCategoryService(singleton(mockCategory), null, mockCategory); - final CategorySync categorySync = new CategorySync(categorySyncOptions, getMockTypeService(), - mockCategoryService); - - final CategoryDraft categoryDraft = mock(CategoryDraft.class); - when(categoryDraft.getName()).thenReturn(LocalizedString.of(Locale.ENGLISH, "name")); - when(categoryDraft.getKey()).thenReturn("key"); - when(categoryDraft.getCustom()).thenReturn(null); - - final List categoryDrafts = singletonList(categoryDraft); - - final CategorySyncStatistics syncStatistics = categorySync.sync(categoryDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 1, 0); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - } - - @Test - void sync_WithExistingCategoryButWithEmptyParentReference_ShouldFailSync() { - final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); - final CategoryService mockCategoryService = - mockCategoryService(singleton(mockCategory), null, mockCategory); - final CategorySync categorySync = new CategorySync(categorySyncOptions, getMockTypeService(), - mockCategoryService); - final List categoryDrafts = singletonList( - getMockCategoryDraft(Locale.ENGLISH, "name", "key", "", - "customTypeId", new HashMap<>())); - - final CategorySyncStatistics syncStatistics = categorySync.sync(categoryDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format("Failed to process the CategoryDraft with" - + " key:'key'. Reason: %s: Failed to resolve parent reference on CategoryDraft with key:'key'. " - + "Reason: %s", ReferenceResolutionException.class.getCanonicalName(), - BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(ReferenceResolutionException.class); - } - - @Test - void sync_WithExistingCategoryButWithEmptyCustomTypeReference_ShouldFailSync() { - final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); - final CategoryService mockCategoryService = - mockCategoryService(singleton(mockCategory), null, mockCategory); - final CategorySync categorySync = new CategorySync(categorySyncOptions, getMockTypeService(), - mockCategoryService); - final List categoryDrafts = singletonList( - getMockCategoryDraft(Locale.ENGLISH, "name", "key", "parentKey", - "", new HashMap<>())); - - final CategorySyncStatistics syncStatistics = categorySync.sync(categoryDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format("Failed to process the CategoryDraft with" - + " key:'key'. Reason: %s: Failed to resolve custom type reference on CategoryDraft " - + "with key:'key'. Reason: %s", ReferenceResolutionException.class.getCanonicalName(), + } + + /** + * Initializes instances of {@link CategorySyncOptions} and {@link CategorySync} which will be + * used by some of the unit test methods in this test class. + */ + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); + categorySyncOptions = + CategorySyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); + }) + .build(); + } + + @Test + void sync_WithEmptyListOfDrafts_ShouldNotProcessAnyCategories() { + final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); + final CategorySync mockCategorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + + final CategorySyncStatistics syncStatistics = + mockCategorySync.sync(emptyList()).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(0, 0, 0, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } + + @Test + void sync_WithANullDraft_ShouldBeCountedAsFailed() { + final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); + final CategorySync mockCategorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + + final CategorySyncStatistics syncStatistics = + mockCategorySync.sync(singletonList(null)).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo("CategoryDraft is null."); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); + } + + @Test + void sync_WithADraftWithNoSetKey_ShouldFailSync() { + final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); + final CategorySync mockCategorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + final List categoryDrafts = + singletonList(getMockCategoryDraft(Locale.ENGLISH, "noKeyDraft", "no-key-id-draft", null)); + + final CategorySyncStatistics syncStatistics = + mockCategorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo( + "CategoryDraft with name: " + + "LocalizedString(en -> noKeyDraft) doesn't have a key. Please make sure all category drafts have keys."); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); + } + + @Test + void sync_WithNoExistingCategory_ShouldCreateCategory() { + final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); + final CategoryService mockCategoryService = mockCategoryService(emptySet(), mockCategory); + final CategorySync mockCategorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + final List categoryDrafts = + singletonList( + getMockCategoryDraft( + Locale.ENGLISH, "name", "newKey", "parentKey", "customTypeId", new HashMap<>())); + + final CategorySyncStatistics syncStatistics = + mockCategorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 1, 0, 0); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + } + + @Test + void sync_WithExistingCategory_ShouldUpdateCategory() { + final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); + final CategoryService mockCategoryService = + mockCategoryService(singleton(mockCategory), null, mockCategory); + final CategorySync mockCategorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + final List categoryDrafts = + singletonList( + getMockCategoryDraft( + Locale.ENGLISH, "name", "key", "parentKey", "customTypeId", new HashMap<>())); + + final CategorySyncStatistics syncStatistics = + mockCategorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + } + + @Test + void sync_WithIdenticalExistingCategory_ShouldNotUpdateCategory() { + final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); + final CategoryDraft identicalCategoryDraft = CategoryDraftBuilder.of(mockCategory).build(); + final CategoryService mockCategoryService = + mockCategoryService(singleton(mockCategory), null, mockCategory); + final CategorySync mockCategorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + final List categoryDrafts = singletonList(identicalCategoryDraft); + + final CategorySyncStatistics syncStatistics = + mockCategorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 0, 0); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + } + + @Test + void sync_WithExistingCategoryButWithNullParentReference_ShouldFailSync() { + final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); + final CategoryService mockCategoryService = + mockCategoryService(singleton(mockCategory), null, mockCategory); + final CategorySync categorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + final List categoryDrafts = + singletonList( + getMockCategoryDraft( + Locale.ENGLISH, "name", "key", null, "customTypeId", new HashMap<>())); + + final CategorySyncStatistics syncStatistics = + categorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo( + format( + "Failed to process the CategoryDraft with" + + " key:'key'. Reason: %s: Failed to resolve parent reference on CategoryDraft" + + " with key:'key'. Reason: %s", + ReferenceResolutionException.class.getCanonicalName(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); - assertThat(errorCallBackExceptions.get(0).getCause()).isExactlyInstanceOf(ReferenceResolutionException.class); - } - - @Test - void requiresChangeParentUpdateAction_WithTwoDifferentParents_ShouldReturnTrue() { - final String parentId = "parentId"; - final Category category = mock(Category.class); - when(category.getParent()).thenReturn(Category.referenceOfId(parentId)); - - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "name"), LocalizedString.of(Locale.ENGLISH, "slug")) - .parent(ResourceIdentifier.ofId("differentParent")) - .build(); - final boolean doesRequire = CategorySync.requiresChangeParentUpdateAction(category, categoryDraft); - assertThat(doesRequire).isTrue(); - } - - @Test - void requiresChangeParentUpdateAction_WithTwoIdenticalParents_ShouldReturnFalse() { - final String parentId = "parentId"; - final String parentKey = "parentkey"; - final Category category = mock(Category.class); - when(category.getParent()).thenReturn(Category.referenceOfId(parentId)); - - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "name"), LocalizedString.of(Locale.ENGLISH, "slug")) - .parent(ResourceIdentifier.ofIdOrKey(parentId, parentKey)) - .build(); - - // checking with ids (on draft reference id will be resolved, but in test it's given) - final boolean doesRequire = CategorySync.requiresChangeParentUpdateAction(category, categoryDraft); - assertThat(doesRequire).isFalse(); - } - - @Test - void requiresChangeParentUpdateAction_WithNonExistingParents_ShouldReturnFalse() { - final Category category = mock(Category.class); - when(category.getParent()).thenReturn(null); - - final CategoryDraft categoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "name"), LocalizedString.of(Locale.ENGLISH, "slug")) - .build(); - final boolean doesRequire = CategorySync.requiresChangeParentUpdateAction(category, categoryDraft); - assertThat(doesRequire).isFalse(); - } - - @Test - void sync_WithBatchSizeSet_ShouldCallSyncOnEachBatch() { - // preparation - final int batchSize = 1; - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)) + .isExactlyInstanceOf(ReferenceResolutionException.class); + } + + @Test + void sync_WithExistingCategoryButWithNoCustomType_ShouldSync() { + final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); + final CategoryService mockCategoryService = + mockCategoryService(singleton(mockCategory), null, mockCategory); + final CategorySync categorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + + final CategoryDraft categoryDraft = mock(CategoryDraft.class); + when(categoryDraft.getName()).thenReturn(LocalizedString.of(Locale.ENGLISH, "name")); + when(categoryDraft.getKey()).thenReturn("key"); + when(categoryDraft.getCustom()).thenReturn(null); + + final List categoryDrafts = singletonList(categoryDraft); + + final CategorySyncStatistics syncStatistics = + categorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 1, 0); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + } + + @Test + void sync_WithExistingCategoryButWithEmptyParentReference_ShouldFailSync() { + final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); + final CategoryService mockCategoryService = + mockCategoryService(singleton(mockCategory), null, mockCategory); + final CategorySync categorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + final List categoryDrafts = + singletonList( + getMockCategoryDraft( + Locale.ENGLISH, "name", "key", "", "customTypeId", new HashMap<>())); + + final CategorySyncStatistics syncStatistics = + categorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo( + format( + "Failed to process the CategoryDraft with" + + " key:'key'. Reason: %s: Failed to resolve parent reference on CategoryDraft with key:'key'. " + + "Reason: %s", + ReferenceResolutionException.class.getCanonicalName(), + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)) + .isExactlyInstanceOf(ReferenceResolutionException.class); + } + + @Test + void sync_WithExistingCategoryButWithEmptyCustomTypeReference_ShouldFailSync() { + final Category mockCategory = getMockCategory(UUID.randomUUID().toString(), "key"); + final CategoryService mockCategoryService = + mockCategoryService(singleton(mockCategory), null, mockCategory); + final CategorySync categorySync = + new CategorySync(categorySyncOptions, getMockTypeService(), mockCategoryService); + final List categoryDrafts = + singletonList( + getMockCategoryDraft(Locale.ENGLISH, "name", "key", "parentKey", "", new HashMap<>())); + + final CategorySyncStatistics syncStatistics = + categorySync.sync(categoryDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo( + format( + "Failed to process the CategoryDraft with" + + " key:'key'. Reason: %s: Failed to resolve custom type reference on CategoryDraft " + + "with key:'key'. Reason: %s", + ReferenceResolutionException.class.getCanonicalName(), + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); + assertThat(errorCallBackExceptions.get(0).getCause()) + .isExactlyInstanceOf(ReferenceResolutionException.class); + } + + @Test + void requiresChangeParentUpdateAction_WithTwoDifferentParents_ShouldReturnTrue() { + final String parentId = "parentId"; + final Category category = mock(Category.class); + when(category.getParent()).thenReturn(Category.referenceOfId(parentId)); + + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "name"), + LocalizedString.of(Locale.ENGLISH, "slug")) + .parent(ResourceIdentifier.ofId("differentParent")) + .build(); + final boolean doesRequire = + CategorySync.requiresChangeParentUpdateAction(category, categoryDraft); + assertThat(doesRequire).isTrue(); + } + + @Test + void requiresChangeParentUpdateAction_WithTwoIdenticalParents_ShouldReturnFalse() { + final String parentId = "parentId"; + final String parentKey = "parentkey"; + final Category category = mock(Category.class); + when(category.getParent()).thenReturn(Category.referenceOfId(parentId)); + + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "name"), + LocalizedString.of(Locale.ENGLISH, "slug")) + .parent(ResourceIdentifier.ofIdOrKey(parentId, parentKey)) + .build(); + + // checking with ids (on draft reference id will be resolved, but in test it's given) + final boolean doesRequire = + CategorySync.requiresChangeParentUpdateAction(category, categoryDraft); + assertThat(doesRequire).isFalse(); + } + + @Test + void requiresChangeParentUpdateAction_WithNonExistingParents_ShouldReturnFalse() { + final Category category = mock(Category.class); + when(category.getParent()).thenReturn(null); + + final CategoryDraft categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(Locale.ENGLISH, "name"), + LocalizedString.of(Locale.ENGLISH, "slug")) + .build(); + final boolean doesRequire = + CategorySync.requiresChangeParentUpdateAction(category, categoryDraft); + assertThat(doesRequire).isFalse(); + } + + @Test + void sync_WithBatchSizeSet_ShouldCallSyncOnEachBatch() { + // preparation + final int batchSize = 1; + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }) - .batchSize(batchSize) - .build(); - final int numberOfCategoryDrafts = 160; - final List mockedCreatedCategories = - IntStream.range(0, numberOfCategoryDrafts) - .mapToObj(i -> getMockCategory(UUID.randomUUID().toString(), "key" + i)) - .collect(Collectors.toList()); - final List categoryDrafts = - mockedCreatedCategories.stream() - .map(category -> CategoryDraftBuilder.of(category).build()) - .collect(Collectors.toList()); - - final Category createdCategory = mock(Category.class); - when(createdCategory.getKey()).thenReturn("foo"); - when(createdCategory.getId()).thenReturn(UUID.randomUUID().toString()); - - final CategoryService mockCategoryService = mockCategoryService(emptySet(), createdCategory); - - final CategorySyncMock categorySync = new CategorySyncMock(categorySyncOptions, getMockTypeService(), - mockCategoryService); - final CategorySyncMock syncSpy = spy(categorySync); - - // test - syncSpy.sync(categoryDrafts).toCompletableFuture().join(); - - // assertion - int expectedNumberOfCalls = (int) (Math.ceil(numberOfCategoryDrafts / batchSize) + 1); - verify(syncSpy, times(expectedNumberOfCalls)).syncBatches(any(), any()); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - } - - @Test - void sync_WithDefaultBatchSize_ShouldCallSyncOnEachBatch() { - // preparation - final int numberOfCategoryDrafts = 160; - final List mockedCreatedCategories = - IntStream.range(0, numberOfCategoryDrafts) - .mapToObj(i -> getMockCategory(UUID.randomUUID().toString(), "key" + i)) - .collect(Collectors.toList()); - final List categoryDrafts = - mockedCreatedCategories.stream() - .map(category -> CategoryDraftBuilder.of(category).build()) - .collect(Collectors.toList()); - - final Category createdCategory = mock(Category.class); - when(createdCategory.getKey()).thenReturn("foo"); - when(createdCategory.getId()).thenReturn(UUID.randomUUID().toString()); - - final CategoryService mockCategoryService = mockCategoryService(emptySet(), createdCategory); - - final CategorySyncMock categorySync = new CategorySyncMock(categorySyncOptions, getMockTypeService(), - mockCategoryService); - - final CategorySyncMock syncSpy = spy(categorySync); - - // test - syncSpy.sync(categoryDrafts).toCompletableFuture().join(); - - // assertion - final int expectedNumberOfCalls = - (int) (Math.ceil(numberOfCategoryDrafts / (double) CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT) + 1); - verify(syncSpy, times(expectedNumberOfCalls)).syncBatches(any(), any()); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - } - - @Test - void sync_WithFailOnCachingKeysToIds_ShouldTriggerErrorCallbackAndReturnProperStats() { - // preparation - final SphereClient mockClient = mock(SphereClient.class); - when(mockClient.execute(any(ResourceKeyIdGraphQlRequest.class))) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); + .batchSize(batchSize) + .build(); + final int numberOfCategoryDrafts = 160; + final List mockedCreatedCategories = + IntStream.range(0, numberOfCategoryDrafts) + .mapToObj(i -> getMockCategory(UUID.randomUUID().toString(), "key" + i)) + .collect(Collectors.toList()); + final List categoryDrafts = + mockedCreatedCategories.stream() + .map(category -> CategoryDraftBuilder.of(category).build()) + .collect(Collectors.toList()); + + final Category createdCategory = mock(Category.class); + when(createdCategory.getKey()).thenReturn("foo"); + when(createdCategory.getId()).thenReturn(UUID.randomUUID().toString()); + + final CategoryService mockCategoryService = mockCategoryService(emptySet(), createdCategory); + + final CategorySyncMock categorySync = + new CategorySyncMock(categorySyncOptions, getMockTypeService(), mockCategoryService); + final CategorySyncMock syncSpy = spy(categorySync); + + // test + syncSpy.sync(categoryDrafts).toCompletableFuture().join(); + + // assertion + int expectedNumberOfCalls = (int) (Math.ceil(numberOfCategoryDrafts / batchSize) + 1); + verify(syncSpy, times(expectedNumberOfCalls)).syncBatches(any(), any()); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + } + + @Test + void sync_WithDefaultBatchSize_ShouldCallSyncOnEachBatch() { + // preparation + final int numberOfCategoryDrafts = 160; + final List mockedCreatedCategories = + IntStream.range(0, numberOfCategoryDrafts) + .mapToObj(i -> getMockCategory(UUID.randomUUID().toString(), "key" + i)) + .collect(Collectors.toList()); + final List categoryDrafts = + mockedCreatedCategories.stream() + .map(category -> CategoryDraftBuilder.of(category).build()) + .collect(Collectors.toList()); + + final Category createdCategory = mock(Category.class); + when(createdCategory.getKey()).thenReturn("foo"); + when(createdCategory.getId()).thenReturn(UUID.randomUUID().toString()); + + final CategoryService mockCategoryService = mockCategoryService(emptySet(), createdCategory); + + final CategorySyncMock categorySync = + new CategorySyncMock(categorySyncOptions, getMockTypeService(), mockCategoryService); + + final CategorySyncMock syncSpy = spy(categorySync); + + // test + syncSpy.sync(categoryDrafts).toCompletableFuture().join(); + + // assertion + final int expectedNumberOfCalls = + (int) + (Math.ceil( + numberOfCategoryDrafts / (double) CategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT) + + 1); + verify(syncSpy, times(expectedNumberOfCalls)).syncBatches(any(), any()); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + } + + @Test + void sync_WithFailOnCachingKeysToIds_ShouldTriggerErrorCallbackAndReturnProperStats() { + // preparation + final SphereClient mockClient = mock(SphereClient.class); + when(mockClient.execute(any(ResourceKeyIdGraphQlRequest.class))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); })); - final CategorySyncOptions syncOptions = CategorySyncOptionsBuilder - .of(mockClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); + final CategorySyncOptions syncOptions = + CategorySyncOptionsBuilder.of(mockClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }) - .build(); - - final CategoryService categoryServiceSpy = spy(new CategoryServiceImpl(syncOptions)); - - final CategorySync mockCategorySync = new CategorySync(syncOptions, getMockTypeService(), categoryServiceSpy); - - CategoryDraft categoryDraft = - getMockCategoryDraft(Locale.ENGLISH, "name", "newKey", "parentKey", "customTypeId", new HashMap<>()); - - // test - final CategorySyncStatistics syncStatistics = mockCategorySync.sync(singletonList(categoryDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(syncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to build a cache of keys to ids.")); + .build(); + + final CategoryService categoryServiceSpy = spy(new CategoryServiceImpl(syncOptions)); + + final CategorySync mockCategorySync = + new CategorySync(syncOptions, getMockTypeService(), categoryServiceSpy); + + CategoryDraft categoryDraft = + getMockCategoryDraft( + Locale.ENGLISH, "name", "newKey", "parentKey", "customTypeId", new HashMap<>()); + + // test + final CategorySyncStatistics syncStatistics = + mockCategorySync.sync(singletonList(categoryDraft)).toCompletableFuture().join(); + + // assertions + assertThat(syncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to build a cache of keys to ids.")); + + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + }); + } + + @Test + void sync_WithFailOnFetchingCategories_ShouldTriggerErrorCallbackAndReturnProperStats() { + // preparation + final SphereClient mockClient = mock(SphereClient.class); + + final String categoryKey = "key"; + final Category mockCategory = getMockCategory("foo", categoryKey); + ResourceKeyId resourceKeyId = new ResourceKeyId(categoryKey, "foo"); + + @SuppressWarnings("unchecked") + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = + mock(ResourceKeyIdGraphQlResult.class); + when(resourceKeyIdGraphQlResult.getResults()).thenReturn(singleton(resourceKeyId)); + + // successful caching + when(mockClient.execute(any(ResourceKeyIdGraphQlRequest.class))) + .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); + + // exception on fetch. + when(mockClient.execute(any(CategoryQuery.class))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); - }); - } + when(mockClient.execute(any(CategoryCreateCommand.class))) + .thenReturn(CompletableFuture.completedFuture(mockCategory)); - @Test - void sync_WithFailOnFetchingCategories_ShouldTriggerErrorCallbackAndReturnProperStats() { - // preparation - final SphereClient mockClient = mock(SphereClient.class); - - final String categoryKey = "key"; - final Category mockCategory = getMockCategory("foo", categoryKey); - ResourceKeyId resourceKeyId = new ResourceKeyId(categoryKey, "foo"); - - @SuppressWarnings("unchecked") final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock( - ResourceKeyIdGraphQlResult.class); - when(resourceKeyIdGraphQlResult.getResults()).thenReturn(singleton(resourceKeyId)); - - // successful caching - when(mockClient.execute(any(ResourceKeyIdGraphQlRequest.class))) - .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); - - // exception on fetch. - when(mockClient.execute(any(CategoryQuery.class))) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - when(mockClient.execute(any(CategoryCreateCommand.class))) - .thenReturn(CompletableFuture.completedFuture(mockCategory)); - - final CategorySyncOptions syncOptions = CategorySyncOptionsBuilder - .of(mockClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); + final CategorySyncOptions syncOptions = + CategorySyncOptionsBuilder.of(mockClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }) - .build(); - - final CategoryService categoryServiceSpy = spy(new CategoryServiceImpl(syncOptions)); - - final CategorySync mockCategorySync = new CategorySync(syncOptions, getMockTypeService(), categoryServiceSpy); - - final CategoryDraft categoryDraft = - getMockCategoryDraft(Locale.ENGLISH, "name", categoryKey, "parentKey", "customTypeId", new HashMap<>()); - - // test - final CategorySyncStatistics syncStatistics = mockCategorySync.sync(singletonList(categoryDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(syncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorCallBackMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to fetch existing categories")); - - assertThat(errorCallBackExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); - }); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { - // preparation - final CategoryDraft categoryDraft = - getMockCategoryDraft(Locale.ENGLISH, "name", "foo", "parentKey", "customTypeId", new HashMap<>()); - - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); - - final Category createdCategory = mock(Category.class); - when(createdCategory.getKey()).thenReturn(categoryDraft.getKey()); - - final CategoryService categoryService = mockCategoryService(emptySet(), createdCategory); - - final CategorySyncOptions spyCategorySyncOptions = spy(categorySyncOptions); - - // test - new CategorySync(spyCategorySyncOptions, getMockTypeService(), categoryService) - .sync(singletonList(categoryDraft)).toCompletableFuture().join(); - - // assertion - verify(spyCategorySyncOptions).applyBeforeCreateCallback(any()); - verify(spyCategorySyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - @Test - void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { - // preparation - final CategoryDraft categoryDraft = - getMockCategoryDraft(Locale.ENGLISH, "name", "1", "parentKey", "customTypeId", new HashMap<>()); - - final Category mockedExistingCategory = - readObjectFromResource(CATEGORY_KEY_1_RESOURCE_PATH, Category.class); - - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); - - final CategoryService categoryService = mockCategoryService(singleton(mockedExistingCategory), - mockedExistingCategory, mockedExistingCategory); - - when(categoryService.cacheKeysToIds(anySet())) - .thenReturn(completedFuture(singletonMap("1", UUID.randomUUID().toString()))); - - final CategorySyncOptions spyCategorySyncOptions = spy(categorySyncOptions); - - // test - new CategorySync(spyCategorySyncOptions, getMockTypeService(), categoryService) - .sync(singletonList(categoryDraft)).toCompletableFuture().join(); - - // assertion - verify(spyCategorySyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spyCategorySyncOptions, never()).applyBeforeCreateCallback(any()); - } + .build(); + + final CategoryService categoryServiceSpy = spy(new CategoryServiceImpl(syncOptions)); + + final CategorySync mockCategorySync = + new CategorySync(syncOptions, getMockTypeService(), categoryServiceSpy); + + final CategoryDraft categoryDraft = + getMockCategoryDraft( + Locale.ENGLISH, "name", categoryKey, "parentKey", "customTypeId", new HashMap<>()); + + // test + final CategorySyncStatistics syncStatistics = + mockCategorySync.sync(singletonList(categoryDraft)).toCompletableFuture().join(); + + // assertions + assertThat(syncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorCallBackMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to fetch existing categories")); + + assertThat(errorCallBackExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + }); + } + + @Test + void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { + // preparation + final CategoryDraft categoryDraft = + getMockCategoryDraft( + Locale.ENGLISH, "name", "foo", "parentKey", "customTypeId", new HashMap<>()); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final Category createdCategory = mock(Category.class); + when(createdCategory.getKey()).thenReturn(categoryDraft.getKey()); + + final CategoryService categoryService = mockCategoryService(emptySet(), createdCategory); + + final CategorySyncOptions spyCategorySyncOptions = spy(categorySyncOptions); + + // test + new CategorySync(spyCategorySyncOptions, getMockTypeService(), categoryService) + .sync(singletonList(categoryDraft)) + .toCompletableFuture() + .join(); + + // assertion + verify(spyCategorySyncOptions).applyBeforeCreateCallback(any()); + verify(spyCategorySyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { + // preparation + final CategoryDraft categoryDraft = + getMockCategoryDraft( + Locale.ENGLISH, "name", "1", "parentKey", "customTypeId", new HashMap<>()); + + final Category mockedExistingCategory = + readObjectFromResource(CATEGORY_KEY_1_RESOURCE_PATH, Category.class); + + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final CategoryService categoryService = + mockCategoryService( + singleton(mockedExistingCategory), mockedExistingCategory, mockedExistingCategory); + + when(categoryService.cacheKeysToIds(anySet())) + .thenReturn(completedFuture(singletonMap("1", UUID.randomUUID().toString()))); + + final CategorySyncOptions spyCategorySyncOptions = spy(categorySyncOptions); + + // test + new CategorySync(spyCategorySyncOptions, getMockTypeService(), categoryService) + .sync(singletonList(categoryDraft)) + .toCompletableFuture() + .join(); + + // assertion + verify(spyCategorySyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spyCategorySyncOptions, never()).applyBeforeCreateCallback(any()); + } } diff --git a/src/test/java/com/commercetools/sync/categories/helpers/CategoryAssetActionFactoryTest.java b/src/test/java/com/commercetools/sync/categories/helpers/CategoryAssetActionFactoryTest.java index d1d24f6d57..fad158663b 100644 --- a/src/test/java/com/commercetools/sync/categories/helpers/CategoryAssetActionFactoryTest.java +++ b/src/test/java/com/commercetools/sync/categories/helpers/CategoryAssetActionFactoryTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.categories.helpers; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Lists.emptyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; import io.sphere.sdk.categories.Category; @@ -13,81 +19,70 @@ import io.sphere.sdk.models.Asset; import io.sphere.sdk.models.AssetDraft; import io.sphere.sdk.models.AssetDraftBuilder; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashSet; import java.util.List; - -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.util.Lists.emptyList; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CategoryAssetActionFactoryTest { - private CategoryAssetActionFactory categoryAssetActionFactory; - - @BeforeEach - void setup() { - final CategorySyncOptions syncOptions = CategorySyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); + private CategoryAssetActionFactory categoryAssetActionFactory; - categoryAssetActionFactory = new CategoryAssetActionFactory(syncOptions); - } + @BeforeEach + void setup() { + final CategorySyncOptions syncOptions = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); - @Test - void buildRemoveAssetAction_always_ShouldBuildCorrectAction() { - final UpdateAction action = categoryAssetActionFactory.buildRemoveAssetAction("foo"); - assertThat(action).isNotNull(); - assertThat(action).isInstanceOf(RemoveAsset.class); - final RemoveAsset removeAsset = (RemoveAsset) action; - assertThat(removeAsset.getAssetId()).isNull(); - assertThat(removeAsset.getAssetKey()).isEqualTo("foo"); - } + categoryAssetActionFactory = new CategoryAssetActionFactory(syncOptions); + } - @Test - void buildChangeAssetOrderAction_always_ShouldBuildCorrectAction() { - final UpdateAction action = categoryAssetActionFactory.buildChangeAssetOrderAction(emptyList()); - assertThat(action).isNotNull(); - assertThat(action).isInstanceOf(ChangeAssetOrder.class); - final ChangeAssetOrder changeAssetOrder = (ChangeAssetOrder) action; - assertThat(changeAssetOrder.getAssetOrder()).isEqualTo(emptyList()); - } + @Test + void buildRemoveAssetAction_always_ShouldBuildCorrectAction() { + final UpdateAction action = categoryAssetActionFactory.buildRemoveAssetAction("foo"); + assertThat(action).isNotNull(); + assertThat(action).isInstanceOf(RemoveAsset.class); + final RemoveAsset removeAsset = (RemoveAsset) action; + assertThat(removeAsset.getAssetId()).isNull(); + assertThat(removeAsset.getAssetKey()).isEqualTo("foo"); + } - @Test - void buildAddAssetAction_always_ShouldBuildCorrectAction() { - final AssetDraft assetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .build(); + @Test + void buildChangeAssetOrderAction_always_ShouldBuildCorrectAction() { + final UpdateAction action = + categoryAssetActionFactory.buildChangeAssetOrderAction(emptyList()); + assertThat(action).isNotNull(); + assertThat(action).isInstanceOf(ChangeAssetOrder.class); + final ChangeAssetOrder changeAssetOrder = (ChangeAssetOrder) action; + assertThat(changeAssetOrder.getAssetOrder()).isEqualTo(emptyList()); + } + @Test + void buildAddAssetAction_always_ShouldBuildCorrectAction() { + final AssetDraft assetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")).build(); - final UpdateAction action = categoryAssetActionFactory.buildAddAssetAction(assetDraft, 0); - assertThat(action).isNotNull(); - assertThat(action).isInstanceOf(AddAsset.class); - final AddAsset addAsset = (AddAsset) action; - assertThat(addAsset.getAsset().getName()).isEqualTo(ofEnglish("assetName")); - assertThat(addAsset.getAsset().getSources()).isEqualTo(emptyList()); - assertThat(addAsset.getPosition()).isEqualTo(0); - } + final UpdateAction action = + categoryAssetActionFactory.buildAddAssetAction(assetDraft, 0); + assertThat(action).isNotNull(); + assertThat(action).isInstanceOf(AddAsset.class); + final AddAsset addAsset = (AddAsset) action; + assertThat(addAsset.getAsset().getName()).isEqualTo(ofEnglish("assetName")); + assertThat(addAsset.getAsset().getSources()).isEqualTo(emptyList()); + assertThat(addAsset.getPosition()).isEqualTo(0); + } - @Test - void buildAssetActions_always_ShouldBuildCorrectAction() { - final HashSet newTags = new HashSet<>(); - newTags.add("newTag"); - final Asset asset = mock(Asset.class); - when(asset.getKey()).thenReturn("assetKey"); - when(asset.getName()).thenReturn(ofEnglish("assetName")); - final AssetDraft assetDraft = AssetDraftBuilder.of(asset) - .tags(newTags) - .build(); - Category category = mock(Category.class); - CategoryDraft categoryDraft = mock(CategoryDraft.class); - final List> updateActions = categoryAssetActionFactory - .buildAssetActions(category, categoryDraft, asset, assetDraft); + @Test + void buildAssetActions_always_ShouldBuildCorrectAction() { + final HashSet newTags = new HashSet<>(); + newTags.add("newTag"); + final Asset asset = mock(Asset.class); + when(asset.getKey()).thenReturn("assetKey"); + when(asset.getName()).thenReturn(ofEnglish("assetName")); + final AssetDraft assetDraft = AssetDraftBuilder.of(asset).tags(newTags).build(); + Category category = mock(Category.class); + CategoryDraft categoryDraft = mock(CategoryDraft.class); + final List> updateActions = + categoryAssetActionFactory.buildAssetActions(category, categoryDraft, asset, assetDraft); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).containsExactly( - SetAssetTags.ofKey(asset.getKey(), newTags) - ); - } + assertThat(updateActions).isNotNull(); + assertThat(updateActions).containsExactly(SetAssetTags.ofKey(asset.getKey(), newTags)); + } } diff --git a/src/test/java/com/commercetools/sync/categories/helpers/CategoryBatchValidatorTest.java b/src/test/java/com/commercetools/sync/categories/helpers/CategoryBatchValidatorTest.java index b0d41e6551..3284fa82be 100644 --- a/src/test/java/com/commercetools/sync/categories/helpers/CategoryBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/categories/helpers/CategoryBatchValidatorTest.java @@ -1,122 +1,128 @@ package com.commercetools.sync.categories.helpers; +import static com.commercetools.sync.categories.helpers.CategoryBatchValidator.CATEGORY_DRAFT_IS_NULL; +import static com.commercetools.sync.categories.helpers.CategoryBatchValidator.CATEGORY_DRAFT_KEY_NOT_SET; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; import io.sphere.sdk.categories.CategoryDraft; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.types.CustomFieldsDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; - -import static com.commercetools.sync.categories.helpers.CategoryBatchValidator.CATEGORY_DRAFT_IS_NULL; -import static com.commercetools.sync.categories.helpers.CategoryBatchValidator.CATEGORY_DRAFT_KEY_NOT_SET; -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CategoryBatchValidatorTest { - private CategorySyncOptions syncOptions; - private CategorySyncStatistics syncStatistics; - private List errorCallBackMessages; + private CategorySyncOptions syncOptions; + private CategorySyncStatistics syncStatistics; + private List errorCallBackMessages; - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = CategorySyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorCallBackMessages.add(exception.getMessage())) + syncOptions = + CategorySyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallBackMessages.add(exception.getMessage())) .build(); - syncStatistics = mock(CategorySyncStatistics.class); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.emptyList()); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullCategoryDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.singletonList(null)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(CATEGORY_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCategoryDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final CategoryDraft categoryDraft = mock(CategoryDraft.class); - final Set validDrafts = getValidDrafts(Collections.singletonList(categoryDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format(CATEGORY_DRAFT_KEY_NOT_SET, categoryDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCategoryDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { - final CategoryDraft categoryDraft = mock(CategoryDraft.class); - when(categoryDraft.getKey()).thenReturn(EMPTY); - final Set validDrafts = getValidDrafts(Collections.singletonList(categoryDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format(CATEGORY_DRAFT_KEY_NOT_SET, categoryDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { - final CategoryDraft validCategoryDraft = mock(CategoryDraft.class); - when(validCategoryDraft.getKey()).thenReturn("validDraftKey"); - when(validCategoryDraft.getParent()).thenReturn(ResourceIdentifier.ofKey("validParentKey")); - when(validCategoryDraft.getCustom()) - .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", Collections.emptyMap())); - - final CategoryDraft validMainCategoryDraft = mock(CategoryDraft.class); - when(validMainCategoryDraft.getKey()).thenReturn("validDraftKey1"); - - final CategoryDraft invalidCategoryDraft = mock(CategoryDraft.class); - when(invalidCategoryDraft.getParent()).thenReturn(ResourceIdentifier.ofKey("key")); - - final CategoryBatchValidator categoryBatchValidator = new CategoryBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, CategoryBatchValidator.ReferencedKeys> pair - = categoryBatchValidator.validateAndCollectReferencedKeys( + syncStatistics = mock(CategorySyncStatistics.class); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.emptyList()); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullCategoryDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.singletonList(null)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(CATEGORY_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCategoryDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final CategoryDraft categoryDraft = mock(CategoryDraft.class); + final Set validDrafts = getValidDrafts(Collections.singletonList(categoryDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CATEGORY_DRAFT_KEY_NOT_SET, categoryDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCategoryDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { + final CategoryDraft categoryDraft = mock(CategoryDraft.class); + when(categoryDraft.getKey()).thenReturn(EMPTY); + final Set validDrafts = getValidDrafts(Collections.singletonList(categoryDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CATEGORY_DRAFT_KEY_NOT_SET, categoryDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { + final CategoryDraft validCategoryDraft = mock(CategoryDraft.class); + when(validCategoryDraft.getKey()).thenReturn("validDraftKey"); + when(validCategoryDraft.getParent()).thenReturn(ResourceIdentifier.ofKey("validParentKey")); + when(validCategoryDraft.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", Collections.emptyMap())); + + final CategoryDraft validMainCategoryDraft = mock(CategoryDraft.class); + when(validMainCategoryDraft.getKey()).thenReturn("validDraftKey1"); + + final CategoryDraft invalidCategoryDraft = mock(CategoryDraft.class); + when(invalidCategoryDraft.getParent()).thenReturn(ResourceIdentifier.ofKey("key")); + + final CategoryBatchValidator categoryBatchValidator = + new CategoryBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, CategoryBatchValidator.ReferencedKeys> pair = + categoryBatchValidator.validateAndCollectReferencedKeys( Arrays.asList(validCategoryDraft, invalidCategoryDraft, validMainCategoryDraft)); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CATEGORY_DRAFT_KEY_NOT_SET, invalidCategoryDraft.getName())); - assertThat(pair.getLeft()) - .containsExactlyInAnyOrder(validCategoryDraft, validMainCategoryDraft); - assertThat(pair.getRight().getCategoryKeys()) - .containsExactlyInAnyOrder("validDraftKey", "validParentKey", "validDraftKey1"); - assertThat(pair.getRight().getTypeKeys()) - .containsExactlyInAnyOrder("typeKey"); - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List categoryDrafts) { - final CategoryBatchValidator categoryBatchValidator = new CategoryBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, CategoryBatchValidator.ReferencedKeys> pair = - categoryBatchValidator.validateAndCollectReferencedKeys(categoryDrafts); - return pair.getLeft(); - } + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CATEGORY_DRAFT_KEY_NOT_SET, invalidCategoryDraft.getName())); + assertThat(pair.getLeft()) + .containsExactlyInAnyOrder(validCategoryDraft, validMainCategoryDraft); + assertThat(pair.getRight().getCategoryKeys()) + .containsExactlyInAnyOrder("validDraftKey", "validParentKey", "validDraftKey1"); + assertThat(pair.getRight().getTypeKeys()).containsExactlyInAnyOrder("typeKey"); + } + + @Nonnull + private Set getValidDrafts(@Nonnull final List categoryDrafts) { + final CategoryBatchValidator categoryBatchValidator = + new CategoryBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, CategoryBatchValidator.ReferencedKeys> pair = + categoryBatchValidator.validateAndCollectReferencedKeys(categoryDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/categories/helpers/CategoryReferenceResolverTest.java b/src/test/java/com/commercetools/sync/categories/helpers/CategoryReferenceResolverTest.java index 841acb1b28..e287eb00f9 100644 --- a/src/test/java/com/commercetools/sync/categories/helpers/CategoryReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/categories/helpers/CategoryReferenceResolverTest.java @@ -1,5 +1,23 @@ package com.commercetools.sync.categories.helpers; +import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategoryDraftBuilder; +import static com.commercetools.sync.categories.helpers.CategoryReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; +import static com.commercetools.sync.categories.helpers.CategoryReferenceResolver.FAILED_TO_RESOLVE_PARENT; +import static com.commercetools.sync.categories.helpers.CategoryReferenceResolver.PARENT_CATEGORY_DOES_NOT_EXIST; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; @@ -21,337 +39,380 @@ import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.queries.TypeQuery; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategoryDraftBuilder; -import static com.commercetools.sync.categories.helpers.CategoryReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; -import static com.commercetools.sync.categories.helpers.CategoryReferenceResolver.FAILED_TO_RESOLVE_PARENT; -import static com.commercetools.sync.categories.helpers.CategoryReferenceResolver.PARENT_CATEGORY_DOES_NOT_EXIST; -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CategoryReferenceResolverTest { - private TypeService typeService; - private CategoryService categoryService; - - private static final String CACHED_CATEGORY_ID = UUID.randomUUID().toString(); - private static final String CACHED_CATEGORY_KEY = "someKey"; - - - private CategoryReferenceResolver referenceResolver; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - typeService = getMockTypeService(); - categoryService = mock(CategoryService.class); - when(categoryService.fetchCachedCategoryId(CACHED_CATEGORY_KEY)) - .thenReturn(CompletableFuture.completedFuture(Optional.of(CACHED_CATEGORY_ID))); - final CategorySyncOptions syncOptions = CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new CategoryReferenceResolver(syncOptions, typeService, categoryService); - } - - @Test - void resolveAssetsReferences_WithEmptyAssets_ShouldNotResolveAssets() { - final CategoryDraftBuilder categoryDraftBuilder = - getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", CACHED_CATEGORY_KEY, - "customTypeKey", new HashMap<>()) - .assets(emptyList()); - - final CategoryDraftBuilder resolvedBuilder = referenceResolver.resolveAssetsReferences(categoryDraftBuilder) - .toCompletableFuture().join(); - - final List resolvedBuilderAssets = resolvedBuilder.getAssets(); - assertThat(resolvedBuilderAssets).isEmpty(); - } - - @Test - void resolveAssetsReferences_WithNullAssets_ShouldNotResolveAssets() { - final CategoryDraftBuilder categoryDraftBuilder = - getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", CACHED_CATEGORY_KEY, - "customTypeKey", new HashMap<>()) - .assets(null); - - final CategoryDraftBuilder resolvedBuilder = referenceResolver.resolveAssetsReferences(categoryDraftBuilder) - .toCompletableFuture().join(); - - final List resolvedBuilderAssets = resolvedBuilder.getAssets(); - assertThat(resolvedBuilderAssets).isNull(); - } - - @Test - void resolveAssetsReferences_WithANullAsset_ShouldNotResolveAssets() { - final CategoryDraftBuilder categoryDraftBuilder = - getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", CACHED_CATEGORY_KEY, - "customTypeKey", new HashMap<>()).assets(singletonList(null)); - - final CategoryDraftBuilder resolvedBuilder = referenceResolver.resolveAssetsReferences(categoryDraftBuilder) - .toCompletableFuture().join(); - - final List resolvedBuilderAssets = resolvedBuilder.getAssets(); - assertThat(resolvedBuilderAssets).isEmpty(); - } - - @Test - void resolveAssetsReferences_WithAssetReferences_ShouldResolveAssets() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("customTypeKey", new HashMap<>()); - - final AssetDraft assetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .custom(customFieldsDraft) - .build(); - - - final CategoryDraftBuilder categoryDraftBuilder = - getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", CACHED_CATEGORY_KEY, - "customTypeKey", new HashMap<>()).assets(singletonList(assetDraft)); - - final CategoryDraftBuilder resolvedBuilder = referenceResolver.resolveAssetsReferences(categoryDraftBuilder) - .toCompletableFuture().join(); - - final List resolvedBuilderAssets = resolvedBuilder.getAssets(); - assertThat(resolvedBuilderAssets).hasSize(1); - assertThat(resolvedBuilderAssets).allSatisfy(resolvedDraft -> { - assertThat(resolvedDraft).isNotNull(); - assertThat(resolvedDraft.getCustom()).isNotNull(); - assertThat(resolvedDraft.getCustom().getType().getId()).isEqualTo("typeId"); - }); - } - - @Test - void resolveParentReference_WithExceptionOnFetch_ShouldNotResolveReferences() { - // Preparation - final SphereClient ctpClient = mock(SphereClient.class); - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(ctpClient) - .build(); - final CategoryService categoryService = new CategoryServiceImpl(categorySyncOptions); - - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(ctpClient.execute(any(CategoryQuery.class))).thenReturn(futureThrowingSphereException); - - final CategoryDraftBuilder categoryDraft = getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", - "nonExistingCategoryKey", "customTypeKey", new HashMap<>()); - - final CategoryReferenceResolver categoryReferenceResolver = new CategoryReferenceResolver(categorySyncOptions, - typeService, categoryService); - - // Test and assertion - assertThat(categoryReferenceResolver.resolveParentReference(categoryDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveParentReference_WithNonExistentParentCategory_ShouldNotResolveParentReference() { - // Preparation - final CategoryDraftBuilder categoryDraft = getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", - CACHED_CATEGORY_KEY, "customTypeKey", new HashMap<>()); - when(categoryService.fetchCachedCategoryId(CACHED_CATEGORY_KEY)) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - // Test and assertion - final String expectedMessageWithCause = format(FAILED_TO_RESOLVE_PARENT, categoryDraft.getKey(), + private TypeService typeService; + private CategoryService categoryService; + + private static final String CACHED_CATEGORY_ID = UUID.randomUUID().toString(); + private static final String CACHED_CATEGORY_KEY = "someKey"; + + private CategoryReferenceResolver referenceResolver; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + typeService = getMockTypeService(); + categoryService = mock(CategoryService.class); + when(categoryService.fetchCachedCategoryId(CACHED_CATEGORY_KEY)) + .thenReturn(CompletableFuture.completedFuture(Optional.of(CACHED_CATEGORY_ID))); + final CategorySyncOptions syncOptions = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = new CategoryReferenceResolver(syncOptions, typeService, categoryService); + } + + @Test + void resolveAssetsReferences_WithEmptyAssets_ShouldNotResolveAssets() { + final CategoryDraftBuilder categoryDraftBuilder = + getMockCategoryDraftBuilder( + Locale.ENGLISH, + "myDraft", + "key", + CACHED_CATEGORY_KEY, + "customTypeKey", + new HashMap<>()) + .assets(emptyList()); + + final CategoryDraftBuilder resolvedBuilder = + referenceResolver + .resolveAssetsReferences(categoryDraftBuilder) + .toCompletableFuture() + .join(); + + final List resolvedBuilderAssets = resolvedBuilder.getAssets(); + assertThat(resolvedBuilderAssets).isEmpty(); + } + + @Test + void resolveAssetsReferences_WithNullAssets_ShouldNotResolveAssets() { + final CategoryDraftBuilder categoryDraftBuilder = + getMockCategoryDraftBuilder( + Locale.ENGLISH, + "myDraft", + "key", + CACHED_CATEGORY_KEY, + "customTypeKey", + new HashMap<>()) + .assets(null); + + final CategoryDraftBuilder resolvedBuilder = + referenceResolver + .resolveAssetsReferences(categoryDraftBuilder) + .toCompletableFuture() + .join(); + + final List resolvedBuilderAssets = resolvedBuilder.getAssets(); + assertThat(resolvedBuilderAssets).isNull(); + } + + @Test + void resolveAssetsReferences_WithANullAsset_ShouldNotResolveAssets() { + final CategoryDraftBuilder categoryDraftBuilder = + getMockCategoryDraftBuilder( + Locale.ENGLISH, + "myDraft", + "key", + CACHED_CATEGORY_KEY, + "customTypeKey", + new HashMap<>()) + .assets(singletonList(null)); + + final CategoryDraftBuilder resolvedBuilder = + referenceResolver + .resolveAssetsReferences(categoryDraftBuilder) + .toCompletableFuture() + .join(); + + final List resolvedBuilderAssets = resolvedBuilder.getAssets(); + assertThat(resolvedBuilderAssets).isEmpty(); + } + + @Test + void resolveAssetsReferences_WithAssetReferences_ShouldResolveAssets() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("customTypeKey", new HashMap<>()); + + final AssetDraft assetDraft = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")).custom(customFieldsDraft).build(); + + final CategoryDraftBuilder categoryDraftBuilder = + getMockCategoryDraftBuilder( + Locale.ENGLISH, + "myDraft", + "key", + CACHED_CATEGORY_KEY, + "customTypeKey", + new HashMap<>()) + .assets(singletonList(assetDraft)); + + final CategoryDraftBuilder resolvedBuilder = + referenceResolver + .resolveAssetsReferences(categoryDraftBuilder) + .toCompletableFuture() + .join(); + + final List resolvedBuilderAssets = resolvedBuilder.getAssets(); + assertThat(resolvedBuilderAssets).hasSize(1); + assertThat(resolvedBuilderAssets) + .allSatisfy( + resolvedDraft -> { + assertThat(resolvedDraft).isNotNull(); + assertThat(resolvedDraft.getCustom()).isNotNull(); + assertThat(resolvedDraft.getCustom().getType().getId()).isEqualTo("typeId"); + }); + } + + @Test + void resolveParentReference_WithExceptionOnFetch_ShouldNotResolveReferences() { + // Preparation + final SphereClient ctpClient = mock(SphereClient.class); + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(ctpClient).build(); + final CategoryService categoryService = new CategoryServiceImpl(categorySyncOptions); + + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(ctpClient.execute(any(CategoryQuery.class))).thenReturn(futureThrowingSphereException); + + final CategoryDraftBuilder categoryDraft = + getMockCategoryDraftBuilder( + Locale.ENGLISH, + "myDraft", + "key", + "nonExistingCategoryKey", + "customTypeKey", + new HashMap<>()); + + final CategoryReferenceResolver categoryReferenceResolver = + new CategoryReferenceResolver(categorySyncOptions, typeService, categoryService); + + // Test and assertion + assertThat(categoryReferenceResolver.resolveParentReference(categoryDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveParentReference_WithNonExistentParentCategory_ShouldNotResolveParentReference() { + // Preparation + final CategoryDraftBuilder categoryDraft = + getMockCategoryDraftBuilder( + Locale.ENGLISH, + "myDraft", + "key", + CACHED_CATEGORY_KEY, + "customTypeKey", + new HashMap<>()); + when(categoryService.fetchCachedCategoryId(CACHED_CATEGORY_KEY)) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + // Test and assertion + final String expectedMessageWithCause = + format( + FAILED_TO_RESOLVE_PARENT, + categoryDraft.getKey(), format(PARENT_CATEGORY_DOES_NOT_EXIST, CACHED_CATEGORY_KEY)); - assertThat(referenceResolver.resolveParentReference(categoryDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveParentReference_WithEmptyKeyOnParentResId_ShouldNotResolveParentReference() { - final CategoryDraftBuilder categoryDraft = CategoryDraftBuilder.of(ofEnglish("foo"), ofEnglish("bar")) - .key("key") - .parent(ResourceIdentifier.ofKey("")); - - assertThat(referenceResolver.resolveParentReference(categoryDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve parent reference on CategoryDraft with key:'key'. Reason: %s", + assertThat(referenceResolver.resolveParentReference(categoryDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void resolveParentReference_WithEmptyKeyOnParentResId_ShouldNotResolveParentReference() { + final CategoryDraftBuilder categoryDraft = + CategoryDraftBuilder.of(ofEnglish("foo"), ofEnglish("bar")) + .key("key") + .parent(ResourceIdentifier.ofKey("")); + + assertThat(referenceResolver.resolveParentReference(categoryDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve parent reference on CategoryDraft with key:'key'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveParentReference_WithNullKeyOnParentResId_ShouldNotResolveParentReference() { - // Preparation - final CategoryDraftBuilder categoryDraft = CategoryDraftBuilder.of(ofEnglish("foo"), ofEnglish("bar")) - .key("key") - .parent(ResourceIdentifier.ofKey(null)); - - assertThat(referenceResolver.resolveParentReference(categoryDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve parent reference on CategoryDraft with key:'key'. Reason: %s", + } + + @Test + void resolveParentReference_WithNullKeyOnParentResId_ShouldNotResolveParentReference() { + // Preparation + final CategoryDraftBuilder categoryDraft = + CategoryDraftBuilder.of(ofEnglish("foo"), ofEnglish("bar")) + .key("key") + .parent(ResourceIdentifier.ofKey(null)); + + assertThat(referenceResolver.resolveParentReference(categoryDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve parent reference on CategoryDraft with key:'key'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveParentReference_WithNonNullIdOnParentResId_ShouldResolveParentReference() { - // Preparation - final String parentId = UUID.randomUUID().toString(); - final CategoryDraftBuilder categoryDraft = CategoryDraftBuilder - .of(ofEnglish("foo"), ofEnglish("bar")) + } + + @Test + void resolveParentReference_WithNonNullIdOnParentResId_ShouldResolveParentReference() { + // Preparation + final String parentId = UUID.randomUUID().toString(); + final CategoryDraftBuilder categoryDraft = + CategoryDraftBuilder.of(ofEnglish("foo"), ofEnglish("bar")) .key("key") .parent(ResourceIdentifier.ofId(parentId)); - // Test - final CategoryDraftBuilder resolvedDraftBuilder = - referenceResolver.resolveParentReference(categoryDraft) - .toCompletableFuture() - .join(); - - // Assertion - assertThat(resolvedDraftBuilder.getParent()).isNotNull(); - assertThat(resolvedDraftBuilder.getParent().getId()).isEqualTo(parentId); - } - - @Test - void resolveParentReference_WithNonNullKeyOnParentResId_ShouldResolveParentReference() { - // Preparation - final CategoryDraftBuilder categoryDraft = CategoryDraftBuilder - .of(ofEnglish("foo"), ofEnglish("bar")) + // Test + final CategoryDraftBuilder resolvedDraftBuilder = + referenceResolver.resolveParentReference(categoryDraft).toCompletableFuture().join(); + + // Assertion + assertThat(resolvedDraftBuilder.getParent()).isNotNull(); + assertThat(resolvedDraftBuilder.getParent().getId()).isEqualTo(parentId); + } + + @Test + void resolveParentReference_WithNonNullKeyOnParentResId_ShouldResolveParentReference() { + // Preparation + final CategoryDraftBuilder categoryDraft = + CategoryDraftBuilder.of(ofEnglish("foo"), ofEnglish("bar")) .key("key") .parent(ResourceIdentifier.ofKey(CACHED_CATEGORY_KEY)); - // Test - final CategoryDraftBuilder resolvedDraftBuilder = - referenceResolver.resolveParentReference(categoryDraft) - .toCompletableFuture() - .join(); - - // Assertion - assertThat(resolvedDraftBuilder.getParent()).isNotNull(); - assertThat(resolvedDraftBuilder.getParent().getId()).isEqualTo(CACHED_CATEGORY_ID); - } - - @Test - void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { - // Preparation - final SphereClient ctpClient = mock(SphereClient.class); - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(ctpClient) - .build(); - final TypeService typeService = new TypeServiceImpl(categorySyncOptions); - - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(ctpClient.execute(any(TypeQuery.class))).thenReturn(futureThrowingSphereException); - - final CategoryDraftBuilder categoryDraft = getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", - null, "customTypeId", new HashMap<>()); - - final CategoryReferenceResolver categoryReferenceResolver = new CategoryReferenceResolver(categorySyncOptions, - typeService, categoryService); - - // Test and assertion - assertThat(categoryReferenceResolver.resolveCustomTypeReference(categoryDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { - // Preparation - final CategoryDraftBuilder categoryDraft = - getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", - null, "customTypeKey", new HashMap<>()); - - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - // Test and assertion - final String expectedExceptionMessage = format(FAILED_TO_RESOLVE_CUSTOM_TYPE, categoryDraft.getKey()); - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, "customTypeKey")); - - assertThat(referenceResolver.resolveCustomTypeReference(categoryDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { - final CategoryDraftBuilder categoryDraft = getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", - null, "", emptyMap()); - - assertThat(referenceResolver.resolveCustomTypeReference(categoryDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on CategoryDraft with key:'key'. Reason: %s", + // Test + final CategoryDraftBuilder resolvedDraftBuilder = + referenceResolver.resolveParentReference(categoryDraft).toCompletableFuture().join(); + + // Assertion + assertThat(resolvedDraftBuilder.getParent()).isNotNull(); + assertThat(resolvedDraftBuilder.getParent().getId()).isEqualTo(CACHED_CATEGORY_ID); + } + + @Test + void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { + // Preparation + final SphereClient ctpClient = mock(SphereClient.class); + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(ctpClient).build(); + final TypeService typeService = new TypeServiceImpl(categorySyncOptions); + + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(ctpClient.execute(any(TypeQuery.class))).thenReturn(futureThrowingSphereException); + + final CategoryDraftBuilder categoryDraft = + getMockCategoryDraftBuilder( + Locale.ENGLISH, "myDraft", "key", null, "customTypeId", new HashMap<>()); + + final CategoryReferenceResolver categoryReferenceResolver = + new CategoryReferenceResolver(categorySyncOptions, typeService, categoryService); + + // Test and assertion + assertThat(categoryReferenceResolver.resolveCustomTypeReference(categoryDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { + // Preparation + final CategoryDraftBuilder categoryDraft = + getMockCategoryDraftBuilder( + Locale.ENGLISH, "myDraft", "key", null, "customTypeKey", new HashMap<>()); + + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + // Test and assertion + final String expectedExceptionMessage = + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, categoryDraft.getKey()); + final String expectedMessageWithCause = + format( + "%s Reason: %s", + expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, "customTypeKey")); + + assertThat(referenceResolver.resolveCustomTypeReference(categoryDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { + final CategoryDraftBuilder categoryDraft = + getMockCategoryDraftBuilder(Locale.ENGLISH, "myDraft", "key", null, "", emptyMap()); + + assertThat(referenceResolver.resolveCustomTypeReference(categoryDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on CategoryDraft with key:'key'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomTypeReference_WithNonNullIdOnCustomTypeResId_ShouldResolveCustomTypeReference() { - // Preparation - final String customTypeId = UUID.randomUUID().toString(); - final CategoryDraftBuilder categoryDraft = - CategoryDraftBuilder.of(LocalizedString.ofEnglish("myDraft"), LocalizedString.ofEnglish("testSlug")) - .key("key") - .custom(CustomFieldsDraft.ofTypeIdAndJson(customTypeId, new HashMap<>())); - - // Test - final CategoryDraftBuilder resolvedDraftBuilder = referenceResolver.resolveCustomTypeReference(categoryDraft) - .toCompletableFuture().join(); - - // Assertion - assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); - assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo(customTypeId); - } - - @Test - void resolveCustomTypeReference_WithNonNullKeyOnCustomTypeResId_ShouldResolveCustomTypeReference() { - // Preparation - final CategoryDraftBuilder categoryDraft = - CategoryDraftBuilder.of(LocalizedString.ofEnglish("myDraft"), LocalizedString.ofEnglish("testSlug")) - .key("key") - .custom(CustomFieldsDraft.ofTypeKeyAndJson("myTypeKey", new HashMap<>())); - - // Test - final CategoryDraftBuilder resolvedDraftBuilder = referenceResolver.resolveCustomTypeReference(categoryDraft) - .toCompletableFuture().join(); - - // Assertion - assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); - assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo("typeId"); - } - - @Test - void resolveReferences_WithNoCustomTypeReferenceAndNoParentReference_ShouldNotResolveReferences() { - final CategoryDraft categoryDraft = mock(CategoryDraft.class); - when(categoryDraft.getName()).thenReturn(LocalizedString.of(Locale.ENGLISH, "myDraft")); - when(categoryDraft.getKey()).thenReturn("key"); - - final CategoryDraft referencesResolvedDraft = referenceResolver.resolveReferences(categoryDraft) - .toCompletableFuture().join(); - - assertThat(referencesResolvedDraft.getCustom()).isNull(); - assertThat(referencesResolvedDraft.getParent()).isNull(); - } + } + + @Test + void + resolveCustomTypeReference_WithNonNullIdOnCustomTypeResId_ShouldResolveCustomTypeReference() { + // Preparation + final String customTypeId = UUID.randomUUID().toString(); + final CategoryDraftBuilder categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.ofEnglish("myDraft"), LocalizedString.ofEnglish("testSlug")) + .key("key") + .custom(CustomFieldsDraft.ofTypeIdAndJson(customTypeId, new HashMap<>())); + + // Test + final CategoryDraftBuilder resolvedDraftBuilder = + referenceResolver.resolveCustomTypeReference(categoryDraft).toCompletableFuture().join(); + + // Assertion + assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); + assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo(customTypeId); + } + + @Test + void + resolveCustomTypeReference_WithNonNullKeyOnCustomTypeResId_ShouldResolveCustomTypeReference() { + // Preparation + final CategoryDraftBuilder categoryDraft = + CategoryDraftBuilder.of( + LocalizedString.ofEnglish("myDraft"), LocalizedString.ofEnglish("testSlug")) + .key("key") + .custom(CustomFieldsDraft.ofTypeKeyAndJson("myTypeKey", new HashMap<>())); + + // Test + final CategoryDraftBuilder resolvedDraftBuilder = + referenceResolver.resolveCustomTypeReference(categoryDraft).toCompletableFuture().join(); + + // Assertion + assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); + assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo("typeId"); + } + + @Test + void + resolveReferences_WithNoCustomTypeReferenceAndNoParentReference_ShouldNotResolveReferences() { + final CategoryDraft categoryDraft = mock(CategoryDraft.class); + when(categoryDraft.getName()).thenReturn(LocalizedString.of(Locale.ENGLISH, "myDraft")); + when(categoryDraft.getKey()).thenReturn("key"); + + final CategoryDraft referencesResolvedDraft = + referenceResolver.resolveReferences(categoryDraft).toCompletableFuture().join(); + + assertThat(referencesResolvedDraft.getCustom()).isNull(); + assertThat(referencesResolvedDraft.getParent()).isNull(); + } } diff --git a/src/test/java/com/commercetools/sync/categories/helpers/CategorySyncStatisticsTest.java b/src/test/java/com/commercetools/sync/categories/helpers/CategorySyncStatisticsTest.java index d206e8fc43..88bdcc8927 100644 --- a/src/test/java/com/commercetools/sync/categories/helpers/CategorySyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/categories/helpers/CategorySyncStatisticsTest.java @@ -1,273 +1,293 @@ package com.commercetools.sync.categories.helpers; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; - -import static java.util.Collections.emptySet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CategorySyncStatisticsTest { - private CategorySyncStatistics categorySyncStatistics; - - @BeforeEach - void setup() { - categorySyncStatistics = new CategorySyncStatistics(); - } - - @Test - void getUpdated_WithNoUpdated_ShouldReturnZero() { - assertThat(categorySyncStatistics.getUpdated()).hasValue(0); - } - - @Test - void incrementUpdated_ShouldIncrementUpdatedValue() { - categorySyncStatistics.incrementUpdated(); - assertThat(categorySyncStatistics.getUpdated()).hasValue(1); - } - - @Test - void getCreated_WithNoCreated_ShouldReturnZero() { - assertThat(categorySyncStatistics.getCreated()).hasValue(0); - } - - @Test - void incrementCreated_ShouldIncrementCreatedValue() { - categorySyncStatistics.incrementCreated(); - assertThat(categorySyncStatistics.getCreated()).hasValue(1); - } - - @Test - void getProcessed_WithNoProcessed_ShouldReturnZero() { - assertThat(categorySyncStatistics.getProcessed()).hasValue(0); - } - - @Test - void incrementProcessed_ShouldIncrementProcessedValue() { - categorySyncStatistics.incrementProcessed(); - assertThat(categorySyncStatistics.getProcessed()).hasValue(1); - } - - @Test - void getFailed_WithNoFailed_ShouldReturnZero() { - assertThat(categorySyncStatistics.getFailed()).hasValue(0); - } - - @Test - void incrementFailed_ShouldIncrementFailedValue() { - categorySyncStatistics.incrementFailed(); - assertThat(categorySyncStatistics.getFailed()).hasValue(1); - } - - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - categorySyncStatistics.incrementCreated(1); - categorySyncStatistics.incrementFailed(1); - categorySyncStatistics.incrementUpdated(1); - categorySyncStatistics.incrementProcessed(3); - - assertThat(categorySyncStatistics.getReportMessage()).isEqualTo("Summary: 3 categories were processed in total " - + "(1 created, 1 updated, 1 failed to sync and 0 categories with a missing parent)."); - } - - @Test - void getNumberOfCategoriesWithMissingParents_WithEmptyMap_ShouldReturn0() { - final ConcurrentHashMap> catKeysWithMissingParents = new ConcurrentHashMap<>(); - categorySyncStatistics.setCategoryKeysWithMissingParents(catKeysWithMissingParents); - - assertThat(categorySyncStatistics.getNumberOfCategoriesWithMissingParents()).isZero(); - } - - @Test - void getNumberOfCategoriesWithMissingParents_WithEmptyValue_ShouldReturn0() { - final ConcurrentHashMap> categoryKeysWithMissingParents = new ConcurrentHashMap<>(); - categoryKeysWithMissingParents.put("parent2", emptySet()); - - categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); - assertThat(categorySyncStatistics.getNumberOfCategoriesWithMissingParents()).isZero(); - } - - @Test - void getNumberOfCategoriesWithMissingParents_WithNonEmptyMap_ShouldReturnCorrectNumberOfChildren() { - final ConcurrentHashMap> categoryKeysWithMissingParents = new ConcurrentHashMap<>(); - final Set firstMissingParentChildrenKeys = new HashSet<>(); - firstMissingParentChildrenKeys.add("key1"); - firstMissingParentChildrenKeys.add("key2"); - - final Set secondMissingParentChildrenKeys = new HashSet<>(); - secondMissingParentChildrenKeys.add("key3"); - secondMissingParentChildrenKeys.add("key4"); - - categoryKeysWithMissingParents.put("parent1", firstMissingParentChildrenKeys); - categoryKeysWithMissingParents.put("parent2", secondMissingParentChildrenKeys); - - - categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); - - assertThat(categorySyncStatistics.getNumberOfCategoriesWithMissingParents()).isEqualTo(4); - } - - @Test - void getCategoryKeysWithMissingParents_WithAnyMap_ShouldGetUnmodifiableMapCorrectly() { - final ConcurrentHashMap> catKeysWithMissingParents = new ConcurrentHashMap<>(); - categorySyncStatistics.setCategoryKeysWithMissingParents(catKeysWithMissingParents); - - final Map> fetchedMap = categorySyncStatistics.getCategoryKeysWithMissingParents(); - - assertThatThrownBy(() -> fetchedMap.put("e", emptySet())) - .isExactlyInstanceOf(UnsupportedOperationException.class); - - assertThat(fetchedMap).isEqualTo(catKeysWithMissingParents); - } - - @Test - void putMissingParentCategoryChildKey_OnAnEmptyList_ShouldAddNewParentEntryInTheMap() { - final String parentKey = "parentKey"; - final String childKey = "childKey"; - categorySyncStatistics.putMissingParentCategoryChildKey(parentKey, childKey); - - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isNotEmpty(); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); - final Set childrenKeySet = categorySyncStatistics.getCategoryKeysWithMissingParents().get(parentKey); - assertThat(childrenKeySet).isNotNull(); - assertThat(childrenKeySet).hasSize(1); - assertThat(childrenKeySet.iterator().next()).isEqualTo(childKey); - } - - @Test - void putMissingParentCategoryChildKey_OnANonEmptyListWithNewParent_ShouldAddNewParentEntryInTheMap() { - final ConcurrentHashMap> categoryKeysWithMissingParents = new ConcurrentHashMap<>(); - final Set existingChildrenKeys = new HashSet<>(); - existingChildrenKeys.add("key1"); - existingChildrenKeys.add("key2"); - - final String existingParentKey = "existingParent"; - categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); - categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); - - final String newParentKey = "parentKey"; - final String newChildKey = "childKey"; - categorySyncStatistics.putMissingParentCategoryChildKey(newParentKey, newChildKey); - - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isNotEmpty(); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(2); - final Set newChildrenKeySet = categorySyncStatistics.getCategoryKeysWithMissingParents() - .get(newParentKey); - assertThat(newChildrenKeySet).isNotNull(); - assertThat(newChildrenKeySet).hasSize(1); - assertThat(newChildrenKeySet.iterator().next()).isEqualTo(newChildKey); - - final Set existingChildrenKeySet = categorySyncStatistics.getCategoryKeysWithMissingParents() - .get(existingParentKey); - assertThat(existingChildrenKeySet).isNotNull(); - assertThat(existingChildrenKeySet).hasSize(2); - assertThat(existingChildrenKeySet).isEqualTo(existingChildrenKeys); - } - - @Test - void putMissingParentCategoryChildKey_OnNonEmptyListWithExistingParent_ShouldAddParentToExistingEntry() { - final ConcurrentHashMap> categoryKeysWithMissingParents = new ConcurrentHashMap<>(); - final Set existingChildrenKeys = new HashSet<>(); - existingChildrenKeys.add("key1"); - existingChildrenKeys.add("key2"); - - final String existingParentKey = "existingParent"; - categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); - categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); - - final String newChildKey = "childKey"; - categorySyncStatistics.putMissingParentCategoryChildKey(existingParentKey, newChildKey); - - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isNotEmpty(); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); - final Set newChildrenKeySet = categorySyncStatistics.getCategoryKeysWithMissingParents() - .get(existingParentKey); - assertThat(newChildrenKeySet).isNotNull(); - assertThat(newChildrenKeySet).hasSize(3); - assertThat(newChildrenKeySet).containsOnly(newChildKey, "key1", "key2"); - } - - @Test - void getMissingParentKey_OAnEmptyList_ShouldReturnAnEmptyOptional() { - assertThat(categorySyncStatistics.getMissingParentKey("foo")).isEmpty(); - } - - @Test - void getMissingParentKey_OnANonEmptyList_ShouldReturnCorrectParentKeyInOptional() { - final ConcurrentHashMap> categoryKeysWithMissingParents = new ConcurrentHashMap<>(); - final Set existingChildrenKeys = new HashSet<>(); - existingChildrenKeys.add("key1"); - existingChildrenKeys.add("key2"); - - final String existingParentKey = "existingParent"; - categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); - categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); - - assertThat(categorySyncStatistics.getMissingParentKey("key1")).contains(existingParentKey); - assertThat(categorySyncStatistics.getMissingParentKey("key2")).contains(existingParentKey); - } - - @Test - void removeChildCategoryKeyFromMissingParentsMap_OnAEmptyList_ShouldNotAffectTheMap() { - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isEmpty(); - categorySyncStatistics.removeChildCategoryKeyFromMissingParentsMap("foo"); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isEmpty(); - } - - @Test - void removeChildCategoryKeyFromMissingParentsMap_OnANonEmptyListButNonExistingKey_ShouldNotAffectTheMap() { - final ConcurrentHashMap> categoryKeysWithMissingParents = new ConcurrentHashMap<>(); - final Set existingChildrenKeys = new HashSet<>(); - existingChildrenKeys.add("key1"); - existingChildrenKeys.add("key2"); - - final String existingParentKey = "existingParent"; - categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); - categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); - - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)).hasSize(2); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) - .containsOnly("key2", "key1"); - - categorySyncStatistics.removeChildCategoryKeyFromMissingParentsMap("foo"); - - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)).hasSize(2); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) - .containsOnly("key2", "key1"); - } - - @Test - void removeChildCategoryKeyFromMissingParentsMap_OnANonEmptyListWithExistingKey_ShouldNotAffectTheMap() { - final ConcurrentHashMap> categoryKeysWithMissingParents = new ConcurrentHashMap<>(); - final Set existingChildrenKeys = new HashSet<>(); - existingChildrenKeys.add("key1"); - existingChildrenKeys.add("key2"); - - final String existingParentKey = "existingParent"; - categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); - categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); - - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)).hasSize(2); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) - .isEqualTo(existingChildrenKeys); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) - .containsOnly("key2", "key1"); - - categorySyncStatistics.removeChildCategoryKeyFromMissingParentsMap("key1"); - - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)).hasSize(1); - assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) - .doesNotContain("key1").containsOnly("key2"); - } + private CategorySyncStatistics categorySyncStatistics; + + @BeforeEach + void setup() { + categorySyncStatistics = new CategorySyncStatistics(); + } + + @Test + void getUpdated_WithNoUpdated_ShouldReturnZero() { + assertThat(categorySyncStatistics.getUpdated()).hasValue(0); + } + + @Test + void incrementUpdated_ShouldIncrementUpdatedValue() { + categorySyncStatistics.incrementUpdated(); + assertThat(categorySyncStatistics.getUpdated()).hasValue(1); + } + + @Test + void getCreated_WithNoCreated_ShouldReturnZero() { + assertThat(categorySyncStatistics.getCreated()).hasValue(0); + } + + @Test + void incrementCreated_ShouldIncrementCreatedValue() { + categorySyncStatistics.incrementCreated(); + assertThat(categorySyncStatistics.getCreated()).hasValue(1); + } + + @Test + void getProcessed_WithNoProcessed_ShouldReturnZero() { + assertThat(categorySyncStatistics.getProcessed()).hasValue(0); + } + + @Test + void incrementProcessed_ShouldIncrementProcessedValue() { + categorySyncStatistics.incrementProcessed(); + assertThat(categorySyncStatistics.getProcessed()).hasValue(1); + } + + @Test + void getFailed_WithNoFailed_ShouldReturnZero() { + assertThat(categorySyncStatistics.getFailed()).hasValue(0); + } + + @Test + void incrementFailed_ShouldIncrementFailedValue() { + categorySyncStatistics.incrementFailed(); + assertThat(categorySyncStatistics.getFailed()).hasValue(1); + } + + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + categorySyncStatistics.incrementCreated(1); + categorySyncStatistics.incrementFailed(1); + categorySyncStatistics.incrementUpdated(1); + categorySyncStatistics.incrementProcessed(3); + + assertThat(categorySyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 3 categories were processed in total " + + "(1 created, 1 updated, 1 failed to sync and 0 categories with a missing parent)."); + } + + @Test + void getNumberOfCategoriesWithMissingParents_WithEmptyMap_ShouldReturn0() { + final ConcurrentHashMap> catKeysWithMissingParents = + new ConcurrentHashMap<>(); + categorySyncStatistics.setCategoryKeysWithMissingParents(catKeysWithMissingParents); + + assertThat(categorySyncStatistics.getNumberOfCategoriesWithMissingParents()).isZero(); + } + + @Test + void getNumberOfCategoriesWithMissingParents_WithEmptyValue_ShouldReturn0() { + final ConcurrentHashMap> categoryKeysWithMissingParents = + new ConcurrentHashMap<>(); + categoryKeysWithMissingParents.put("parent2", emptySet()); + + categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); + assertThat(categorySyncStatistics.getNumberOfCategoriesWithMissingParents()).isZero(); + } + + @Test + void + getNumberOfCategoriesWithMissingParents_WithNonEmptyMap_ShouldReturnCorrectNumberOfChildren() { + final ConcurrentHashMap> categoryKeysWithMissingParents = + new ConcurrentHashMap<>(); + final Set firstMissingParentChildrenKeys = new HashSet<>(); + firstMissingParentChildrenKeys.add("key1"); + firstMissingParentChildrenKeys.add("key2"); + + final Set secondMissingParentChildrenKeys = new HashSet<>(); + secondMissingParentChildrenKeys.add("key3"); + secondMissingParentChildrenKeys.add("key4"); + + categoryKeysWithMissingParents.put("parent1", firstMissingParentChildrenKeys); + categoryKeysWithMissingParents.put("parent2", secondMissingParentChildrenKeys); + + categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); + + assertThat(categorySyncStatistics.getNumberOfCategoriesWithMissingParents()).isEqualTo(4); + } + + @Test + void getCategoryKeysWithMissingParents_WithAnyMap_ShouldGetUnmodifiableMapCorrectly() { + final ConcurrentHashMap> catKeysWithMissingParents = + new ConcurrentHashMap<>(); + categorySyncStatistics.setCategoryKeysWithMissingParents(catKeysWithMissingParents); + + final Map> fetchedMap = + categorySyncStatistics.getCategoryKeysWithMissingParents(); + + assertThatThrownBy(() -> fetchedMap.put("e", emptySet())) + .isExactlyInstanceOf(UnsupportedOperationException.class); + + assertThat(fetchedMap).isEqualTo(catKeysWithMissingParents); + } + + @Test + void putMissingParentCategoryChildKey_OnAnEmptyList_ShouldAddNewParentEntryInTheMap() { + final String parentKey = "parentKey"; + final String childKey = "childKey"; + categorySyncStatistics.putMissingParentCategoryChildKey(parentKey, childKey); + + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isNotEmpty(); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); + final Set childrenKeySet = + categorySyncStatistics.getCategoryKeysWithMissingParents().get(parentKey); + assertThat(childrenKeySet).isNotNull(); + assertThat(childrenKeySet).hasSize(1); + assertThat(childrenKeySet.iterator().next()).isEqualTo(childKey); + } + + @Test + void + putMissingParentCategoryChildKey_OnANonEmptyListWithNewParent_ShouldAddNewParentEntryInTheMap() { + final ConcurrentHashMap> categoryKeysWithMissingParents = + new ConcurrentHashMap<>(); + final Set existingChildrenKeys = new HashSet<>(); + existingChildrenKeys.add("key1"); + existingChildrenKeys.add("key2"); + + final String existingParentKey = "existingParent"; + categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); + categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); + + final String newParentKey = "parentKey"; + final String newChildKey = "childKey"; + categorySyncStatistics.putMissingParentCategoryChildKey(newParentKey, newChildKey); + + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isNotEmpty(); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(2); + final Set newChildrenKeySet = + categorySyncStatistics.getCategoryKeysWithMissingParents().get(newParentKey); + assertThat(newChildrenKeySet).isNotNull(); + assertThat(newChildrenKeySet).hasSize(1); + assertThat(newChildrenKeySet.iterator().next()).isEqualTo(newChildKey); + + final Set existingChildrenKeySet = + categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey); + assertThat(existingChildrenKeySet).isNotNull(); + assertThat(existingChildrenKeySet).hasSize(2); + assertThat(existingChildrenKeySet).isEqualTo(existingChildrenKeys); + } + + @Test + void + putMissingParentCategoryChildKey_OnNonEmptyListWithExistingParent_ShouldAddParentToExistingEntry() { + final ConcurrentHashMap> categoryKeysWithMissingParents = + new ConcurrentHashMap<>(); + final Set existingChildrenKeys = new HashSet<>(); + existingChildrenKeys.add("key1"); + existingChildrenKeys.add("key2"); + + final String existingParentKey = "existingParent"; + categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); + categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); + + final String newChildKey = "childKey"; + categorySyncStatistics.putMissingParentCategoryChildKey(existingParentKey, newChildKey); + + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isNotEmpty(); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); + final Set newChildrenKeySet = + categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey); + assertThat(newChildrenKeySet).isNotNull(); + assertThat(newChildrenKeySet).hasSize(3); + assertThat(newChildrenKeySet).containsOnly(newChildKey, "key1", "key2"); + } + + @Test + void getMissingParentKey_OAnEmptyList_ShouldReturnAnEmptyOptional() { + assertThat(categorySyncStatistics.getMissingParentKey("foo")).isEmpty(); + } + + @Test + void getMissingParentKey_OnANonEmptyList_ShouldReturnCorrectParentKeyInOptional() { + final ConcurrentHashMap> categoryKeysWithMissingParents = + new ConcurrentHashMap<>(); + final Set existingChildrenKeys = new HashSet<>(); + existingChildrenKeys.add("key1"); + existingChildrenKeys.add("key2"); + + final String existingParentKey = "existingParent"; + categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); + categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); + + assertThat(categorySyncStatistics.getMissingParentKey("key1")).contains(existingParentKey); + assertThat(categorySyncStatistics.getMissingParentKey("key2")).contains(existingParentKey); + } + + @Test + void removeChildCategoryKeyFromMissingParentsMap_OnAEmptyList_ShouldNotAffectTheMap() { + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isEmpty(); + categorySyncStatistics.removeChildCategoryKeyFromMissingParentsMap("foo"); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).isEmpty(); + } + + @Test + void + removeChildCategoryKeyFromMissingParentsMap_OnANonEmptyListButNonExistingKey_ShouldNotAffectTheMap() { + final ConcurrentHashMap> categoryKeysWithMissingParents = + new ConcurrentHashMap<>(); + final Set existingChildrenKeys = new HashSet<>(); + existingChildrenKeys.add("key1"); + existingChildrenKeys.add("key2"); + + final String existingParentKey = "existingParent"; + categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); + categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); + + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) + .hasSize(2); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) + .containsOnly("key2", "key1"); + + categorySyncStatistics.removeChildCategoryKeyFromMissingParentsMap("foo"); + + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) + .hasSize(2); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) + .containsOnly("key2", "key1"); + } + + @Test + void + removeChildCategoryKeyFromMissingParentsMap_OnANonEmptyListWithExistingKey_ShouldNotAffectTheMap() { + final ConcurrentHashMap> categoryKeysWithMissingParents = + new ConcurrentHashMap<>(); + final Set existingChildrenKeys = new HashSet<>(); + existingChildrenKeys.add("key1"); + existingChildrenKeys.add("key2"); + + final String existingParentKey = "existingParent"; + categoryKeysWithMissingParents.put(existingParentKey, existingChildrenKeys); + categorySyncStatistics.setCategoryKeysWithMissingParents(categoryKeysWithMissingParents); + + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) + .hasSize(2); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) + .isEqualTo(existingChildrenKeys); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) + .containsOnly("key2", "key1"); + + categorySyncStatistics.removeChildCategoryKeyFromMissingParentsMap("key1"); + + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents()).hasSize(1); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) + .hasSize(1); + assertThat(categorySyncStatistics.getCategoryKeysWithMissingParents().get(existingParentKey)) + .doesNotContain("key1") + .containsOnly("key2"); + } } diff --git a/src/test/java/com/commercetools/sync/categories/utils/CategoryAssetUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/categories/utils/CategoryAssetUpdateActionUtilsTest.java index 5d04419085..9cc67496cf 100644 --- a/src/test/java/com/commercetools/sync/categories/utils/CategoryAssetUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/categories/utils/CategoryAssetUpdateActionUtilsTest.java @@ -1,5 +1,19 @@ package com.commercetools.sync.categories.utils; +import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildActions; +import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildChangeAssetNameUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildCustomUpdateActions; +import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildSetAssetDescriptionUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildSetAssetSourcesUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildSetAssetTagsUpdateAction; +import static io.sphere.sdk.models.LocalizedString.empty; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -23,8 +37,6 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -33,417 +45,418 @@ import java.util.Map; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildActions; -import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildChangeAssetNameUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildCustomUpdateActions; -import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildSetAssetDescriptionUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildSetAssetSourcesUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryAssetUpdateActionUtils.buildSetAssetTagsUpdateAction; -import static io.sphere.sdk.models.LocalizedString.empty; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CategoryAssetUpdateActionUtilsTest { - private static final CategorySyncOptions SYNC_OPTIONS = CategorySyncOptionsBuilder - .of(mock(SphereClient.class)).build(); - - Category mainCategory = mock(Category.class); - CategoryDraft mainCategoryDraft = mock(CategoryDraft.class); - - @Test - void buildActions_WithDifferentValues_ShouldBuildUpdateAction() { - final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); - final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); - - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - - final Set oldTags = new HashSet<>(); - oldTags.add("oldTag"); - final Set newTags = new HashSet<>(); - oldTags.add("newTag"); - - final List oldAssetSources = singletonList(AssetSourceBuilder.ofUri("oldUri").build()); - final List newAssetSources = singletonList(AssetSourceBuilder.ofUri("newUri").build()); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getName()).thenReturn(oldName); - when(oldAsset.getSources()).thenReturn(oldAssetSources); - when(oldAsset.getTags()).thenReturn(oldTags); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final AssetDraft newAssetDraft = AssetDraftBuilder.of(newAssetSources, newName) - .tags(newTags) - .custom(newCustomFieldsDraft) - .build(); - - - final List> updateActions = buildActions(mainCategory, mainCategoryDraft, - oldAsset, newAssetDraft, SYNC_OPTIONS); - - assertThat(updateActions).hasSize(5); - assertThat(updateActions).containsExactlyInAnyOrder( + private static final CategorySyncOptions SYNC_OPTIONS = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + Category mainCategory = mock(Category.class); + CategoryDraft mainCategoryDraft = mock(CategoryDraft.class); + + @Test + void buildActions_WithDifferentValues_ShouldBuildUpdateAction() { + final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); + final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); + + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + + final Set oldTags = new HashSet<>(); + oldTags.add("oldTag"); + final Set newTags = new HashSet<>(); + oldTags.add("newTag"); + + final List oldAssetSources = + singletonList(AssetSourceBuilder.ofUri("oldUri").build()); + final List newAssetSources = + singletonList(AssetSourceBuilder.ofUri("newUri").build()); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getName()).thenReturn(oldName); + when(oldAsset.getSources()).thenReturn(oldAssetSources); + when(oldAsset.getTags()).thenReturn(oldTags); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(newAssetSources, newName) + .tags(newTags) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildActions(mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, SYNC_OPTIONS); + + assertThat(updateActions).hasSize(5); + assertThat(updateActions) + .containsExactlyInAnyOrder( ChangeAssetName.ofKey(null, newName), SetAssetTags.ofKey(null, newTags), SetAssetSources.ofKey(null, newAssetSources), - SetAssetCustomField - .ofJsonValueWithKey(null, "invisibleInShop", newCustomFieldsMap.get("invisibleInShop")), - SetAssetCustomField - .ofJsonValueWithKey(null, "backgroundColor", newCustomFieldsMap.get("backgroundColor")) - ); - } - - @Test - void buildActions_WithIdenticalValues_ShouldBuildUpdateAction() { - final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); + SetAssetCustomField.ofJsonValueWithKey( + null, "invisibleInShop", newCustomFieldsMap.get("invisibleInShop")), + SetAssetCustomField.ofJsonValueWithKey( + null, "backgroundColor", newCustomFieldsMap.get("backgroundColor"))); + } - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + @Test + void buildActions_WithIdenticalValues_ShouldBuildUpdateAction() { + final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - final Set oldTags = new HashSet<>(); - oldTags.add("oldTag"); + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - final List oldAssetSources = singletonList(AssetSourceBuilder.ofUri("oldUri").build()); + final Set oldTags = new HashSet<>(); + oldTags.add("oldTag"); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getName()).thenReturn(oldName); - when(oldAsset.getSources()).thenReturn(oldAssetSources); - when(oldAsset.getTags()).thenReturn(oldTags); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); + final List oldAssetSources = + singletonList(AssetSourceBuilder.ofUri("oldUri").build()); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(oldAsset).build(); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getName()).thenReturn(oldName); + when(oldAsset.getSources()).thenReturn(oldAssetSources); + when(oldAsset.getTags()).thenReturn(oldTags); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + final AssetDraft newAssetDraft = AssetDraftBuilder.of(oldAsset).build(); - final List> updateActions = buildActions(mainCategory, mainCategoryDraft, - oldAsset, newAssetDraft, SYNC_OPTIONS); + final List> updateActions = + buildActions(mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildChangeAssetNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); - final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); + @Test + void buildChangeAssetNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); + final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getName()).thenReturn(oldName); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getName()).thenReturn(oldName); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), newName).build(); + final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), newName).build(); - final UpdateAction changeNameUpdateAction = buildChangeAssetNameUpdateAction(oldAsset, newAssetDraft) - .orElse(null); + final UpdateAction changeNameUpdateAction = + buildChangeAssetNameUpdateAction(oldAsset, newAssetDraft).orElse(null); - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction).isInstanceOf(ChangeAssetName.class); - assertThat(((ChangeAssetName) changeNameUpdateAction).getName()).isEqualTo(newName); - } + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction).isInstanceOf(ChangeAssetName.class); + assertThat(((ChangeAssetName) changeNameUpdateAction).getName()).isEqualTo(newName); + } - @Test - void buildChangeAssetNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); + @Test + void buildChangeAssetNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getName()).thenReturn(oldName); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getName()).thenReturn(oldName); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), oldName).build(); + final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), oldName).build(); - final Optional> changeNameUpdateAction = - buildChangeAssetNameUpdateAction(oldAsset, newAssetDraft); + final Optional> changeNameUpdateAction = + buildChangeAssetNameUpdateAction(oldAsset, newAssetDraft); - assertThat(changeNameUpdateAction).isEmpty(); - } + assertThat(changeNameUpdateAction).isEmpty(); + } - @Test - void buildSetAssetDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final LocalizedString oldDesc = LocalizedString.of(Locale.GERMAN, "oldDesc"); - final LocalizedString newDesc = LocalizedString.of(Locale.GERMAN, "newDesc"); + @Test + void buildSetAssetDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final LocalizedString oldDesc = LocalizedString.of(Locale.GERMAN, "oldDesc"); + final LocalizedString newDesc = LocalizedString.of(Locale.GERMAN, "newDesc"); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getDescription()).thenReturn(oldDesc); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getDescription()).thenReturn(oldDesc); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .description(newDesc).build(); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).description(newDesc).build(); - final UpdateAction setAssetDescription = - buildSetAssetDescriptionUpdateAction(oldAsset, newAssetDraft).orElse(null); + final UpdateAction setAssetDescription = + buildSetAssetDescriptionUpdateAction(oldAsset, newAssetDraft).orElse(null); - assertThat(setAssetDescription).isNotNull(); - assertThat(setAssetDescription).isInstanceOf(SetAssetDescription.class); - assertThat(((SetAssetDescription) setAssetDescription).getDescription()).isEqualTo(newDesc); - } + assertThat(setAssetDescription).isNotNull(); + assertThat(setAssetDescription).isInstanceOf(SetAssetDescription.class); + assertThat(((SetAssetDescription) setAssetDescription).getDescription()).isEqualTo(newDesc); + } - @Test - void buildSetAssetDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final LocalizedString oldDesc = LocalizedString.of(Locale.GERMAN, "oldDesc"); + @Test + void buildSetAssetDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final LocalizedString oldDesc = LocalizedString.of(Locale.GERMAN, "oldDesc"); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getDescription()).thenReturn(oldDesc); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getDescription()).thenReturn(oldDesc); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .description(oldDesc).build(); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).description(oldDesc).build(); - final Optional> setAssetDescription = - buildSetAssetDescriptionUpdateAction(oldAsset, newAssetDraft); + final Optional> setAssetDescription = + buildSetAssetDescriptionUpdateAction(oldAsset, newAssetDraft); - assertThat(setAssetDescription).isEmpty(); - } + assertThat(setAssetDescription).isEmpty(); + } - @Test - void buildSetAssetDescriptionUpdateAction_WithNullOldValue_ShouldBuildUpdateAction() { - final LocalizedString newDesc = LocalizedString.of(Locale.GERMAN, "newDesc"); + @Test + void buildSetAssetDescriptionUpdateAction_WithNullOldValue_ShouldBuildUpdateAction() { + final LocalizedString newDesc = LocalizedString.of(Locale.GERMAN, "newDesc"); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getDescription()).thenReturn(null); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getDescription()).thenReturn(null); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .description(newDesc).build(); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).description(newDesc).build(); - final UpdateAction setAssetDescription = - buildSetAssetDescriptionUpdateAction(oldAsset, newAssetDraft).orElse(null); + final UpdateAction setAssetDescription = + buildSetAssetDescriptionUpdateAction(oldAsset, newAssetDraft).orElse(null); - assertThat(setAssetDescription).isNotNull(); - assertThat(setAssetDescription).isInstanceOf(SetAssetDescription.class); - assertThat(((SetAssetDescription) setAssetDescription).getDescription()).isEqualTo(newDesc); - } + assertThat(setAssetDescription).isNotNull(); + assertThat(setAssetDescription).isInstanceOf(SetAssetDescription.class); + assertThat(((SetAssetDescription) setAssetDescription).getDescription()).isEqualTo(newDesc); + } - @Test - void buildSetAssetTagsUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final Set oldTags = new HashSet<>(); - oldTags.add("oldTag"); - final Set newTags = new HashSet<>(); - newTags.add("newTag"); + @Test + void buildSetAssetTagsUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final Set oldTags = new HashSet<>(); + oldTags.add("oldTag"); + final Set newTags = new HashSet<>(); + newTags.add("newTag"); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getTags()).thenReturn(oldTags); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getTags()).thenReturn(oldTags); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .tags(newTags).build(); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).tags(newTags).build(); - final UpdateAction productUpdateAction = - buildSetAssetTagsUpdateAction(oldAsset, newAssetDraft).orElse(null); + final UpdateAction productUpdateAction = + buildSetAssetTagsUpdateAction(oldAsset, newAssetDraft).orElse(null); - assertThat(productUpdateAction).isNotNull(); - assertThat(productUpdateAction).isInstanceOf(SetAssetTags.class); - assertThat(((SetAssetTags) productUpdateAction).getTags()).isEqualTo(newTags); - } + assertThat(productUpdateAction).isNotNull(); + assertThat(productUpdateAction).isInstanceOf(SetAssetTags.class); + assertThat(((SetAssetTags) productUpdateAction).getTags()).isEqualTo(newTags); + } - @Test - void buildSetAssetTagsUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final Set oldTags = new HashSet<>(); - oldTags.add("oldTag"); + @Test + void buildSetAssetTagsUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final Set oldTags = new HashSet<>(); + oldTags.add("oldTag"); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getTags()).thenReturn(oldTags); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getTags()).thenReturn(oldTags); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .tags(oldTags).build(); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).tags(oldTags).build(); + final Optional> productUpdateAction = + buildSetAssetTagsUpdateAction(oldAsset, newAssetDraft); - final Optional> productUpdateAction = - buildSetAssetTagsUpdateAction(oldAsset, newAssetDraft); + assertThat(productUpdateAction).isEmpty(); + } - assertThat(productUpdateAction).isEmpty(); - } + @Test + void buildSetAssetTagsUpdateAction_WithNullOldValues_ShouldBuildUpdateAction() { + final Set newTags = new HashSet<>(); + newTags.add("newTag"); - @Test - void buildSetAssetTagsUpdateAction_WithNullOldValues_ShouldBuildUpdateAction() { - final Set newTags = new HashSet<>(); - newTags.add("newTag"); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getTags()).thenReturn(null); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getTags()).thenReturn(null); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).tags(newTags).build(); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .tags(newTags).build(); + final UpdateAction productUpdateAction = + buildSetAssetTagsUpdateAction(oldAsset, newAssetDraft).orElse(null); - final UpdateAction productUpdateAction = - buildSetAssetTagsUpdateAction(oldAsset, newAssetDraft).orElse(null); + assertThat(productUpdateAction).isNotNull(); + assertThat(productUpdateAction).isInstanceOf(SetAssetTags.class); + assertThat(((SetAssetTags) productUpdateAction).getTags()).isEqualTo(newTags); + } - assertThat(productUpdateAction).isNotNull(); - assertThat(productUpdateAction).isInstanceOf(SetAssetTags.class); - assertThat(((SetAssetTags) productUpdateAction).getTags()).isEqualTo(newTags); - } + @Test + void buildSetAssetSourcesUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final List oldAssetSources = + singletonList(AssetSourceBuilder.ofUri("oldUri").build()); + final List newAssetSources = + singletonList(AssetSourceBuilder.ofUri("newUri").build()); - @Test - void buildSetAssetSourcesUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final List oldAssetSources = singletonList(AssetSourceBuilder.ofUri("oldUri").build()); - final List newAssetSources = singletonList(AssetSourceBuilder.ofUri("newUri").build()); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getSources()).thenReturn(oldAssetSources); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getSources()).thenReturn(oldAssetSources); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).sources(newAssetSources).build(); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .sources(newAssetSources).build(); + final UpdateAction productUpdateAction = + buildSetAssetSourcesUpdateAction(oldAsset, newAssetDraft).orElse(null); - final UpdateAction productUpdateAction = - buildSetAssetSourcesUpdateAction(oldAsset, newAssetDraft).orElse(null); + assertThat(productUpdateAction).isNotNull(); + assertThat(productUpdateAction).isInstanceOf(SetAssetSources.class); + assertThat(((SetAssetSources) productUpdateAction).getSources()).isEqualTo(newAssetSources); + } - assertThat(productUpdateAction).isNotNull(); - assertThat(productUpdateAction).isInstanceOf(SetAssetSources.class); - assertThat(((SetAssetSources) productUpdateAction).getSources()).isEqualTo(newAssetSources); - } + @Test + void buildSetAssetSourcesUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final List oldAssetSources = + singletonList(AssetSourceBuilder.ofUri("oldUri").build()); - @Test - void buildSetAssetSourcesUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final List oldAssetSources = singletonList(AssetSourceBuilder.ofUri("oldUri").build()); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getSources()).thenReturn(oldAssetSources); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getSources()).thenReturn(oldAssetSources); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).sources(oldAssetSources).build(); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .sources(oldAssetSources).build(); + final Optional> productUpdateAction = + buildSetAssetSourcesUpdateAction(oldAsset, newAssetDraft); - final Optional> productUpdateAction = - buildSetAssetSourcesUpdateAction(oldAsset, newAssetDraft); + assertThat(productUpdateAction).isEmpty(); + } - assertThat(productUpdateAction).isEmpty(); - } + @Test + void buildCustomUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - @Test - void buildCustomUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); + final List> updateActions = + buildCustomUpdateActions( + mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, SYNC_OPTIONS); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); + assertThat(updateActions).isEmpty(); + } - final List> updateActions = - buildCustomUpdateActions(mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, SYNC_OPTIONS); + @Test + void buildCustomUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - assertThat(updateActions).isEmpty(); - } + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - @Test - void buildCustomUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + final List> updateActions = + buildCustomUpdateActions( + mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, SYNC_OPTIONS); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); + assertThat(updateActions).hasSize(2); + } - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); + @Test + void buildCustomUpdateActions_WithNullOldValues_ShouldBuildUpdateAction() { - final List> updateActions = - buildCustomUpdateActions(mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, SYNC_OPTIONS); + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - assertThat(updateActions).hasSize(2); - } + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - @Test - void buildCustomUpdateActions_WithNullOldValues_ShouldBuildUpdateAction() { + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(null); - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + final List> updateActions = + buildCustomUpdateActions( + mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, SYNC_OPTIONS); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(null); + assertThat(updateActions) + .containsExactly(SetAssetCustomType.ofKey(newAssetDraft.getKey(), newCustomFieldsDraft)); + } - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); + @Test + void + buildCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerErrorCallback() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - final List> updateActions = - buildCustomUpdateActions(mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, SYNC_OPTIONS); + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - assertThat(updateActions).containsExactly( - SetAssetCustomType.ofKey(newAssetDraft.getKey(), newCustomFieldsDraft) - ); - } + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - @Test - void buildCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerErrorCallback() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final List errors = new ArrayList<>(); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final CategorySyncOptions syncOptions = - CategorySyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> + final CategorySyncOptions syncOptions = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> errors.add(exception.getMessage())) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, syncOptions); - - assertThat(updateActions).isEmpty(); - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo(format("Failed to build custom fields update actions on the asset with id '%s'." - + " Reason: Custom type ids are not set for both the old and new asset.", oldAsset.getId())); - } + .build(); + + final List> updateActions = + buildCustomUpdateActions( + mainCategory, mainCategoryDraft, oldAsset, newAssetDraft, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + format( + "Failed to build custom fields update actions on the asset with id '%s'." + + " Reason: Custom type ids are not set for both the old and new asset.", + oldAsset.getId())); + } } diff --git a/src/test/java/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtilsTest.java index 352e052a43..a5a41cb7df 100644 --- a/src/test/java/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtilsTest.java @@ -1,5 +1,13 @@ package com.commercetools.sync.categories.utils; +import static com.commercetools.sync.commons.MockUtils.getAssetMockWithCustomFields; +import static com.commercetools.sync.commons.MockUtils.getTypeMock; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.CategoryDraft; import io.sphere.sdk.categories.queries.CategoryQuery; @@ -9,152 +17,147 @@ import io.sphere.sdk.models.Reference; import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.UUID; - -import static com.commercetools.sync.commons.MockUtils.getAssetMockWithCustomFields; -import static com.commercetools.sync.commons.MockUtils.getTypeMock; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CategoryReferenceResolutionUtilsTest { - @Test - void mapToCategoryDrafts_WithAllExpandedCategoryReferences_ShouldReturnReferencesWithKeys() { - final String parentId = UUID.randomUUID().toString(); - final Type mockCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - - // Mock asset with expanded custom type reference - final Type assetCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - final Asset asset = getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), - assetCustomType)); - - final List mockCategories = new ArrayList<>(); - for (int i = 0; i < 2; i++) { - final Category mockCategory = mock(Category.class); - - //Mock categories parent fields with expanded category references. - final Category mockParent = mock(Category.class); - when(mockParent.getId()).thenReturn(parentId); - when(mockParent.getKey()).thenReturn("parentKey" + i); - final Reference parentReference = Reference.ofResourceTypeIdAndObj(UUID.randomUUID().toString(), - mockParent); - when(mockCategory.getParent()).thenReturn(parentReference); - - //Mock categories custom fields with expanded type references. - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = Reference.ofResourceTypeIdAndObj("resourceTypeId", - mockCustomType); - when(mockCustomFields.getType()).thenReturn(typeReference); - when(mockCategory.getCustom()).thenReturn(mockCustomFields); - - //Mock categories assets with expanded custom type references. - when(mockCategory.getAssets()).thenReturn(singletonList(asset)); - - mockCategories.add(mockCategory); - } - - for (final Category category : mockCategories) { - assertThat(category.getParent().getId()).isEqualTo(parentId); - assertThat(category.getCustom().getType().getId()).isEqualTo(mockCustomType.getId()); - } - final List referenceReplacedDrafts = - CategoryReferenceResolutionUtils.mapToCategoryDrafts(mockCategories); - - for (int i = 0; i < referenceReplacedDrafts.size(); i++) { - assertThat(referenceReplacedDrafts.get(i).getParent().getKey()).isEqualTo("parentKey" + i); - assertThat(referenceReplacedDrafts.get(i).getCustom().getType().getKey()) - .isEqualTo(mockCustomType.getKey()); - - final List referenceReplacedDraftAssets = referenceReplacedDrafts.get(i).getAssets(); - assertThat(referenceReplacedDraftAssets).hasSize(1); - assertThat(referenceReplacedDraftAssets.get(0).getCustom()).isNotNull(); - assertThat(referenceReplacedDraftAssets.get(0).getCustom().getType().getKey()) - .isEqualTo(assetCustomType.getKey()); - } + @Test + void mapToCategoryDrafts_WithAllExpandedCategoryReferences_ShouldReturnReferencesWithKeys() { + final String parentId = UUID.randomUUID().toString(); + final Type mockCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + + // Mock asset with expanded custom type reference + final Type assetCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + final Asset asset = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), assetCustomType)); + + final List mockCategories = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + final Category mockCategory = mock(Category.class); + + // Mock categories parent fields with expanded category references. + final Category mockParent = mock(Category.class); + when(mockParent.getId()).thenReturn(parentId); + when(mockParent.getKey()).thenReturn("parentKey" + i); + final Reference parentReference = + Reference.ofResourceTypeIdAndObj(UUID.randomUUID().toString(), mockParent); + when(mockCategory.getParent()).thenReturn(parentReference); + + // Mock categories custom fields with expanded type references. + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = + Reference.ofResourceTypeIdAndObj("resourceTypeId", mockCustomType); + when(mockCustomFields.getType()).thenReturn(typeReference); + when(mockCategory.getCustom()).thenReturn(mockCustomFields); + + // Mock categories assets with expanded custom type references. + when(mockCategory.getAssets()).thenReturn(singletonList(asset)); + + mockCategories.add(mockCategory); } - @Test - void mapToCategoryDrafts_WithNonExpandedReferences_ShouldReturnReferencesWithoutKeys() { - final String parentId = UUID.randomUUID().toString(); - final String customTypeId = UUID.randomUUID().toString(); - - // Mock asset with non-expanded custom type reference - final Asset asset = getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), - UUID.randomUUID().toString())); - - final List mockCategories = new ArrayList<>(); - for (int i = 0; i < 2; i++) { - final Category mockCategory = mock(Category.class); - - //Mock categories parent fields with non-expanded category references. - final Reference parentReference = Reference.ofResourceTypeIdAndId(UUID.randomUUID().toString(), - parentId); - when(mockCategory.getParent()).thenReturn(parentReference); - - //Mock categories custom fields with non-expanded type references. - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = Reference.ofResourceTypeIdAndId("resourceTypeId", - customTypeId); - when(mockCustomFields.getType()).thenReturn(typeReference); - when(mockCategory.getCustom()).thenReturn(mockCustomFields); - - //Mock categories assets with non-expanded custom type references. - when(mockCategory.getAssets()).thenReturn(singletonList(asset)); - - mockCategories.add(mockCategory); - } - - for (final Category category : mockCategories) { - assertThat(category.getParent().getId()).isEqualTo(parentId); - assertThat(category.getCustom().getType().getId()).isEqualTo(customTypeId); - } - final List referenceReplacedDrafts = - CategoryReferenceResolutionUtils.mapToCategoryDrafts(mockCategories); - - for (CategoryDraft referenceReplacedDraft : referenceReplacedDrafts) { - assertThat(referenceReplacedDraft.getParent().getId()).isEqualTo(parentId); - assertThat(referenceReplacedDraft.getCustom().getType().getId()).isEqualTo(customTypeId); - - final List referenceReplacedDraftAssets = referenceReplacedDraft.getAssets(); - assertThat(referenceReplacedDraftAssets).hasSize(1); - assertThat(referenceReplacedDraftAssets.get(0).getCustom()).isNotNull(); - assertThat(referenceReplacedDraftAssets.get(0).getCustom().getType().getId()) - .isEqualTo(asset.getCustom().getType().getId()); - } + for (final Category category : mockCategories) { + assertThat(category.getParent().getId()).isEqualTo(parentId); + assertThat(category.getCustom().getType().getId()).isEqualTo(mockCustomType.getId()); + } + final List referenceReplacedDrafts = + CategoryReferenceResolutionUtils.mapToCategoryDrafts(mockCategories); + + for (int i = 0; i < referenceReplacedDrafts.size(); i++) { + assertThat(referenceReplacedDrafts.get(i).getParent().getKey()).isEqualTo("parentKey" + i); + assertThat(referenceReplacedDrafts.get(i).getCustom().getType().getKey()) + .isEqualTo(mockCustomType.getKey()); + + final List referenceReplacedDraftAssets = + referenceReplacedDrafts.get(i).getAssets(); + assertThat(referenceReplacedDraftAssets).hasSize(1); + assertThat(referenceReplacedDraftAssets.get(0).getCustom()).isNotNull(); + assertThat(referenceReplacedDraftAssets.get(0).getCustom().getType().getKey()) + .isEqualTo(assetCustomType.getKey()); + } + } + + @Test + void mapToCategoryDrafts_WithNonExpandedReferences_ShouldReturnReferencesWithoutKeys() { + final String parentId = UUID.randomUUID().toString(); + final String customTypeId = UUID.randomUUID().toString(); + + // Mock asset with non-expanded custom type reference + final Asset asset = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), UUID.randomUUID().toString())); + + final List mockCategories = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + final Category mockCategory = mock(Category.class); + + // Mock categories parent fields with non-expanded category references. + final Reference parentReference = + Reference.ofResourceTypeIdAndId(UUID.randomUUID().toString(), parentId); + when(mockCategory.getParent()).thenReturn(parentReference); + + // Mock categories custom fields with non-expanded type references. + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = + Reference.ofResourceTypeIdAndId("resourceTypeId", customTypeId); + when(mockCustomFields.getType()).thenReturn(typeReference); + when(mockCategory.getCustom()).thenReturn(mockCustomFields); + + // Mock categories assets with non-expanded custom type references. + when(mockCategory.getAssets()).thenReturn(singletonList(asset)); + + mockCategories.add(mockCategory); } - @Test - void mapToCategoryDrafts_WithoutReferences_ShouldNotReturnReferences() { - final List mockCategories = new ArrayList<>(); - for (int i = 0; i < 2; i++) { - final Category mockCategory = mock(Category.class); - when(mockCategory.getParent()).thenReturn(null); - when(mockCategory.getCustom()).thenReturn(null); - when(mockCategory.getAssets()).thenReturn(emptyList()); - mockCategories.add(mockCategory); - } - final List referenceReplacedDrafts = - CategoryReferenceResolutionUtils.mapToCategoryDrafts(mockCategories); - for (CategoryDraft referenceReplacedDraft : referenceReplacedDrafts) { - assertThat(referenceReplacedDraft.getParent()).isNull(); - assertThat(referenceReplacedDraft.getCustom()).isNull(); - assertThat(referenceReplacedDraft.getAssets()).isEmpty(); - } + for (final Category category : mockCategories) { + assertThat(category.getParent().getId()).isEqualTo(parentId); + assertThat(category.getCustom().getType().getId()).isEqualTo(customTypeId); + } + final List referenceReplacedDrafts = + CategoryReferenceResolutionUtils.mapToCategoryDrafts(mockCategories); + + for (CategoryDraft referenceReplacedDraft : referenceReplacedDrafts) { + assertThat(referenceReplacedDraft.getParent().getId()).isEqualTo(parentId); + assertThat(referenceReplacedDraft.getCustom().getType().getId()).isEqualTo(customTypeId); + + final List referenceReplacedDraftAssets = referenceReplacedDraft.getAssets(); + assertThat(referenceReplacedDraftAssets).hasSize(1); + assertThat(referenceReplacedDraftAssets.get(0).getCustom()).isNotNull(); + assertThat(referenceReplacedDraftAssets.get(0).getCustom().getType().getId()) + .isEqualTo(asset.getCustom().getType().getId()); } + } + + @Test + void mapToCategoryDrafts_WithoutReferences_ShouldNotReturnReferences() { + final List mockCategories = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + final Category mockCategory = mock(Category.class); + when(mockCategory.getParent()).thenReturn(null); + when(mockCategory.getCustom()).thenReturn(null); + when(mockCategory.getAssets()).thenReturn(emptyList()); + mockCategories.add(mockCategory); + } + final List referenceReplacedDrafts = + CategoryReferenceResolutionUtils.mapToCategoryDrafts(mockCategories); + for (CategoryDraft referenceReplacedDraft : referenceReplacedDrafts) { + assertThat(referenceReplacedDraft.getParent()).isNull(); + assertThat(referenceReplacedDraft.getCustom()).isNull(); + assertThat(referenceReplacedDraft.getAssets()).isEmpty(); + } + } - @Test - void buildCategoryQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final CategoryQuery categoryQuery = CategoryReferenceResolutionUtils.buildCategoryQuery(); - assertThat(categoryQuery.expansionPaths()).containsExactly( + @Test + void buildCategoryQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final CategoryQuery categoryQuery = CategoryReferenceResolutionUtils.buildCategoryQuery(); + assertThat(categoryQuery.expansionPaths()) + .containsExactly( ExpansionPath.of("custom.type"), ExpansionPath.of("assets[*].custom.type"), ExpansionPath.of("parent")); - } + } } diff --git a/src/test/java/com/commercetools/sync/categories/utils/CategorySyncUtilsTest.java b/src/test/java/com/commercetools/sync/categories/utils/CategorySyncUtilsTest.java index 2390052433..b86ec44790 100644 --- a/src/test/java/com/commercetools/sync/categories/utils/CategorySyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/categories/utils/CategorySyncUtilsTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.categories.utils; +import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategory; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; @@ -23,49 +29,45 @@ import io.sphere.sdk.models.AssetSourceBuilder; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.ResourceIdentifier; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Locale; - -import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategory; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CategorySyncUtilsTest { - private Category mockOldCategory; - private CategorySyncOptions categorySyncOptions; - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private static final Locale LOCALE = Locale.GERMAN; - private static final String CATEGORY_PARENT_ID = "1"; - private static final String CATEGORY_NAME = "categoryName"; - private static final String CATEGORY_SLUG = "categorySlug"; - private static final String CATEGORY_KEY = "categoryKey"; - private static final String CATEGORY_EXTERNAL_ID = "externalId"; - private static final String CATEGORY_DESC = "categoryDesc"; - private static final String CATEGORY_META_DESC = "categoryMetaDesc"; - private static final String CATEGORY_META_TITLE = "categoryMetaTitle"; - private static final String CATEGORY_KEYWORDS = "categoryKeywords"; - private static final String CATEGORY_ORDER_HINT = "123"; - private static final List ASSET_DRAFTS = asList( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("name")).key("1") - .build(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("name")).key("2") - .build()); + private Category mockOldCategory; + private CategorySyncOptions categorySyncOptions; + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private static final Locale LOCALE = Locale.GERMAN; + private static final String CATEGORY_PARENT_ID = "1"; + private static final String CATEGORY_NAME = "categoryName"; + private static final String CATEGORY_SLUG = "categorySlug"; + private static final String CATEGORY_KEY = "categoryKey"; + private static final String CATEGORY_EXTERNAL_ID = "externalId"; + private static final String CATEGORY_DESC = "categoryDesc"; + private static final String CATEGORY_META_DESC = "categoryMetaDesc"; + private static final String CATEGORY_META_TITLE = "categoryMetaTitle"; + private static final String CATEGORY_KEYWORDS = "categoryKeywords"; + private static final String CATEGORY_ORDER_HINT = "123"; + private static final List ASSET_DRAFTS = + asList( + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("name")) + .key("1") + .build(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("name")) + .key("2") + .build()); - /** - * Initializes an instance of {@link CategorySyncOptions} and {@link Category}. - */ - @BeforeEach - void setup() { - categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .build(); + /** Initializes an instance of {@link CategorySyncOptions} and {@link Category}. */ + @BeforeEach + void setup() { + categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT).build(); - mockOldCategory = getMockCategory(LOCALE, + mockOldCategory = + getMockCategory( + LOCALE, CATEGORY_NAME, CATEGORY_SLUG, CATEGORY_KEY, @@ -76,12 +78,14 @@ void setup() { CATEGORY_KEYWORDS, CATEGORY_ORDER_HINT, CATEGORY_PARENT_ID); - } + } - @Test - void buildActions_FromDraftsWithDifferentNameValues_ShouldBuildUpdateActions() { - final CategoryDraft newCategoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(LOCALE, "differentName"), LocalizedString.of(LOCALE, CATEGORY_SLUG)) + @Test + void buildActions_FromDraftsWithDifferentNameValues_ShouldBuildUpdateActions() { + final CategoryDraft newCategoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(LOCALE, "differentName"), + LocalizedString.of(LOCALE, CATEGORY_SLUG)) .key(CATEGORY_KEY) .externalId(CATEGORY_EXTERNAL_ID) .description(LocalizedString.of(LOCALE, CATEGORY_DESC)) @@ -92,19 +96,20 @@ void buildActions_FromDraftsWithDifferentNameValues_ShouldBuildUpdateActions() { .parent(ResourceIdentifier.ofId(CATEGORY_PARENT_ID)) .build(); - final List> updateActions = - CategorySyncUtils.buildActions(mockOldCategory, newCategoryDraft, categorySyncOptions); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).containsExactly( - ChangeName.of(LocalizedString.of(LOCALE, "differentName"))); + final List> updateActions = + CategorySyncUtils.buildActions(mockOldCategory, newCategoryDraft, categorySyncOptions); + assertThat(updateActions).isNotNull(); + assertThat(updateActions) + .containsExactly(ChangeName.of(LocalizedString.of(LOCALE, "differentName"))); + } - } + @Test + void buildActions_FromDraftsWithMultipleDifferentValues_ShouldBuildUpdateActions() { - @Test - void buildActions_FromDraftsWithMultipleDifferentValues_ShouldBuildUpdateActions() { - - final CategoryDraft newCategoryDraft = CategoryDraftBuilder - .of(LocalizedString.of(LOCALE, "differentName"), LocalizedString.of(LOCALE, "differentSlug")) + final CategoryDraft newCategoryDraft = + CategoryDraftBuilder.of( + LocalizedString.of(LOCALE, "differentName"), + LocalizedString.of(LOCALE, "differentSlug")) .key(CATEGORY_KEY) .externalId("differentExternalId") .description(LocalizedString.of(LOCALE, "differentDescription")) @@ -116,12 +121,12 @@ void buildActions_FromDraftsWithMultipleDifferentValues_ShouldBuildUpdateActions .assets(ASSET_DRAFTS) .build(); + final List> updateActions = + CategorySyncUtils.buildActions(mockOldCategory, newCategoryDraft, categorySyncOptions); + assertThat(updateActions).isNotNull(); - final List> updateActions = - CategorySyncUtils.buildActions(mockOldCategory, newCategoryDraft, categorySyncOptions); - assertThat(updateActions).isNotNull(); - - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( ChangeName.of(LocalizedString.of(LOCALE, "differentName")), ChangeSlug.of(LocalizedString.of(LOCALE, "differentSlug")), SetExternalId.of("differentExternalId"), @@ -133,5 +138,5 @@ void buildActions_FromDraftsWithMultipleDifferentValues_ShouldBuildUpdateActions SetMetaKeywords.of(LocalizedString.of(LOCALE, "differentMetaKeywords")), AddAsset.of(ASSET_DRAFTS.get(0), 0), AddAsset.of(ASSET_DRAFTS.get(1), 1)); - } + } } diff --git a/src/test/java/com/commercetools/sync/categories/utils/CategoryUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/categories/utils/CategoryUpdateActionUtilsTest.java index bc57c28d50..170e772c5a 100644 --- a/src/test/java/com/commercetools/sync/categories/utils/CategoryUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/categories/utils/CategoryUpdateActionUtilsTest.java @@ -1,5 +1,20 @@ package com.commercetools.sync.categories.utils; +import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategory; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeNameUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeOrderHintUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeParentUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeSlugUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetDescriptionUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetExternalIdUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetMetaDescriptionUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetMetaKeywordsUpdateAction; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetMetaTitleUpdateAction; +import static com.commercetools.sync.products.ProductSyncMockUtils.CATEGORY_KEY_1_RESOURCE_PATH; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; @@ -20,490 +35,484 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.ResourceIdentifier; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategory; -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeNameUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeOrderHintUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeParentUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildChangeSlugUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetDescriptionUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetExternalIdUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetMetaDescriptionUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetMetaKeywordsUpdateAction; -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildSetMetaTitleUpdateAction; -import static com.commercetools.sync.products.ProductSyncMockUtils.CATEGORY_KEY_1_RESOURCE_PATH; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CategoryUpdateActionUtilsTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private static final CategorySyncOptions CATEGORY_SYNC_OPTIONS = CategorySyncOptionsBuilder.of(CTP_CLIENT).build(); - private static final Locale LOCALE = Locale.GERMAN; - private static final String MOCK_OLD_CATEGORY_PARENT_ID = "1"; - private static final String MOCK_OLD_CATEGORY_NAME = "categoryName"; - private static final String MOCK_OLD_CATEGORY_SLUG = "categorySlug"; - private static final String MOCK_OLD_CATEGORY_KEY = "categoryKey"; - private static final String MOCK_OLD_CATEGORY_EXTERNAL_ID = "externalId"; - private static final String MOCK_OLD_CATEGORY_DESCRIPTION = "categoryDesc"; - private static final String MOCK_OLD_CATEGORY_META_DESCRIPTION = "categoryMetaDesc"; - private static final String MOCK_OLD_CATEGORY_META_TITLE = "categoryMetaTitle"; - private static final String MOCK_OLD_CATEGORY_META_KEYWORDS = "categoryKeywords"; - private static final String MOCK_OLD_CATEGORY_ORDERHINT = "123"; - private static final Category MOCK_OLD_CATEGORY = getMockCategory(LOCALE, - MOCK_OLD_CATEGORY_NAME, - MOCK_OLD_CATEGORY_SLUG, - MOCK_OLD_CATEGORY_KEY, - MOCK_OLD_CATEGORY_EXTERNAL_ID, - MOCK_OLD_CATEGORY_DESCRIPTION, - MOCK_OLD_CATEGORY_META_DESCRIPTION, - MOCK_OLD_CATEGORY_META_TITLE, - MOCK_OLD_CATEGORY_META_KEYWORDS, - MOCK_OLD_CATEGORY_ORDERHINT, - MOCK_OLD_CATEGORY_PARENT_ID); - - @Test - void buildChangeNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getName()).thenReturn(LocalizedString.of(LOCALE, "newName")); - - final UpdateAction changeNameUpdateAction = - buildChangeNameUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); - assertThat(((ChangeName) changeNameUpdateAction).getName()).isEqualTo(LocalizedString.of(LOCALE, "newName")); - } - - @Test - void buildChangeNameUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getName()).thenReturn(null); - - final UpdateAction changeNameUpdateAction = - buildChangeNameUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); - assertThat(((ChangeName) changeNameUpdateAction).getName()).isNull(); - } - - @Test - void buildChangeNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CategoryDraft newCategoryDraftWithSameName = mock(CategoryDraft.class); - when(newCategoryDraftWithSameName.getName()) - .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> changeNameUpdateAction = - buildChangeNameUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameName); - - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction).isNotPresent(); - } - - @Test - void buildChangeSlugUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getSlug()).thenReturn(LocalizedString.of(LOCALE, "newSlug")); - - final UpdateAction changeSlugUpdateAction = - buildChangeSlugUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(changeSlugUpdateAction).isNotNull(); - assertThat(changeSlugUpdateAction.getAction()).isEqualTo("changeSlug"); - assertThat(((ChangeSlug) changeSlugUpdateAction).getSlug()).isEqualTo(LocalizedString.of(LOCALE, "newSlug")); - } - - @Test - void buildChangeSlugUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getSlug()).thenReturn(null); - - final UpdateAction changeSlugUpdateAction = - buildChangeSlugUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(changeSlugUpdateAction).isNotNull(); - assertThat(changeSlugUpdateAction.getAction()).isEqualTo("changeSlug"); - assertThat(((ChangeSlug) changeSlugUpdateAction).getSlug()).isNull(); - } - - @Test - void buildChangeSlugUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CategoryDraft newCategoryDraftWithSameSlug = mock(CategoryDraft.class); - when(newCategoryDraftWithSameSlug.getSlug()) - .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_SLUG)); - - final Optional> changeSlugUpdateAction = - buildChangeSlugUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameSlug); - - assertThat(changeSlugUpdateAction).isNotNull(); - assertThat(changeSlugUpdateAction).isNotPresent(); - } - - @Test - void buildSetDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getDescription()).thenReturn(LocalizedString.of(LOCALE, "newDescription")); - - final UpdateAction setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(setDescriptionUpdateAction).isNotNull(); - assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); - assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()) - .isEqualTo(LocalizedString.of(LOCALE, "newDescription")); - } - - @Test - void buildSetDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CategoryDraft newCategoryDraftWithSameDescription = mock(CategoryDraft.class); - when(newCategoryDraftWithSameDescription.getDescription()) - .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_DESCRIPTION)); - - final Optional> setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameDescription); - - assertThat(setDescriptionUpdateAction).isNotNull(); - assertThat(setDescriptionUpdateAction).isNotPresent(); - } - - @Test - void buildSetDescriptionUpdateAction_WithNullValues_ShouldNotBuildUpdateActionAndCallCallback() { - when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); - final CategoryDraft newCategory = mock(CategoryDraft.class); - when(newCategory.getDescription()).thenReturn(null); - - final ArrayList callBackResponse = new ArrayList<>(); - - final UpdateAction setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategory).orElse(null); - - assertThat(setDescriptionUpdateAction).isNotNull(); - assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); - assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()) - .isEqualTo(null); - assertThat(callBackResponse).isEmpty(); - } - - @Test - void buildChangeParentUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getParent()).thenReturn(ResourceIdentifier.ofId("2")); - - final UpdateAction changeParentUpdateAction = // parent id is 1. - buildChangeParentUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft, CATEGORY_SYNC_OPTIONS).orElse(null); - - assertThat(changeParentUpdateAction).isNotNull(); - assertThat(changeParentUpdateAction.getAction()).isEqualTo("changeParent"); - assertThat(((ChangeParent) changeParentUpdateAction).getParent()) - .isEqualTo(ResourceIdentifier.ofId("2")); - } - - @Test - void buildChangeParentUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final Category oldCategory = readObjectFromResource(CATEGORY_KEY_1_RESOURCE_PATH, Category.class); - final CategoryDraft newCategory = mock(CategoryDraft.class); - when(newCategory.getParent()) - .thenReturn(ResourceIdentifier.ofId(oldCategory.getParent().getId())); - - final Optional> changeParentUpdateAction = - buildChangeParentUpdateAction(oldCategory, newCategory, CATEGORY_SYNC_OPTIONS); - - assertThat(changeParentUpdateAction).isNotNull(); - assertThat(changeParentUpdateAction).isNotPresent(); - } - - @Test - void buildChangeParentUpdateAction_WithNullValues_ShouldNotBuildUpdateActionAndCallCallback() { - when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); - final CategoryDraft newCategory = mock(CategoryDraft.class); - when(newCategory.getParent()).thenReturn(null); - - final ArrayList callBackResponse = new ArrayList<>(); - final TriConsumer, Optional> updateActionWarningCallBack = + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private static final CategorySyncOptions CATEGORY_SYNC_OPTIONS = + CategorySyncOptionsBuilder.of(CTP_CLIENT).build(); + private static final Locale LOCALE = Locale.GERMAN; + private static final String MOCK_OLD_CATEGORY_PARENT_ID = "1"; + private static final String MOCK_OLD_CATEGORY_NAME = "categoryName"; + private static final String MOCK_OLD_CATEGORY_SLUG = "categorySlug"; + private static final String MOCK_OLD_CATEGORY_KEY = "categoryKey"; + private static final String MOCK_OLD_CATEGORY_EXTERNAL_ID = "externalId"; + private static final String MOCK_OLD_CATEGORY_DESCRIPTION = "categoryDesc"; + private static final String MOCK_OLD_CATEGORY_META_DESCRIPTION = "categoryMetaDesc"; + private static final String MOCK_OLD_CATEGORY_META_TITLE = "categoryMetaTitle"; + private static final String MOCK_OLD_CATEGORY_META_KEYWORDS = "categoryKeywords"; + private static final String MOCK_OLD_CATEGORY_ORDERHINT = "123"; + private static final Category MOCK_OLD_CATEGORY = + getMockCategory( + LOCALE, + MOCK_OLD_CATEGORY_NAME, + MOCK_OLD_CATEGORY_SLUG, + MOCK_OLD_CATEGORY_KEY, + MOCK_OLD_CATEGORY_EXTERNAL_ID, + MOCK_OLD_CATEGORY_DESCRIPTION, + MOCK_OLD_CATEGORY_META_DESCRIPTION, + MOCK_OLD_CATEGORY_META_TITLE, + MOCK_OLD_CATEGORY_META_KEYWORDS, + MOCK_OLD_CATEGORY_ORDERHINT, + MOCK_OLD_CATEGORY_PARENT_ID); + + @Test + void buildChangeNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getName()).thenReturn(LocalizedString.of(LOCALE, "newName")); + + final UpdateAction changeNameUpdateAction = + buildChangeNameUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); + assertThat(((ChangeName) changeNameUpdateAction).getName()) + .isEqualTo(LocalizedString.of(LOCALE, "newName")); + } + + @Test + void buildChangeNameUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getName()).thenReturn(null); + + final UpdateAction changeNameUpdateAction = + buildChangeNameUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); + assertThat(((ChangeName) changeNameUpdateAction).getName()).isNull(); + } + + @Test + void buildChangeNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CategoryDraft newCategoryDraftWithSameName = mock(CategoryDraft.class); + when(newCategoryDraftWithSameName.getName()) + .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> changeNameUpdateAction = + buildChangeNameUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameName); + + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction).isNotPresent(); + } + + @Test + void buildChangeSlugUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getSlug()).thenReturn(LocalizedString.of(LOCALE, "newSlug")); + + final UpdateAction changeSlugUpdateAction = + buildChangeSlugUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(changeSlugUpdateAction).isNotNull(); + assertThat(changeSlugUpdateAction.getAction()).isEqualTo("changeSlug"); + assertThat(((ChangeSlug) changeSlugUpdateAction).getSlug()) + .isEqualTo(LocalizedString.of(LOCALE, "newSlug")); + } + + @Test + void buildChangeSlugUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getSlug()).thenReturn(null); + + final UpdateAction changeSlugUpdateAction = + buildChangeSlugUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(changeSlugUpdateAction).isNotNull(); + assertThat(changeSlugUpdateAction.getAction()).isEqualTo("changeSlug"); + assertThat(((ChangeSlug) changeSlugUpdateAction).getSlug()).isNull(); + } + + @Test + void buildChangeSlugUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CategoryDraft newCategoryDraftWithSameSlug = mock(CategoryDraft.class); + when(newCategoryDraftWithSameSlug.getSlug()) + .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_SLUG)); + + final Optional> changeSlugUpdateAction = + buildChangeSlugUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameSlug); + + assertThat(changeSlugUpdateAction).isNotNull(); + assertThat(changeSlugUpdateAction).isNotPresent(); + } + + @Test + void buildSetDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getDescription()) + .thenReturn(LocalizedString.of(LOCALE, "newDescription")); + + final UpdateAction setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(setDescriptionUpdateAction).isNotNull(); + assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); + assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()) + .isEqualTo(LocalizedString.of(LOCALE, "newDescription")); + } + + @Test + void buildSetDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CategoryDraft newCategoryDraftWithSameDescription = mock(CategoryDraft.class); + when(newCategoryDraftWithSameDescription.getDescription()) + .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_DESCRIPTION)); + + final Optional> setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameDescription); + + assertThat(setDescriptionUpdateAction).isNotNull(); + assertThat(setDescriptionUpdateAction).isNotPresent(); + } + + @Test + void buildSetDescriptionUpdateAction_WithNullValues_ShouldNotBuildUpdateActionAndCallCallback() { + when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); + final CategoryDraft newCategory = mock(CategoryDraft.class); + when(newCategory.getDescription()).thenReturn(null); + + final ArrayList callBackResponse = new ArrayList<>(); + + final UpdateAction setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategory).orElse(null); + + assertThat(setDescriptionUpdateAction).isNotNull(); + assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); + assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()).isEqualTo(null); + assertThat(callBackResponse).isEmpty(); + } + + @Test + void buildChangeParentUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getParent()).thenReturn(ResourceIdentifier.ofId("2")); + + final UpdateAction changeParentUpdateAction = // parent id is 1. + buildChangeParentUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft, CATEGORY_SYNC_OPTIONS) + .orElse(null); + + assertThat(changeParentUpdateAction).isNotNull(); + assertThat(changeParentUpdateAction.getAction()).isEqualTo("changeParent"); + assertThat(((ChangeParent) changeParentUpdateAction).getParent()) + .isEqualTo(ResourceIdentifier.ofId("2")); + } + + @Test + void buildChangeParentUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final Category oldCategory = + readObjectFromResource(CATEGORY_KEY_1_RESOURCE_PATH, Category.class); + final CategoryDraft newCategory = mock(CategoryDraft.class); + when(newCategory.getParent()) + .thenReturn(ResourceIdentifier.ofId(oldCategory.getParent().getId())); + + final Optional> changeParentUpdateAction = + buildChangeParentUpdateAction(oldCategory, newCategory, CATEGORY_SYNC_OPTIONS); + + assertThat(changeParentUpdateAction).isNotNull(); + assertThat(changeParentUpdateAction).isNotPresent(); + } + + @Test + void buildChangeParentUpdateAction_WithNullValues_ShouldNotBuildUpdateActionAndCallCallback() { + when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); + final CategoryDraft newCategory = mock(CategoryDraft.class); + when(newCategory.getParent()).thenReturn(null); + + final ArrayList callBackResponse = new ArrayList<>(); + final TriConsumer, Optional> + updateActionWarningCallBack = (exception, newResource, oldResource) -> callBackResponse.add(exception.getMessage()); - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .warningCallback( - updateActionWarningCallBack) - .build(); - - final Optional> changeParentUpdateAction = - buildChangeParentUpdateAction(MOCK_OLD_CATEGORY, newCategory, - categorySyncOptions); - - assertThat(changeParentUpdateAction).isNotNull(); - assertThat(changeParentUpdateAction).isNotPresent(); - assertThat(callBackResponse).hasSize(1); - assertThat(callBackResponse.get(0)).isEqualTo("Cannot unset 'parent' field of category with id 'oldCatId'."); - } - - @Test - void buildChangeParentUpdateAction_WithBothNullValues_ShouldNotBuildUpdateActionAndNotCallCallback() { - when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); - when(MOCK_OLD_CATEGORY.getParent()).thenReturn(null); - final CategoryDraft newCategory = mock(CategoryDraft.class); - when(newCategory.getParent()).thenReturn(null); - - final ArrayList callBackResponse = new ArrayList<>(); - final TriConsumer, Optional> updateActionWarningCallBack = + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT) + .warningCallback(updateActionWarningCallBack) + .build(); + + final Optional> changeParentUpdateAction = + buildChangeParentUpdateAction(MOCK_OLD_CATEGORY, newCategory, categorySyncOptions); + + assertThat(changeParentUpdateAction).isNotNull(); + assertThat(changeParentUpdateAction).isNotPresent(); + assertThat(callBackResponse).hasSize(1); + assertThat(callBackResponse.get(0)) + .isEqualTo("Cannot unset 'parent' field of category with id 'oldCatId'."); + } + + @Test + void + buildChangeParentUpdateAction_WithBothNullValues_ShouldNotBuildUpdateActionAndNotCallCallback() { + when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); + when(MOCK_OLD_CATEGORY.getParent()).thenReturn(null); + final CategoryDraft newCategory = mock(CategoryDraft.class); + when(newCategory.getParent()).thenReturn(null); + + final ArrayList callBackResponse = new ArrayList<>(); + final TriConsumer, Optional> + updateActionWarningCallBack = (exception, newResource, oldResource) -> callBackResponse.add(exception.getMessage()); - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .warningCallback( - updateActionWarningCallBack) - .build(); - - final Optional> changeParentUpdateAction = - buildChangeParentUpdateAction(MOCK_OLD_CATEGORY, newCategory, - categorySyncOptions); - - assertThat(changeParentUpdateAction).isNotNull(); - assertThat(changeParentUpdateAction).isNotPresent(); - assertThat(callBackResponse).isEmpty(); - } - - @Test - void buildChangeOrderHintUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getOrderHint()).thenReturn("099"); - - final UpdateAction changeOrderHintUpdateAction = - buildChangeOrderHintUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft, CATEGORY_SYNC_OPTIONS).orElse(null); - - assertThat(changeOrderHintUpdateAction).isNotNull(); - assertThat(changeOrderHintUpdateAction.getAction()).isEqualTo("changeOrderHint"); - assertThat(((ChangeOrderHint) changeOrderHintUpdateAction).getOrderHint()) - .isEqualTo("099"); - } - - @Test - void buildChangeOrderHintUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CategoryDraft newCategoryDraftWithSameOrderHint = mock(CategoryDraft.class); - when(newCategoryDraftWithSameOrderHint.getOrderHint()).thenReturn(MOCK_OLD_CATEGORY_ORDERHINT); - - final Optional> changeOrderHintUpdateAction = - buildChangeOrderHintUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameOrderHint, - CATEGORY_SYNC_OPTIONS); - - assertThat(changeOrderHintUpdateAction).isNotNull(); - assertThat(changeOrderHintUpdateAction).isNotPresent(); - } - - @Test - void buildChangeOrderHintUpdateAction_WithNullValues_ShouldNotBuildUpdateActionAndCallCallback() { - when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); - final CategoryDraft newCategory = mock(CategoryDraft.class); - when(newCategory.getOrderHint()).thenReturn(null); - - final ArrayList callBackResponse = new ArrayList<>(); - final TriConsumer, Optional> updateActionWarningCallBack = + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT) + .warningCallback(updateActionWarningCallBack) + .build(); + + final Optional> changeParentUpdateAction = + buildChangeParentUpdateAction(MOCK_OLD_CATEGORY, newCategory, categorySyncOptions); + + assertThat(changeParentUpdateAction).isNotNull(); + assertThat(changeParentUpdateAction).isNotPresent(); + assertThat(callBackResponse).isEmpty(); + } + + @Test + void buildChangeOrderHintUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getOrderHint()).thenReturn("099"); + + final UpdateAction changeOrderHintUpdateAction = + buildChangeOrderHintUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft, CATEGORY_SYNC_OPTIONS) + .orElse(null); + + assertThat(changeOrderHintUpdateAction).isNotNull(); + assertThat(changeOrderHintUpdateAction.getAction()).isEqualTo("changeOrderHint"); + assertThat(((ChangeOrderHint) changeOrderHintUpdateAction).getOrderHint()).isEqualTo("099"); + } + + @Test + void buildChangeOrderHintUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CategoryDraft newCategoryDraftWithSameOrderHint = mock(CategoryDraft.class); + when(newCategoryDraftWithSameOrderHint.getOrderHint()).thenReturn(MOCK_OLD_CATEGORY_ORDERHINT); + + final Optional> changeOrderHintUpdateAction = + buildChangeOrderHintUpdateAction( + MOCK_OLD_CATEGORY, newCategoryDraftWithSameOrderHint, CATEGORY_SYNC_OPTIONS); + + assertThat(changeOrderHintUpdateAction).isNotNull(); + assertThat(changeOrderHintUpdateAction).isNotPresent(); + } + + @Test + void buildChangeOrderHintUpdateAction_WithNullValues_ShouldNotBuildUpdateActionAndCallCallback() { + when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); + final CategoryDraft newCategory = mock(CategoryDraft.class); + when(newCategory.getOrderHint()).thenReturn(null); + + final ArrayList callBackResponse = new ArrayList<>(); + final TriConsumer, Optional> + updateActionWarningCallBack = (exception, newResource, oldResource) -> callBackResponse.add(exception.getMessage()); - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .warningCallback( - updateActionWarningCallBack) - .build(); - - final Optional> changeOrderHintUpdateAction = - buildChangeOrderHintUpdateAction(MOCK_OLD_CATEGORY, newCategory, - categorySyncOptions); - - assertThat(changeOrderHintUpdateAction).isNotNull(); - assertThat(changeOrderHintUpdateAction).isNotPresent(); - assertThat(callBackResponse).hasSize(1); - assertThat(callBackResponse.get(0)).isEqualTo("Cannot unset 'orderHint' field of category with id 'oldCatId'."); - } - - @Test - void buildChangeOrderHintUpdateAction_WithBothNullValues_ShouldNotBuildUpdateActionAndNotCallCallback() { - when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); - when(MOCK_OLD_CATEGORY.getOrderHint()).thenReturn(null); - final CategoryDraft newCategory = mock(CategoryDraft.class); - when(newCategory.getOrderHint()).thenReturn(null); - - final ArrayList callBackResponse = new ArrayList<>(); - final TriConsumer, Optional> updateActionWarningCallBack = + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT) + .warningCallback(updateActionWarningCallBack) + .build(); + + final Optional> changeOrderHintUpdateAction = + buildChangeOrderHintUpdateAction(MOCK_OLD_CATEGORY, newCategory, categorySyncOptions); + + assertThat(changeOrderHintUpdateAction).isNotNull(); + assertThat(changeOrderHintUpdateAction).isNotPresent(); + assertThat(callBackResponse).hasSize(1); + assertThat(callBackResponse.get(0)) + .isEqualTo("Cannot unset 'orderHint' field of category with id 'oldCatId'."); + } + + @Test + void + buildChangeOrderHintUpdateAction_WithBothNullValues_ShouldNotBuildUpdateActionAndNotCallCallback() { + when(MOCK_OLD_CATEGORY.getId()).thenReturn("oldCatId"); + when(MOCK_OLD_CATEGORY.getOrderHint()).thenReturn(null); + final CategoryDraft newCategory = mock(CategoryDraft.class); + when(newCategory.getOrderHint()).thenReturn(null); + + final ArrayList callBackResponse = new ArrayList<>(); + final TriConsumer, Optional> + updateActionWarningCallBack = (exception, newResource, oldResource) -> callBackResponse.add(exception.getMessage()); - final CategorySyncOptions categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .warningCallback( - updateActionWarningCallBack) - .build(); - - final Optional> changeOrderHintUpdateAction = - buildChangeOrderHintUpdateAction(MOCK_OLD_CATEGORY, newCategory, - categorySyncOptions); - - assertThat(changeOrderHintUpdateAction).isNotNull(); - assertThat(changeOrderHintUpdateAction).isNotPresent(); - assertThat(callBackResponse).isEmpty(); - } - - @Test - void buildSetMetaTitleUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getMetaTitle()).thenReturn(LocalizedString.of(LOCALE, "newMetaTitle")); - - final UpdateAction setMetaTitleUpdateAction = - buildSetMetaTitleUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(setMetaTitleUpdateAction).isNotNull(); - assertThat(setMetaTitleUpdateAction.getAction()).isEqualTo("setMetaTitle"); - assertThat(((SetMetaTitle) setMetaTitleUpdateAction).getMetaTitle()) - .isEqualTo(LocalizedString.of(LOCALE, "newMetaTitle")); - } - - @Test - void buildSetMetaTitleUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getMetaTitle()).thenReturn(null); - - final UpdateAction setMetaTitleUpdateAction = - buildSetMetaTitleUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(setMetaTitleUpdateAction).isNotNull(); - assertThat(setMetaTitleUpdateAction.getAction()).isEqualTo("setMetaTitle"); - assertThat(((SetMetaTitle) setMetaTitleUpdateAction).getMetaTitle()) - .isEqualTo(null); - } - - @Test - void buildSetMetaTitleUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CategoryDraft newCategoryDraftWithSameTitle = mock(CategoryDraft.class); - when(newCategoryDraftWithSameTitle.getMetaTitle()) - .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_META_TITLE)); - - final Optional> setMetaTitleUpdateAction = - buildSetMetaTitleUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameTitle); - - assertThat(setMetaTitleUpdateAction).isNotNull(); - assertThat(setMetaTitleUpdateAction).isNotPresent(); - } - - @Test - void buildSetMetaKeywordsUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getMetaKeywords()).thenReturn(LocalizedString.of(LOCALE, "newMetaKW")); - - final UpdateAction setMetaKeywordsUpdateAction = - buildSetMetaKeywordsUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(setMetaKeywordsUpdateAction).isNotNull(); - assertThat(setMetaKeywordsUpdateAction.getAction()).isEqualTo("setMetaKeywords"); - assertThat(((SetMetaKeywords) setMetaKeywordsUpdateAction).getMetaKeywords()) - .isEqualTo(LocalizedString.of(LOCALE, "newMetaKW")); - } - - @Test - void buildSetMetaKeywordsUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getMetaKeywords()).thenReturn(null); - - final UpdateAction setMetaKeywordsUpdateAction = - buildSetMetaKeywordsUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(setMetaKeywordsUpdateAction).isNotNull(); - assertThat(setMetaKeywordsUpdateAction.getAction()).isEqualTo("setMetaKeywords"); - assertThat(((SetMetaKeywords) setMetaKeywordsUpdateAction).getMetaKeywords()).isNull(); - } - - @Test - void buildSetMetaKeywordsUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CategoryDraft newCategoryDraftWithSameMetaKeywords = mock(CategoryDraft.class); - when(newCategoryDraftWithSameMetaKeywords.getMetaKeywords()) - .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_META_KEYWORDS)); - - final Optional> setMetaKeywordsUpdateAction = - buildSetMetaKeywordsUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameMetaKeywords); - - assertThat(setMetaKeywordsUpdateAction).isNotNull(); - assertThat(setMetaKeywordsUpdateAction).isNotPresent(); - } - - @Test - void buildSetMetaDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getMetaDescription()).thenReturn(LocalizedString.of(LOCALE, "newMetaDesc")); - - final UpdateAction setMetaDescriptionUpdateAction = - buildSetMetaDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(setMetaDescriptionUpdateAction).isNotNull(); - assertThat(setMetaDescriptionUpdateAction.getAction()).isEqualTo("setMetaDescription"); - assertThat(((SetMetaDescription) setMetaDescriptionUpdateAction).getMetaDescription()) - .isEqualTo(LocalizedString.of(LOCALE, "newMetaDesc")); - } - - @Test - void buildSetMetaDescriptionUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getMetaDescription()).thenReturn(null); - - final UpdateAction setMetaDescriptionUpdateAction = - buildSetMetaDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(setMetaDescriptionUpdateAction).isNotNull(); - assertThat(setMetaDescriptionUpdateAction.getAction()).isEqualTo("setMetaDescription"); - assertThat(((SetMetaDescription) setMetaDescriptionUpdateAction).getMetaDescription()).isNull(); - } - - @Test - void buildSetMetaDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final CategoryDraft newCategoryDraftWithSameMetaDescription = mock(CategoryDraft.class); - when(newCategoryDraftWithSameMetaDescription.getMetaDescription()) - .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_META_DESCRIPTION)); - - final Optional> setMetaDescriptionUpdateAction = - buildSetMetaDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameMetaDescription); - - assertThat(setMetaDescriptionUpdateAction).isNotNull(); - assertThat(setMetaDescriptionUpdateAction).isNotPresent(); - } - - @Test - void buildSetExternalIdUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getExternalId()).thenReturn("newExternalId"); - - final UpdateAction setExternalIdUpdateAction = - buildSetExternalIdUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(setExternalIdUpdateAction).isNotNull(); - assertThat(setExternalIdUpdateAction.getAction()).isEqualTo("setExternalId"); - assertThat(((SetExternalId) setExternalIdUpdateAction).getExternalId()) - .isEqualTo("newExternalId"); - } - - @Test - void buildSetExternalIdUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getExternalId()).thenReturn(null); - - final UpdateAction setExternalIdUpdateAction = - buildSetExternalIdUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); - - assertThat(setExternalIdUpdateAction).isNotNull(); - assertThat(setExternalIdUpdateAction.getAction()).isEqualTo("setExternalId"); - assertThat(((SetExternalId) setExternalIdUpdateAction).getExternalId()) - .isNull(); - } - - @Test - void buildSetExternalIdUpdateAction_WithSameValues_ShouldNotBuildUpdateActions() { - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getExternalId()).thenReturn(MOCK_OLD_CATEGORY_EXTERNAL_ID); - - final Optional> setExternalIdUpdateAction = - buildSetExternalIdUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft); - - assertThat(setExternalIdUpdateAction).isNotNull(); - assertThat(setExternalIdUpdateAction).isNotPresent(); - } + final CategorySyncOptions categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT) + .warningCallback(updateActionWarningCallBack) + .build(); + + final Optional> changeOrderHintUpdateAction = + buildChangeOrderHintUpdateAction(MOCK_OLD_CATEGORY, newCategory, categorySyncOptions); + + assertThat(changeOrderHintUpdateAction).isNotNull(); + assertThat(changeOrderHintUpdateAction).isNotPresent(); + assertThat(callBackResponse).isEmpty(); + } + + @Test + void buildSetMetaTitleUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getMetaTitle()).thenReturn(LocalizedString.of(LOCALE, "newMetaTitle")); + + final UpdateAction setMetaTitleUpdateAction = + buildSetMetaTitleUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(setMetaTitleUpdateAction).isNotNull(); + assertThat(setMetaTitleUpdateAction.getAction()).isEqualTo("setMetaTitle"); + assertThat(((SetMetaTitle) setMetaTitleUpdateAction).getMetaTitle()) + .isEqualTo(LocalizedString.of(LOCALE, "newMetaTitle")); + } + + @Test + void buildSetMetaTitleUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getMetaTitle()).thenReturn(null); + + final UpdateAction setMetaTitleUpdateAction = + buildSetMetaTitleUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(setMetaTitleUpdateAction).isNotNull(); + assertThat(setMetaTitleUpdateAction.getAction()).isEqualTo("setMetaTitle"); + assertThat(((SetMetaTitle) setMetaTitleUpdateAction).getMetaTitle()).isEqualTo(null); + } + + @Test + void buildSetMetaTitleUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CategoryDraft newCategoryDraftWithSameTitle = mock(CategoryDraft.class); + when(newCategoryDraftWithSameTitle.getMetaTitle()) + .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_META_TITLE)); + + final Optional> setMetaTitleUpdateAction = + buildSetMetaTitleUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameTitle); + + assertThat(setMetaTitleUpdateAction).isNotNull(); + assertThat(setMetaTitleUpdateAction).isNotPresent(); + } + + @Test + void buildSetMetaKeywordsUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getMetaKeywords()).thenReturn(LocalizedString.of(LOCALE, "newMetaKW")); + + final UpdateAction setMetaKeywordsUpdateAction = + buildSetMetaKeywordsUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(setMetaKeywordsUpdateAction).isNotNull(); + assertThat(setMetaKeywordsUpdateAction.getAction()).isEqualTo("setMetaKeywords"); + assertThat(((SetMetaKeywords) setMetaKeywordsUpdateAction).getMetaKeywords()) + .isEqualTo(LocalizedString.of(LOCALE, "newMetaKW")); + } + + @Test + void buildSetMetaKeywordsUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getMetaKeywords()).thenReturn(null); + + final UpdateAction setMetaKeywordsUpdateAction = + buildSetMetaKeywordsUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(setMetaKeywordsUpdateAction).isNotNull(); + assertThat(setMetaKeywordsUpdateAction.getAction()).isEqualTo("setMetaKeywords"); + assertThat(((SetMetaKeywords) setMetaKeywordsUpdateAction).getMetaKeywords()).isNull(); + } + + @Test + void buildSetMetaKeywordsUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CategoryDraft newCategoryDraftWithSameMetaKeywords = mock(CategoryDraft.class); + when(newCategoryDraftWithSameMetaKeywords.getMetaKeywords()) + .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_META_KEYWORDS)); + + final Optional> setMetaKeywordsUpdateAction = + buildSetMetaKeywordsUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraftWithSameMetaKeywords); + + assertThat(setMetaKeywordsUpdateAction).isNotNull(); + assertThat(setMetaKeywordsUpdateAction).isNotPresent(); + } + + @Test + void buildSetMetaDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getMetaDescription()) + .thenReturn(LocalizedString.of(LOCALE, "newMetaDesc")); + + final UpdateAction setMetaDescriptionUpdateAction = + buildSetMetaDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(setMetaDescriptionUpdateAction).isNotNull(); + assertThat(setMetaDescriptionUpdateAction.getAction()).isEqualTo("setMetaDescription"); + assertThat(((SetMetaDescription) setMetaDescriptionUpdateAction).getMetaDescription()) + .isEqualTo(LocalizedString.of(LOCALE, "newMetaDesc")); + } + + @Test + void buildSetMetaDescriptionUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getMetaDescription()).thenReturn(null); + + final UpdateAction setMetaDescriptionUpdateAction = + buildSetMetaDescriptionUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(setMetaDescriptionUpdateAction).isNotNull(); + assertThat(setMetaDescriptionUpdateAction.getAction()).isEqualTo("setMetaDescription"); + assertThat(((SetMetaDescription) setMetaDescriptionUpdateAction).getMetaDescription()).isNull(); + } + + @Test + void buildSetMetaDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final CategoryDraft newCategoryDraftWithSameMetaDescription = mock(CategoryDraft.class); + when(newCategoryDraftWithSameMetaDescription.getMetaDescription()) + .thenReturn(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_META_DESCRIPTION)); + + final Optional> setMetaDescriptionUpdateAction = + buildSetMetaDescriptionUpdateAction( + MOCK_OLD_CATEGORY, newCategoryDraftWithSameMetaDescription); + + assertThat(setMetaDescriptionUpdateAction).isNotNull(); + assertThat(setMetaDescriptionUpdateAction).isNotPresent(); + } + + @Test + void buildSetExternalIdUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getExternalId()).thenReturn("newExternalId"); + + final UpdateAction setExternalIdUpdateAction = + buildSetExternalIdUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(setExternalIdUpdateAction).isNotNull(); + assertThat(setExternalIdUpdateAction.getAction()).isEqualTo("setExternalId"); + assertThat(((SetExternalId) setExternalIdUpdateAction).getExternalId()) + .isEqualTo("newExternalId"); + } + + @Test + void buildSetExternalIdUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getExternalId()).thenReturn(null); + + final UpdateAction setExternalIdUpdateAction = + buildSetExternalIdUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft).orElse(null); + + assertThat(setExternalIdUpdateAction).isNotNull(); + assertThat(setExternalIdUpdateAction.getAction()).isEqualTo("setExternalId"); + assertThat(((SetExternalId) setExternalIdUpdateAction).getExternalId()).isNull(); + } + + @Test + void buildSetExternalIdUpdateAction_WithSameValues_ShouldNotBuildUpdateActions() { + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getExternalId()).thenReturn(MOCK_OLD_CATEGORY_EXTERNAL_ID); + + final Optional> setExternalIdUpdateAction = + buildSetExternalIdUpdateAction(MOCK_OLD_CATEGORY, newCategoryDraft); + + assertThat(setExternalIdUpdateAction).isNotNull(); + assertThat(setExternalIdUpdateAction).isNotPresent(); + } } diff --git a/src/test/java/com/commercetools/sync/categories/utils/categoryupdateactionutils/BuildAssetsUpdateActionsTest.java b/src/test/java/com/commercetools/sync/categories/utils/categoryupdateactionutils/BuildAssetsUpdateActionsTest.java index a047cde12b..9a5b96a7c1 100644 --- a/src/test/java/com/commercetools/sync/categories/utils/categoryupdateactionutils/BuildAssetsUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/categories/utils/categoryupdateactionutils/BuildAssetsUpdateActionsTest.java @@ -1,5 +1,18 @@ package com.commercetools.sync.categories.utils.categoryupdateactionutils; +import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildAssetsUpdateActions; +import static com.commercetools.sync.commons.utils.AssetsUpdateActionUtils.ASSET_KEY_NOT_SET; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; @@ -17,383 +30,461 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.AssetDraftBuilder; import io.sphere.sdk.models.AssetSourceBuilder; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; - -import static com.commercetools.sync.categories.utils.CategoryUpdateActionUtils.buildAssetsUpdateActions; -import static com.commercetools.sync.commons.utils.AssetsUpdateActionUtils.ASSET_KEY_NOT_SET; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class BuildAssetsUpdateActionsTest { - private static final String RES_ROOT = - "com/commercetools/sync/categories/utils/categoryupdateactionutils/assets/"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_WITHOUT_KEY = RES_ROOT - + "category-draft-with-assets-abc-without-key-for-c.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_ABC = RES_ROOT + "category-draft-with-assets-abc.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_ABB = RES_ROOT + "category-draft-with-assets-abb.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_ABC_WITH_CHANGES = - RES_ROOT + "category-draft-with-assets-abc-with-changes.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_AB = RES_ROOT + "category-draft-with-assets-ab.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_ABCD = RES_ROOT + "category-draft-with-assets-abcd.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_ABD = RES_ROOT + "category-draft-with-assets-abd.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_CAB = RES_ROOT + "category-draft-with-assets-cab.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_CB = RES_ROOT + "category-draft-with-assets-cb.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_ACBD = RES_ROOT + "category-draft-with-assets-acbd.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_ADBC = RES_ROOT + "category-draft-with-assets-adbc.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_CBD = RES_ROOT + "category-draft-with-assets-cbd.json"; - private static final String CATEGORY_DRAFT_WITH_ASSETS_CBD_WITH_CHANGES = - RES_ROOT + "category-draft-with-assets-cbd-with-changes.json"; - - private List warningCallBackMessages; - private CategorySyncOptions syncOptions; - - @BeforeEach - void setupTest() { - warningCallBackMessages = new ArrayList<>(); - syncOptions = CategorySyncOptionsBuilder.of(mock(SphereClient.class)) - .warningCallback((exception, newResource, oldResource) -> + private static final String RES_ROOT = + "com/commercetools/sync/categories/utils/categoryupdateactionutils/assets/"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_WITHOUT_KEY = + RES_ROOT + "category-draft-with-assets-abc-without-key-for-c.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_ABC = + RES_ROOT + "category-draft-with-assets-abc.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_ABB = + RES_ROOT + "category-draft-with-assets-abb.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_ABC_WITH_CHANGES = + RES_ROOT + "category-draft-with-assets-abc-with-changes.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_AB = + RES_ROOT + "category-draft-with-assets-ab.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_ABCD = + RES_ROOT + "category-draft-with-assets-abcd.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_ABD = + RES_ROOT + "category-draft-with-assets-abd.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_CAB = + RES_ROOT + "category-draft-with-assets-cab.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_CB = + RES_ROOT + "category-draft-with-assets-cb.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_ACBD = + RES_ROOT + "category-draft-with-assets-acbd.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_ADBC = + RES_ROOT + "category-draft-with-assets-adbc.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_CBD = + RES_ROOT + "category-draft-with-assets-cbd.json"; + private static final String CATEGORY_DRAFT_WITH_ASSETS_CBD_WITH_CHANGES = + RES_ROOT + "category-draft-with-assets-cbd-with-changes.json"; + + private List warningCallBackMessages; + private CategorySyncOptions syncOptions; + + @BeforeEach + void setupTest() { + warningCallBackMessages = new ArrayList<>(); + syncOptions = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)) + .warningCallback( + (exception, newResource, oldResource) -> warningCallBackMessages.add(exception.getMessage())) .build(); - } - - @Test - void buildAssetsUpdateActions_WithNullNewAssetsAndExistingAssets_ShouldBuild3RemoveActions() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, - CategoryDraftBuilder.of(ofEnglish("name"), ofEnglish("slug")).assets(null).build(), syncOptions); - - assertThat(updateActions).containsExactlyInAnyOrder( - RemoveAsset.ofKey("a"), - RemoveAsset.ofKey("b"), - RemoveAsset.ofKey("c")); - } - - @Test - void buildAssetsUpdateActions_WithNullNewAssetsAndNoOldAssets_ShouldNotBuildActions() { - final Category oldCategory = mock(Category.class); - when(oldCategory.getAssets()).thenReturn(emptyList()); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, - CategoryDraftBuilder.of(ofEnglish("name"), ofEnglish("slug")).assets(null).build(), syncOptions); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAssetsUpdateActions_WithNewAssetsAndNoOldAssets_ShouldBuild3AddActions() { - final Category oldCategory = mock(Category.class); - when(oldCategory.getAssets()).thenReturn(emptyList()); - - final CategoryDraft newCategoryDraft = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, - CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactlyInAnyOrder( + } + + @Test + void buildAssetsUpdateActions_WithNullNewAssetsAndExistingAssets_ShouldBuild3RemoveActions() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + + final List> updateActions = + buildAssetsUpdateActions( + oldCategory, + CategoryDraftBuilder.of(ofEnglish("name"), ofEnglish("slug")).assets(null).build(), + syncOptions); + + assertThat(updateActions) + .containsExactlyInAnyOrder( + RemoveAsset.ofKey("a"), RemoveAsset.ofKey("b"), RemoveAsset.ofKey("c")); + } + + @Test + void buildAssetsUpdateActions_WithNullNewAssetsAndNoOldAssets_ShouldNotBuildActions() { + final Category oldCategory = mock(Category.class); + when(oldCategory.getAssets()).thenReturn(emptyList()); + + final List> updateActions = + buildAssetsUpdateActions( + oldCategory, + CategoryDraftBuilder.of(ofEnglish("name"), ofEnglish("slug")).assets(null).build(), + syncOptions); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAssetsUpdateActions_WithNewAssetsAndNoOldAssets_ShouldBuild3AddActions() { + final Category oldCategory = mock(Category.class); + when(oldCategory.getAssets()).thenReturn(emptyList()); + + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions) + .containsExactlyInAnyOrder( AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("a").tags(emptySet()).build(), 0), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("a") + .tags(emptySet()) + .build(), + 0), AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("b").tags(emptySet()).build(), 1), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("b") + .tags(emptySet()) + .build(), + 1), AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("c").tags(emptySet()).build(), 2) - ); - } + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("c") + .tags(emptySet()) + .build(), + 2)); + } - @Test - void buildAssetsUpdateActions_WithNewAssetsAndOneWithoutKeyAndNoOldAssets_ShouldTriggerWarningCallBack() { - final Category oldCategory = mock(Category.class); - when(oldCategory.getAssets()).thenReturn(emptyList()); + @Test + void + buildAssetsUpdateActions_WithNewAssetsAndOneWithoutKeyAndNoOldAssets_ShouldTriggerWarningCallBack() { + final Category oldCategory = mock(Category.class); + when(oldCategory.getAssets()).thenReturn(emptyList()); - final CategoryDraft newCategoryDraft = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_WITHOUT_KEY, - CategoryDraft.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_WITHOUT_KEY, CategoryDraft.class); - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - assertThat(updateActions).containsExactlyInAnyOrder( + assertThat(updateActions) + .containsExactlyInAnyOrder( AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("a").tags(emptySet()).build(), 0), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("a") + .tags(emptySet()) + .build(), + 0), AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("b").tags(emptySet()).build(), 1) - - - ); - assertThat(warningCallBackMessages.get(0)).isEqualTo(format(ASSET_KEY_NOT_SET, - "name: LocalizedString(en -> asset name)")); - } - - - @Test - void buildAssetsUpdateActions_WithNewAssetsAndOneWithoutKeyAndOldAssets_ShouldTriggerWarningCallBack() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_WITHOUT_KEY, - CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactlyInAnyOrder(RemoveAsset.ofKey("c")); - assertThat(warningCallBackMessages.get(0)).isEqualTo(format(ASSET_KEY_NOT_SET, - "name: LocalizedString(en -> asset name)")); - } - - @Test - void buildAssetsUpdateActions_WithNewAssetsAndOldAssetsAndOneWithoutKey_ShouldTriggerWarningCallBack() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_WITHOUT_KEY, Category.class); - final CategoryDraft newCategoryDraft = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, - CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactlyInAnyOrder( + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("b") + .tags(emptySet()) + .build(), + 1)); + assertThat(warningCallBackMessages.get(0)) + .isEqualTo(format(ASSET_KEY_NOT_SET, "name: LocalizedString(en -> asset name)")); + } + + @Test + void + buildAssetsUpdateActions_WithNewAssetsAndOneWithoutKeyAndOldAssets_ShouldTriggerWarningCallBack() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_WITHOUT_KEY, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions).containsExactlyInAnyOrder(RemoveAsset.ofKey("c")); + assertThat(warningCallBackMessages.get(0)) + .isEqualTo(format(ASSET_KEY_NOT_SET, "name: LocalizedString(en -> asset name)")); + } + + @Test + void + buildAssetsUpdateActions_WithNewAssetsAndOldAssetsAndOneWithoutKey_ShouldTriggerWarningCallBack() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_WITHOUT_KEY, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions) + .containsExactlyInAnyOrder( AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("c").tags(emptySet()).build(), 2) - - ); - assertThat(warningCallBackMessages.get(0)).isEqualTo(format(ASSET_KEY_NOT_SET, "id: 3" )); - - } - - - - - @Test - void buildAssetsUpdateActions_WithIdenticalAssets_ShouldNotBuildUpdateActions() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = - readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, CategoryDraft.class); - - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAssetsUpdateActions_WithDuplicateAssetKeys_ShouldNotBuildActionsAndTriggerErrorCb() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = - readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABB, CategoryDraft.class); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final CategorySyncOptions syncOptions = - CategorySyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - if (exception.getCause() != null) { - errorMessages.add(exception.getCause().getMessage()); - } - exceptions.add(exception.getCause()); + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("c") + .tags(emptySet()) + .build(), + 2)); + assertThat(warningCallBackMessages.get(0)).isEqualTo(format(ASSET_KEY_NOT_SET, "id: 3")); + } + + @Test + void buildAssetsUpdateActions_WithIdenticalAssets_ShouldNotBuildUpdateActions() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAssetsUpdateActions_WithDuplicateAssetKeys_ShouldNotBuildActionsAndTriggerErrorCb() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABB, CategoryDraft.class); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final CategorySyncOptions syncOptions = + CategorySyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + if (exception.getCause() != null) { + errorMessages.add(exception.getCause().getMessage()); + } + exceptions.add(exception.getCause()); }) - .build(); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).isEmpty(); - assertThat(errorMessages).hasSize(2); - assertThat(errorMessages.get(0)).matches("Failed to build update actions for the assets of the category with " - + "the key 'null'."); - assertThat(errorMessages.get(1)).matches(".*DuplicateKeyException: Supplied asset drafts have duplicate keys. " - + "Asset keys are expected to be unique inside their container \\(a product variant or a category\\)."); - assertThat(exceptions).hasSize(1); - assertThat(exceptions.get(0)).isExactlyInstanceOf(BuildUpdateActionException.class); - assertThat(exceptions.get(0).getMessage()).contains("Supplied asset drafts have duplicate " - + "keys. Asset keys are expected to be unique inside their container (a product variant or a category)."); - assertThat(exceptions.get(0).getCause()).isExactlyInstanceOf(DuplicateKeyException.class); - } - - @Test - void buildAssetsUpdateActions_WithSameAssetPositionButChangesWithin_ShouldBuildUpdateActions() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC_WITH_CHANGES, - CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - final HashSet expectedNewTags = new HashSet<>(); - expectedNewTags.add("new tag"); - - assertThat(updateActions).containsExactlyInAnyOrder( + .build(); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(errorMessages).hasSize(2); + assertThat(errorMessages.get(0)) + .matches( + "Failed to build update actions for the assets of the category with " + + "the key 'null'."); + assertThat(errorMessages.get(1)) + .matches( + ".*DuplicateKeyException: Supplied asset drafts have duplicate keys. " + + "Asset keys are expected to be unique inside their container \\(a product variant or a category\\)."); + assertThat(exceptions).hasSize(1); + assertThat(exceptions.get(0)).isExactlyInstanceOf(BuildUpdateActionException.class); + assertThat(exceptions.get(0).getMessage()) + .contains( + "Supplied asset drafts have duplicate " + + "keys. Asset keys are expected to be unique inside their container (a product variant or a category)."); + assertThat(exceptions.get(0).getCause()).isExactlyInstanceOf(DuplicateKeyException.class); + } + + @Test + void buildAssetsUpdateActions_WithSameAssetPositionButChangesWithin_ShouldBuildUpdateActions() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC_WITH_CHANGES, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + final HashSet expectedNewTags = new HashSet<>(); + expectedNewTags.add("new tag"); + + assertThat(updateActions) + .containsExactlyInAnyOrder( ChangeAssetName.ofKey("a", ofEnglish("asset new name")), ChangeAssetName.ofKey("b", ofEnglish("asset new name 2")), ChangeAssetName.ofKey("c", ofEnglish("asset new name 3")), SetAssetTags.ofKey("a", expectedNewTags), - SetAssetSources.ofKey("a", - asList(AssetSourceBuilder.ofUri("new uri").build(), + SetAssetSources.ofKey( + "a", + asList( + AssetSourceBuilder.ofUri("new uri").build(), AssetSourceBuilder.ofUri(null).key("new source").build())), - SetAssetSources.ofKey("c", - singletonList(AssetSourceBuilder.ofUri("uri").key("newKey").build())) - ); - } - - @Test - void buildAssetsUpdateActions_WithOneMissingAsset_ShouldBuildRemoveAssetAction() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_AB, - CategoryDraft.class); - - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactly(RemoveAsset.ofKey("c")); - } - - @Test - void buildAssetsUpdateActions_WithOneExtraAsset_ShouldBuildAddAssetAction() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = - readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABCD, CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactly( + SetAssetSources.ofKey( + "c", singletonList(AssetSourceBuilder.ofUri("uri").key("newKey").build()))); + } + + @Test + void buildAssetsUpdateActions_WithOneMissingAsset_ShouldBuildRemoveAssetAction() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_AB, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions).containsExactly(RemoveAsset.ofKey("c")); + } + + @Test + void buildAssetsUpdateActions_WithOneExtraAsset_ShouldBuildAddAssetAction() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABCD, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions) + .containsExactly( AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) .key("d") .tags(emptySet()) - .build(), 3)); - } - - @Test - void buildAssetsUpdateActions_WithOneAssetSwitch_ShouldBuildRemoveAndAddAssetActions() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABD, - CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactly( + .build(), + 3)); + } + + @Test + void buildAssetsUpdateActions_WithOneAssetSwitch_ShouldBuildRemoveAndAddAssetActions() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABD, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions) + .containsExactly( RemoveAsset.ofKey("c"), AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build(), 2) - ); - - } - - @Test - void buildAssetsUpdateActions_WithDifferent_ShouldBuildChangeAssetOrderAction() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = - readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_CAB, CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactly(ChangeAssetOrder.of(asList("3", "1", "2"))); - } - - @Test - void buildAssetsUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = - readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_CB, CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactly(RemoveAsset.ofKey("a"), ChangeAssetOrder.of(asList("3", "2"))); - } - - @Test - void buildAssetsUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = - readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ACBD, CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactly( + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build(), + 2)); + } + + @Test + void buildAssetsUpdateActions_WithDifferent_ShouldBuildChangeAssetOrderAction() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_CAB, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions).containsExactly(ChangeAssetOrder.of(asList("3", "1", "2"))); + } + + @Test + void + buildAssetsUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_CB, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions) + .containsExactly(RemoveAsset.ofKey("a"), ChangeAssetOrder.of(asList("3", "2"))); + } + + @Test + void buildAssetsUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ACBD, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions) + .containsExactly( ChangeAssetOrder.of(asList("1", "3", "2")), AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build(), 3) - ); - } - - @Test - void buildAssetsUpdateActions_WithAddedAssetInBetween_ShouldBuildAddWithCorrectPositionActions() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = - readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ADBC, CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactly( + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build(), + 3)); + } + + @Test + void buildAssetsUpdateActions_WithAddedAssetInBetween_ShouldBuildAddWithCorrectPositionActions() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ADBC, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions) + .containsExactly( AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build(), 1) - ); - } - - @Test - void buildAssetsUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveAssetActions() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = - readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_CBD, CategoryDraft.class); - - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactly( + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build(), + 1)); + } + + @Test + void buildAssetsUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveAssetActions() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_CBD, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions) + .containsExactly( RemoveAsset.ofKey("a"), ChangeAssetOrder.of(asList("3", "2")), AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build(), 2) - ); - } - - @Test - void buildAssetsUpdateActions_WithAddedRemovedAndDifOrderAndNewName_ShouldBuildAllDiffAssetActions() { - final Category oldCategory = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); - final CategoryDraft newCategoryDraft = readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_CBD_WITH_CHANGES, - CategoryDraft.class); - - final List> updateActions = - buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); - - assertThat(updateActions).containsExactly( + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build(), + 2)); + } + + @Test + void + buildAssetsUpdateActions_WithAddedRemovedAndDifOrderAndNewName_ShouldBuildAllDiffAssetActions() { + final Category oldCategory = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_ABC, Category.class); + final CategoryDraft newCategoryDraft = + readObjectFromResource(CATEGORY_DRAFT_WITH_ASSETS_CBD_WITH_CHANGES, CategoryDraft.class); + + final List> updateActions = + buildAssetsUpdateActions(oldCategory, newCategoryDraft, syncOptions); + + assertThat(updateActions) + .containsExactly( RemoveAsset.ofKey("a"), ChangeAssetName.ofKey("c", ofEnglish("new name")), ChangeAssetOrder.of(asList("3", "2")), AddAsset.of( - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build(), 2) - ); - } + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build(), + 2)); + } } diff --git a/src/test/java/com/commercetools/sync/commons/BaseSyncTest.java b/src/test/java/com/commercetools/sync/commons/BaseSyncTest.java index 0289fbd404..dbcc9ca576 100644 --- a/src/test/java/com/commercetools/sync/commons/BaseSyncTest.java +++ b/src/test/java/com/commercetools/sync/commons/BaseSyncTest.java @@ -1,46 +1,52 @@ package com.commercetools.sync.commons; +import static com.commercetools.sync.commons.BaseSync.executeSupplierIfConcurrentModificationException; +import static org.assertj.core.api.Assertions.assertThat; + import io.sphere.sdk.client.BadRequestException; import io.sphere.sdk.client.ConcurrentModificationException; -import org.junit.jupiter.api.Test; - import java.util.Optional; import java.util.concurrent.CompletionException; import java.util.function.Supplier; - -import static com.commercetools.sync.commons.BaseSync.executeSupplierIfConcurrentModificationException; -import static org.assertj.core.api.Assertions.assertThat; - +import org.junit.jupiter.api.Test; class BaseSyncTest { - @Test - void executeSupplierIfConcurrentModificationException_WithConcModExceptionAsCause_ShouldExecuteFirstSupplier() { - final CompletionException sphereException = new CompletionException(new ConcurrentModificationException()); - final Supplier> firstSupplier = () -> Optional.of("firstSupplier"); - final Supplier> secondSupplier = () -> Optional.of("SecondSupplier"); - final Optional result = executeSupplierIfConcurrentModificationException(sphereException, firstSupplier, - secondSupplier); - assertThat(result).contains("firstSupplier"); - } - - @Test - void executeSupplierIfConcurrentModificationException_WithBadRequestExceptionAsCause_ShouldExecuteSecondSupplier() { - final CompletionException sphereException = new CompletionException(new BadRequestException("Bad Request!")); - final Supplier> firstSupplier = () -> Optional.of("firstSupplier"); - final Supplier> secondSupplier = () -> Optional.of("SecondSupplier"); - final Optional result = executeSupplierIfConcurrentModificationException(sphereException, firstSupplier, - secondSupplier); - assertThat(result).contains("SecondSupplier"); - } - - @Test - void executeSupplierIfConcurrentModificationException_WithConcurrentModException_ShouldExecuteSecondSupplier() { - final ConcurrentModificationException sphereException = new ConcurrentModificationException(); - final Supplier> firstSupplier = () -> Optional.of("firstSupplier"); - final Supplier> secondSupplier = () -> Optional.of("SecondSupplier"); - final Optional result = executeSupplierIfConcurrentModificationException(sphereException, firstSupplier, - secondSupplier); - assertThat(result).contains("SecondSupplier"); - } + @Test + void + executeSupplierIfConcurrentModificationException_WithConcModExceptionAsCause_ShouldExecuteFirstSupplier() { + final CompletionException sphereException = + new CompletionException(new ConcurrentModificationException()); + final Supplier> firstSupplier = () -> Optional.of("firstSupplier"); + final Supplier> secondSupplier = () -> Optional.of("SecondSupplier"); + final Optional result = + executeSupplierIfConcurrentModificationException( + sphereException, firstSupplier, secondSupplier); + assertThat(result).contains("firstSupplier"); + } + + @Test + void + executeSupplierIfConcurrentModificationException_WithBadRequestExceptionAsCause_ShouldExecuteSecondSupplier() { + final CompletionException sphereException = + new CompletionException(new BadRequestException("Bad Request!")); + final Supplier> firstSupplier = () -> Optional.of("firstSupplier"); + final Supplier> secondSupplier = () -> Optional.of("SecondSupplier"); + final Optional result = + executeSupplierIfConcurrentModificationException( + sphereException, firstSupplier, secondSupplier); + assertThat(result).contains("SecondSupplier"); + } + + @Test + void + executeSupplierIfConcurrentModificationException_WithConcurrentModException_ShouldExecuteSecondSupplier() { + final ConcurrentModificationException sphereException = new ConcurrentModificationException(); + final Supplier> firstSupplier = () -> Optional.of("firstSupplier"); + final Supplier> secondSupplier = () -> Optional.of("SecondSupplier"); + final Optional result = + executeSupplierIfConcurrentModificationException( + sphereException, firstSupplier, secondSupplier); + assertThat(result).contains("SecondSupplier"); + } } diff --git a/src/test/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjectsTest.java b/src/test/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjectsTest.java index c71094c651..d0aa2e68d6 100644 --- a/src/test/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjectsTest.java +++ b/src/test/java/com/commercetools/sync/commons/CleanupUnresolvedReferenceCustomObjectsTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.commons; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.models.FetchCustomObjectsGraphQlRequest; import com.commercetools.sync.commons.models.ResourceKeyId; import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; @@ -9,98 +14,98 @@ import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.commands.CustomObjectDeleteCommand; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.concurrent.CompletableFuture; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CleanupUnresolvedReferenceCustomObjectsTest { - private final SphereClient mockClient = mock(SphereClient.class); - private static final int deleteDaysAfterLastModification = 30; - - @Test - void cleanup_withDeleteDaysAfterLastModification_ShouldDeleteAndReturnCleanupStatistics() { - final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); - when(resourceKeyIdGraphQlResult.getResults()).thenReturn(new HashSet<>(Arrays.asList( - new ResourceKeyId("coKey1", "coId1"), - new ResourceKeyId("coKey2", "coId2")))); - - when(mockClient.execute(any(FetchCustomObjectsGraphQlRequest.class))) - .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); - - final Throwable badRequestException = new BadRequestException("key is not valid"); - when(mockClient.execute(any(CustomObjectDeleteCommand.class))) - .thenReturn(CompletableFutureUtils.failed(badRequestException)) - .thenReturn(CompletableFuture.completedFuture(mock(CustomObject.class))); - - final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = - CleanupUnresolvedReferenceCustomObjects.of(mockClient).cleanup(deleteDaysAfterLastModification) - .join(); - - assertThat(statistics.getTotalDeleted()).isEqualTo(3); - assertThat(statistics.getTotalFailed()).isEqualTo(1); - assertThat(statistics.getReportMessage()) - .isEqualTo("Summary: 3 custom objects were deleted in total (1 failed to delete)."); - } - - @Test - void cleanup_withNotFound404Exception_ShouldNotIncrementFailedCounter() { - final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); - when(resourceKeyIdGraphQlResult.getResults()) - .thenReturn(Collections.singleton(new ResourceKeyId("coKey1", "coId1"))); - - when(mockClient.execute(any(FetchCustomObjectsGraphQlRequest.class))) - .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); - - when(mockClient.execute(any(CustomObjectDeleteCommand.class))) - .thenReturn(CompletableFuture.completedFuture(mock(CustomObject.class))) - .thenReturn(CompletableFutureUtils.failed(new NotFoundException())); - - final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = - CleanupUnresolvedReferenceCustomObjects.of(mockClient).cleanup(deleteDaysAfterLastModification) - .join(); - - assertThat(statistics.getTotalDeleted()).isEqualTo(1); - assertThat(statistics.getTotalFailed()).isEqualTo(0); - assertThat(statistics.getReportMessage()) - .isEqualTo("Summary: 1 custom objects were deleted in total (0 failed to delete)."); - } - - @Test - void cleanup_withBadRequest400Exception_ShouldIncrementFailedCounterAndTriggerErrorCallback() { - final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); - when(resourceKeyIdGraphQlResult.getResults()) - .thenReturn(Collections.singleton(new ResourceKeyId("coKey1", "coId1"))); - - when(mockClient.execute(any(FetchCustomObjectsGraphQlRequest.class))) - .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); - - final Throwable badRequestException = new BadRequestException("key is not valid"); - when(mockClient.execute(any(CustomObjectDeleteCommand.class))) - .thenReturn(CompletableFuture.completedFuture(mock(CustomObject.class))) - .thenReturn(CompletableFutureUtils.failed(badRequestException)); - - final List exceptions = new ArrayList<>(); - final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = - CleanupUnresolvedReferenceCustomObjects.of(mockClient) - .errorCallback(exceptions::add) - .cleanup(deleteDaysAfterLastModification) - .join(); - - assertThat(statistics.getTotalDeleted()).isEqualTo(1); - assertThat(statistics.getTotalFailed()).isEqualTo(1); - assertThat(exceptions).contains(badRequestException); - assertThat(statistics.getReportMessage()) - .isEqualTo("Summary: 1 custom objects were deleted in total (1 failed to delete)."); - } - + private final SphereClient mockClient = mock(SphereClient.class); + private static final int deleteDaysAfterLastModification = 30; + + @Test + void cleanup_withDeleteDaysAfterLastModification_ShouldDeleteAndReturnCleanupStatistics() { + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = + mock(ResourceKeyIdGraphQlResult.class); + when(resourceKeyIdGraphQlResult.getResults()) + .thenReturn( + new HashSet<>( + Arrays.asList( + new ResourceKeyId("coKey1", "coId1"), new ResourceKeyId("coKey2", "coId2")))); + + when(mockClient.execute(any(FetchCustomObjectsGraphQlRequest.class))) + .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); + + final Throwable badRequestException = new BadRequestException("key is not valid"); + when(mockClient.execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(CompletableFutureUtils.failed(badRequestException)) + .thenReturn(CompletableFuture.completedFuture(mock(CustomObject.class))); + + final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = + CleanupUnresolvedReferenceCustomObjects.of(mockClient) + .cleanup(deleteDaysAfterLastModification) + .join(); + + assertThat(statistics.getTotalDeleted()).isEqualTo(3); + assertThat(statistics.getTotalFailed()).isEqualTo(1); + assertThat(statistics.getReportMessage()) + .isEqualTo("Summary: 3 custom objects were deleted in total (1 failed to delete)."); + } + + @Test + void cleanup_withNotFound404Exception_ShouldNotIncrementFailedCounter() { + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = + mock(ResourceKeyIdGraphQlResult.class); + when(resourceKeyIdGraphQlResult.getResults()) + .thenReturn(Collections.singleton(new ResourceKeyId("coKey1", "coId1"))); + + when(mockClient.execute(any(FetchCustomObjectsGraphQlRequest.class))) + .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); + + when(mockClient.execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(CompletableFuture.completedFuture(mock(CustomObject.class))) + .thenReturn(CompletableFutureUtils.failed(new NotFoundException())); + + final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = + CleanupUnresolvedReferenceCustomObjects.of(mockClient) + .cleanup(deleteDaysAfterLastModification) + .join(); + + assertThat(statistics.getTotalDeleted()).isEqualTo(1); + assertThat(statistics.getTotalFailed()).isEqualTo(0); + assertThat(statistics.getReportMessage()) + .isEqualTo("Summary: 1 custom objects were deleted in total (0 failed to delete)."); + } + + @Test + void cleanup_withBadRequest400Exception_ShouldIncrementFailedCounterAndTriggerErrorCallback() { + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = + mock(ResourceKeyIdGraphQlResult.class); + when(resourceKeyIdGraphQlResult.getResults()) + .thenReturn(Collections.singleton(new ResourceKeyId("coKey1", "coId1"))); + + when(mockClient.execute(any(FetchCustomObjectsGraphQlRequest.class))) + .thenReturn(CompletableFuture.completedFuture(resourceKeyIdGraphQlResult)); + + final Throwable badRequestException = new BadRequestException("key is not valid"); + when(mockClient.execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(CompletableFuture.completedFuture(mock(CustomObject.class))) + .thenReturn(CompletableFutureUtils.failed(badRequestException)); + + final List exceptions = new ArrayList<>(); + final CleanupUnresolvedReferenceCustomObjects.Statistics statistics = + CleanupUnresolvedReferenceCustomObjects.of(mockClient) + .errorCallback(exceptions::add) + .cleanup(deleteDaysAfterLastModification) + .join(); + + assertThat(statistics.getTotalDeleted()).isEqualTo(1); + assertThat(statistics.getTotalFailed()).isEqualTo(1); + assertThat(exceptions).contains(badRequestException); + assertThat(statistics.getReportMessage()) + .isEqualTo("Summary: 1 custom objects were deleted in total (1 failed to delete)."); + } } diff --git a/src/test/java/com/commercetools/sync/commons/MockUtils.java b/src/test/java/com/commercetools/sync/commons/MockUtils.java index 27840e33c1..53157f3a5e 100644 --- a/src/test/java/com/commercetools/sync/commons/MockUtils.java +++ b/src/test/java/com/commercetools/sync/commons/MockUtils.java @@ -1,5 +1,13 @@ package com.commercetools.sync.commons; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.services.CategoryService; import com.commercetools.sync.services.CustomerService; import com.commercetools.sync.services.TypeService; @@ -13,9 +21,6 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -23,160 +28,163 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; - -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class MockUtils { - /** - * Creates a mock instance of {@link CustomFieldsDraft} with the key 'StepCategoryTypeKey' and two custom fields - * 'invisibleInShop' and'backgroundColor'. - * - *

The 'invisibleInShop' field is of type {@code boolean} and has value {@code false} and the - * the 'backgroundColor' field is of type {@code localisedString} and has the values {"de": "rot", "en": "red"} - * - * @return a mock instance of {@link CustomFieldsDraft} with some hardcoded custom fields and key. - */ - public static CustomFieldsDraft getMockCustomFieldsDraft() { - final Map customFieldsJsons = new HashMap<>(); - customFieldsJsons.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - customFieldsJsons - .put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); - return CustomFieldsDraft.ofTypeIdAndJson("StepCategoryTypeId", customFieldsJsons); - } + /** + * Creates a mock instance of {@link CustomFieldsDraft} with the key 'StepCategoryTypeKey' and two + * custom fields 'invisibleInShop' and'backgroundColor'. + * + *

The 'invisibleInShop' field is of type {@code boolean} and has value {@code false} and the + * the 'backgroundColor' field is of type {@code localisedString} and has the values {"de": "rot", + * "en": "red"} + * + * @return a mock instance of {@link CustomFieldsDraft} with some hardcoded custom fields and key. + */ + public static CustomFieldsDraft getMockCustomFieldsDraft() { + final Map customFieldsJsons = new HashMap<>(); + customFieldsJsons.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + customFieldsJsons.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); + return CustomFieldsDraft.ofTypeIdAndJson("StepCategoryTypeId", customFieldsJsons); + } - /** - * Returns mock {@link CustomFields} instance. Executing {@link CustomFields#getType()} on returned instance will - * return {@link Reference} of given {@code typeId} with mock {@link Type} instance of {@code typeId} and {@code - * typeKey} (getters of key and id would return given values). Executing {@link CustomFields#getFieldsJsonMap()} on - * returned instance will return {@link Map} populated with given {@code fieldName} and {@code fieldValue} - * - * @param typeId custom type id - * @param fieldName custom field name - * @param fieldValue custom field value - * @return mock instance of {@link CustomFields} - */ - public static CustomFields getMockCustomFields(final String typeId, final String fieldName, - final JsonNode fieldValue) { - final CustomFields customFields = mock(CustomFields.class); - final Type type = mock(Type.class); - when(type.getId()).thenReturn(typeId); - when(customFields.getFieldsJsonMap()).thenReturn(mockFields(fieldName, fieldValue)); - when(customFields.getType()).thenReturn(Type.referenceOfId(typeId).filled(type)); - return customFields; - } + /** + * Returns mock {@link CustomFields} instance. Executing {@link CustomFields#getType()} on + * returned instance will return {@link Reference} of given {@code typeId} with mock {@link Type} + * instance of {@code typeId} and {@code typeKey} (getters of key and id would return given + * values). Executing {@link CustomFields#getFieldsJsonMap()} on returned instance will return + * {@link Map} populated with given {@code fieldName} and {@code fieldValue} + * + * @param typeId custom type id + * @param fieldName custom field name + * @param fieldValue custom field value + * @return mock instance of {@link CustomFields} + */ + public static CustomFields getMockCustomFields( + final String typeId, final String fieldName, final JsonNode fieldValue) { + final CustomFields customFields = mock(CustomFields.class); + final Type type = mock(Type.class); + when(type.getId()).thenReturn(typeId); + when(customFields.getFieldsJsonMap()).thenReturn(mockFields(fieldName, fieldValue)); + when(customFields.getType()).thenReturn(Type.referenceOfId(typeId).filled(type)); + return customFields; + } - private static Map mockFields(final String name, final JsonNode value) { - final HashMap fields = new HashMap<>(); - fields.put(name, value); - return fields; - } + private static Map mockFields(final String name, final JsonNode value) { + final HashMap fields = new HashMap<>(); + fields.put(name, value); + return fields; + } - public static CategoryService mockCategoryService(@Nonnull final Set existingCategories, - @Nullable final Category createdCategory) { - final CategoryService mockCategoryService = mock(CategoryService.class); - when(mockCategoryService.fetchMatchingCategoriesByKeys(any())) - .thenReturn(CompletableFuture.completedFuture(existingCategories)); + public static CategoryService mockCategoryService( + @Nonnull final Set existingCategories, @Nullable final Category createdCategory) { + final CategoryService mockCategoryService = mock(CategoryService.class); + when(mockCategoryService.fetchMatchingCategoriesByKeys(any())) + .thenReturn(CompletableFuture.completedFuture(existingCategories)); - final Map keyToIds = - existingCategories.stream().collect(Collectors.toMap(Category::getKey, Category::getId)); - when(mockCategoryService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); - when(mockCategoryService.createCategory(any())) - .thenReturn(CompletableFuture.completedFuture(ofNullable(createdCategory))); - return mockCategoryService; - } + final Map keyToIds = + existingCategories.stream().collect(Collectors.toMap(Category::getKey, Category::getId)); + when(mockCategoryService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); + when(mockCategoryService.createCategory(any())) + .thenReturn(CompletableFuture.completedFuture(ofNullable(createdCategory))); + return mockCategoryService; + } - public static CategoryService mockCategoryService(@Nonnull final Set existingCategories, - @Nullable final Category createdCategory, - @Nonnull final Category updatedCategory) { - final CategoryService mockCategoryService = mockCategoryService(existingCategories, createdCategory); - when(mockCategoryService.updateCategory(any(), any())).thenReturn(completedFuture(updatedCategory)); - return mockCategoryService; - } + public static CategoryService mockCategoryService( + @Nonnull final Set existingCategories, + @Nullable final Category createdCategory, + @Nonnull final Category updatedCategory) { + final CategoryService mockCategoryService = + mockCategoryService(existingCategories, createdCategory); + when(mockCategoryService.updateCategory(any(), any())) + .thenReturn(completedFuture(updatedCategory)); + return mockCategoryService; + } - /** - * Creates a mock {@link TypeService} that returns a dummy type id of value "typeId" instance whenever the - * following method is called on the service: - *

    - *
  • {@link TypeService#fetchCachedTypeId(String)}
  • - *
- * - * @return the created mock of the {@link TypeService}. - */ - public static TypeService getMockTypeService() { - final TypeService typeService = mock(TypeService.class); - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(completedFuture(Optional.of("typeId"))); - when(typeService.cacheKeysToIds(anySet())) - .thenReturn(completedFuture(Collections.singletonMap("typeKey", "typeId"))); - return typeService; - } + /** + * Creates a mock {@link TypeService} that returns a dummy type id of value "typeId" instance + * whenever the following method is called on the service: + * + *
    + *
  • {@link TypeService#fetchCachedTypeId(String)} + *
+ * + * @return the created mock of the {@link TypeService}. + */ + public static TypeService getMockTypeService() { + final TypeService typeService = mock(TypeService.class); + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(completedFuture(Optional.of("typeId"))); + when(typeService.cacheKeysToIds(anySet())) + .thenReturn(completedFuture(Collections.singletonMap("typeKey", "typeId"))); + return typeService; + } - /** - * Creates a mock {@link Type} with the supplied {@code id} and {@code key}. - * @param id the id of the type mock. - * @param key the key of the type mock. - * @return a mock product variant with the supplied prices and assets. - */ - @Nonnull - public static Type getTypeMock(@Nonnull final String id, @Nonnull final String key) { - final Type mockCustomType = mock(Type.class); - when(mockCustomType.getId()).thenReturn(id); - when(mockCustomType.getKey()).thenReturn(key); - return mockCustomType; - } + /** + * Creates a mock {@link Type} with the supplied {@code id} and {@code key}. + * + * @param id the id of the type mock. + * @param key the key of the type mock. + * @return a mock product variant with the supplied prices and assets. + */ + @Nonnull + public static Type getTypeMock(@Nonnull final String id, @Nonnull final String key) { + final Type mockCustomType = mock(Type.class); + when(mockCustomType.getId()).thenReturn(id); + when(mockCustomType.getKey()).thenReturn(key); + return mockCustomType; + } - /** - * Creates a mock {@link Asset} with the supplied {@link Type} reference for it's custom field. - * @param typeReference the type reference to attach to the custom field of the created asset. - * @return a mock asset with the supplied type reference on it's custom field. - */ - @Nonnull - public static Asset getAssetMockWithCustomFields(@Nullable final Reference typeReference) { - // Mock Custom with expanded type reference - final CustomFields mockCustomFields = mock(CustomFields.class); - when(mockCustomFields.getType()).thenReturn(typeReference); + /** + * Creates a mock {@link Asset} with the supplied {@link Type} reference for it's custom field. + * + * @param typeReference the type reference to attach to the custom field of the created asset. + * @return a mock asset with the supplied type reference on it's custom field. + */ + @Nonnull + public static Asset getAssetMockWithCustomFields(@Nullable final Reference typeReference) { + // Mock Custom with expanded type reference + final CustomFields mockCustomFields = mock(CustomFields.class); + when(mockCustomFields.getType()).thenReturn(typeReference); - // Mock asset with custom fields - final Asset asset = mock(Asset.class); - when(asset.getCustom()).thenReturn(mockCustomFields); - return asset; - } + // Mock asset with custom fields + final Asset asset = mock(Asset.class); + when(asset.getCustom()).thenReturn(mockCustomFields); + return asset; + } - /** - * Creates a mock {@link CustomerService} that returns a dummy customer id of value "customerId" instance - * whenever the following method is called on the service: - *
    - *
  • {@link CustomerService#fetchCachedCustomerId(String)}
  • - *
- * - * @return the created mock of the {@link CustomerService}. - */ - public static CustomerService getMockCustomerService() { - final CustomerService customerService = mock(CustomerService.class); - when(customerService.fetchCachedCustomerId(anyString())) - .thenReturn(completedFuture(Optional.of("customerId"))); - when(customerService.cacheKeysToIds(anySet())) - .thenReturn(completedFuture(Collections.singletonMap("customerKey", "customerId"))); - return customerService; - } + /** + * Creates a mock {@link CustomerService} that returns a dummy customer id of value "customerId" + * instance whenever the following method is called on the service: + * + *
    + *
  • {@link CustomerService#fetchCachedCustomerId(String)} + *
+ * + * @return the created mock of the {@link CustomerService}. + */ + public static CustomerService getMockCustomerService() { + final CustomerService customerService = mock(CustomerService.class); + when(customerService.fetchCachedCustomerId(anyString())) + .thenReturn(completedFuture(Optional.of("customerId"))); + when(customerService.cacheKeysToIds(anySet())) + .thenReturn(completedFuture(Collections.singletonMap("customerKey", "customerId"))); + return customerService; + } - /** - * Creates a mock {@link Customer} with the supplied {@code id} and {@code key}. - * - * @param id the id of the created mock {@link Customer}. - * @param key the key of the created mock {@link CustomerGroup}. - * @return a mock customerGroup with the supplied id and key. - */ - public static Customer getMockCustomer(final String id, final String key) { - final Customer customer = mock(Customer.class); - when(customer.getId()).thenReturn(id); - when(customer.getKey()).thenReturn(key); - return customer; - } + /** + * Creates a mock {@link Customer} with the supplied {@code id} and {@code key}. + * + * @param id the id of the created mock {@link Customer}. + * @param key the key of the created mock {@link CustomerGroup}. + * @return a mock customerGroup with the supplied id and key. + */ + public static Customer getMockCustomer(final String id, final String key) { + final Customer customer = mock(Customer.class); + when(customer.getId()).thenReturn(id); + when(customer.getKey()).thenReturn(key); + return customer; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractSetCustomFieldAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractSetCustomFieldAssert.java index b6448f8588..3363450cd3 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractSetCustomFieldAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractSetCustomFieldAssert.java @@ -1,39 +1,39 @@ package com.commercetools.sync.commons.asserts.actions; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.types.customupdateactions.SetCustomFieldBase; - import javax.annotation.Nonnull; import javax.annotation.Nullable; -import static org.assertj.core.api.Assertions.assertThat; - -public class AbstractSetCustomFieldAssert, S extends SetCustomFieldBase> +public class AbstractSetCustomFieldAssert< + T extends AbstractSetCustomFieldAssert, S extends SetCustomFieldBase> extends AbstractUpdateActionAssert { - - AbstractSetCustomFieldAssert(@Nullable final S actual, @Nonnull final Class selfType) { - super(actual, selfType); - } - - /** - * Verifies that the actual {@link SetCustomFieldBase} value has identical fields as the ones supplied. - * - * @param actionName the update action name. - * @param customFieldName the custom field name to update. - * @param customFieldValue the new custom field name to update. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual fields do not match the supplied values. - */ - public AbstractSetCustomFieldAssert hasValues( - @Nonnull final String actionName, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - - super.hasValues(actionName); - assertThat(actual.getName()).isEqualTo(customFieldName); - assertThat(actual.getValue()).isEqualTo(customFieldValue); - return myself; - } + AbstractSetCustomFieldAssert(@Nullable final S actual, @Nonnull final Class selfType) { + super(actual, selfType); + } + + /** + * Verifies that the actual {@link SetCustomFieldBase} value has identical fields as the ones + * supplied. + * + * @param actionName the update action name. + * @param customFieldName the custom field name to update. + * @param customFieldValue the new custom field name to update. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual fields do not match the supplied values. + */ + public AbstractSetCustomFieldAssert hasValues( + @Nonnull final String actionName, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + + super.hasValues(actionName); + assertThat(actual.getName()).isEqualTo(customFieldName); + assertThat(actual.getValue()).isEqualTo(customFieldValue); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractSetCustomTypeAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractSetCustomTypeAssert.java index 2b9bf05ed3..530e47e94f 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractSetCustomTypeAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractSetCustomTypeAssert.java @@ -1,61 +1,62 @@ package com.commercetools.sync.commons.asserts.actions; +import static java.util.Optional.ofNullable; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.customupdateactions.SetCustomTypeBase; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; -import static java.util.Optional.ofNullable; -import static org.assertj.core.api.Assertions.assertThat; - -public class AbstractSetCustomTypeAssert, - S extends SetCustomTypeBase> extends AbstractUpdateActionAssert { - - AbstractSetCustomTypeAssert(@Nullable final S actual, @Nonnull final Class selfType) { - super(actual, selfType); - } - - /** - * Verifies that the actual {@link SetCustomTypeBase} value has identical fields as the ones supplied. - * TODO: GITHUB ISSUE#257 - * - * @param actionName the update action name. - * @param customFields the new custom type fields. - * @param type the new custom type. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual fields do not match the supplied values. - */ - @SuppressWarnings("unchecked") - public AbstractSetCustomTypeAssert hasValues( - @Nonnull final String actionName, - @Nullable final Map customFields, - @Nullable final ResourceIdentifier type) { - - super.hasValues(actionName); - - assertThat(actual.getFields()).isEqualTo(customFields); - return ofNullable(type) - .map(customType -> hasTypeValues(customType.getId(), customType.getKey(), customType.getTypeId())) - .orElseGet(() -> { - assertThat(actual.getType()).isNull(); - return myself; +public class AbstractSetCustomTypeAssert< + T extends AbstractSetCustomTypeAssert, S extends SetCustomTypeBase> + extends AbstractUpdateActionAssert { + + AbstractSetCustomTypeAssert(@Nullable final S actual, @Nonnull final Class selfType) { + super(actual, selfType); + } + + /** + * Verifies that the actual {@link SetCustomTypeBase} value has identical fields as the ones + * supplied. TODO: GITHUB ISSUE#257 + * + * @param actionName the update action name. + * @param customFields the new custom type fields. + * @param type the new custom type. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual fields do not match the supplied values. + */ + @SuppressWarnings("unchecked") + public AbstractSetCustomTypeAssert hasValues( + @Nonnull final String actionName, + @Nullable final Map customFields, + @Nullable final ResourceIdentifier type) { + + super.hasValues(actionName); + + assertThat(actual.getFields()).isEqualTo(customFields); + return ofNullable(type) + .map( + customType -> + hasTypeValues(customType.getId(), customType.getKey(), customType.getTypeId())) + .orElseGet( + () -> { + assertThat(actual.getType()).isNull(); + return myself; }); - } - - private AbstractSetCustomTypeAssert hasTypeValues( - @Nullable final String id, - @Nullable final String key, - @Nullable final String typeId) { - - assertThat(actual.getType()).isNotNull(); - assertThat(actual.getType().getId()).isEqualTo(id); - assertThat(actual.getType().getKey()).isEqualTo(key); - assertThat(actual.getType().getTypeId()).isEqualTo(typeId); - return myself; - } + } + + private AbstractSetCustomTypeAssert hasTypeValues( + @Nullable final String id, @Nullable final String key, @Nullable final String typeId) { + + assertThat(actual.getType()).isNotNull(); + assertThat(actual.getType().getId()).isEqualTo(id); + assertThat(actual.getType().getKey()).isEqualTo(key); + assertThat(actual.getType().getTypeId()).isEqualTo(typeId); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractUpdateActionAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractUpdateActionAssert.java index 6afa9d6e07..94efe8eb0d 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractUpdateActionAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/AbstractUpdateActionAssert.java @@ -1,32 +1,31 @@ package com.commercetools.sync.commons.asserts.actions; -import io.sphere.sdk.commands.UpdateAction; -import org.assertj.core.api.AbstractAssert; +import static org.assertj.core.api.Assertions.assertThat; +import io.sphere.sdk.commands.UpdateAction; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.assertj.core.api.AbstractAssert; -import static org.assertj.core.api.Assertions.assertThat; - -public class AbstractUpdateActionAssert, A extends UpdateAction> +public class AbstractUpdateActionAssert< + S extends AbstractUpdateActionAssert, A extends UpdateAction> extends AbstractAssert { + AbstractUpdateActionAssert(@Nullable final A actual, @Nonnull final Class selfType) { + super(actual, selfType); + } - AbstractUpdateActionAssert(@Nullable final A actual, @Nonnull final Class selfType) { - super(actual, selfType); - } - - /** - * Verifies that the actual {@link UpdateAction} value has identical fields as the ones supplied. - * - * @param actionName the update action name. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual fields do not match the supplied values. - */ - public S hasValues(final String actionName) { - assertThat(actual).isNotNull(); - assertThat(actual.getAction()).isEqualTo(actionName); - return myself; - } + /** + * Verifies that the actual {@link UpdateAction} value has identical fields as the ones supplied. + * + * @param actionName the update action name. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual fields do not match the supplied values. + */ + public S hasValues(final String actionName) { + assertThat(actual).isNotNull(); + assertThat(actual.getAction()).isEqualTo(actionName); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/AssertionsForUpdateActions.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/AssertionsForUpdateActions.java index 84fcd87a3c..641774f950 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/AssertionsForUpdateActions.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/AssertionsForUpdateActions.java @@ -4,103 +4,110 @@ import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomType; import io.sphere.sdk.types.customupdateactions.SetCustomFieldBase; import io.sphere.sdk.types.customupdateactions.SetCustomTypeBase; - import javax.annotation.Nonnull; import javax.annotation.Nullable; public final class AssertionsForUpdateActions { - private AssertionsForUpdateActions() { - } + private AssertionsForUpdateActions() {} - /** - * Create assertion for {@link SetCustomTypeBase}. - * - * @param updateAction the actual value. - * @return the created assertion object. - */ - @Nonnull - public static SetCustomTypeAssert assertThat(@Nullable final SetCustomTypeBase updateAction) { - return new SetCustomTypeAssert(updateAction); - } + /** + * Create assertion for {@link SetCustomTypeBase}. + * + * @param updateAction the actual value. + * @return the created assertion object. + */ + @Nonnull + public static SetCustomTypeAssert assertThat(@Nullable final SetCustomTypeBase updateAction) { + return new SetCustomTypeAssert(updateAction); + } - /** - * Create assertion for {@link SetCustomFieldBase}. - * - * @param updateAction the actual value. - * @return the created assertion object. - */ - @Nonnull - public static AbstractSetCustomFieldAssert assertThat(@Nullable final SetCustomFieldBase updateAction) { - return new SetCustomFieldAssert(updateAction); - } + /** + * Create assertion for {@link SetCustomFieldBase}. + * + * @param updateAction the actual value. + * @return the created assertion object. + */ + @Nonnull + public static AbstractSetCustomFieldAssert assertThat( + @Nullable final SetCustomFieldBase updateAction) { + return new SetCustomFieldAssert(updateAction); + } - /** - * Create assertion for {@link io.sphere.sdk.categories.commands.updateactions.SetAssetCustomType}. - * - * @param updateAction the actual value. - * @return the created assertion object. - */ - @Nonnull - public static CategorySetAssetCustomTypeAssert assertThat( - @Nullable final io.sphere.sdk.categories.commands.updateactions.SetAssetCustomType updateAction) { - return new CategorySetAssetCustomTypeAssert(updateAction); - } + /** + * Create assertion for {@link + * io.sphere.sdk.categories.commands.updateactions.SetAssetCustomType}. + * + * @param updateAction the actual value. + * @return the created assertion object. + */ + @Nonnull + public static CategorySetAssetCustomTypeAssert assertThat( + @Nullable + final io.sphere.sdk.categories.commands.updateactions.SetAssetCustomType updateAction) { + return new CategorySetAssetCustomTypeAssert(updateAction); + } - /** - * Create assertion for {@link io.sphere.sdk.categories.commands.updateactions.SetAssetCustomField}. - * - * @param updateAction the actual value. - * @return the created assertion object. - */ - @Nonnull - public static CategorySetAssetCustomFieldAssert assertThat( - @Nullable final io.sphere.sdk.categories.commands.updateactions.SetAssetCustomField updateAction) { - return new CategorySetAssetCustomFieldAssert(updateAction); - } + /** + * Create assertion for {@link + * io.sphere.sdk.categories.commands.updateactions.SetAssetCustomField}. + * + * @param updateAction the actual value. + * @return the created assertion object. + */ + @Nonnull + public static CategorySetAssetCustomFieldAssert assertThat( + @Nullable + final io.sphere.sdk.categories.commands.updateactions.SetAssetCustomField updateAction) { + return new CategorySetAssetCustomFieldAssert(updateAction); + } - /** - * Create assertion for {@link io.sphere.sdk.products.commands.updateactions.SetAssetCustomType}. - * - * @param updateAction the actual value. - * @return the created assertion object. - */ - @Nonnull - public static ProductSetAssetCustomTypeAssert assertThat( - @Nullable final io.sphere.sdk.products.commands.updateactions.SetAssetCustomType updateAction) { - return new ProductSetAssetCustomTypeAssert(updateAction); - } + /** + * Create assertion for {@link io.sphere.sdk.products.commands.updateactions.SetAssetCustomType}. + * + * @param updateAction the actual value. + * @return the created assertion object. + */ + @Nonnull + public static ProductSetAssetCustomTypeAssert assertThat( + @Nullable + final io.sphere.sdk.products.commands.updateactions.SetAssetCustomType updateAction) { + return new ProductSetAssetCustomTypeAssert(updateAction); + } - /** - * Create assertion for {@link io.sphere.sdk.products.commands.updateactions.SetAssetCustomField}. - * - * @param updateAction the actual value. - * @return the created assertion object. - */ - @Nonnull - public static ProductSetAssetCustomFieldAssert assertThat( - @Nullable final io.sphere.sdk.products.commands.updateactions.SetAssetCustomField updateAction) { - return new ProductSetAssetCustomFieldAssert(updateAction); - } + /** + * Create assertion for {@link io.sphere.sdk.products.commands.updateactions.SetAssetCustomField}. + * + * @param updateAction the actual value. + * @return the created assertion object. + */ + @Nonnull + public static ProductSetAssetCustomFieldAssert assertThat( + @Nullable + final io.sphere.sdk.products.commands.updateactions.SetAssetCustomField updateAction) { + return new ProductSetAssetCustomFieldAssert(updateAction); + } - /** - * Create assertion for {@link SetProductPriceCustomType}. - * - * @param updateAction the actual value. - * @return the created assertion object. - */ - @Nonnull - public static SetPriceCustomTypeAssert assertThat(@Nullable final SetProductPriceCustomType updateAction) { - return new SetPriceCustomTypeAssert(updateAction); - } + /** + * Create assertion for {@link SetProductPriceCustomType}. + * + * @param updateAction the actual value. + * @return the created assertion object. + */ + @Nonnull + public static SetPriceCustomTypeAssert assertThat( + @Nullable final SetProductPriceCustomType updateAction) { + return new SetPriceCustomTypeAssert(updateAction); + } - /** - * Create assertion for {@link SetProductPriceCustomField}. - * - * @param updateAction the actual value. - * @return the created assertion object. - */ - @Nonnull - public static SetPriceCustomFieldAssert assertThat(@Nullable final SetProductPriceCustomField updateAction) { - return new SetPriceCustomFieldAssert(updateAction); - } + /** + * Create assertion for {@link SetProductPriceCustomField}. + * + * @param updateAction the actual value. + * @return the created assertion object. + */ + @Nonnull + public static SetPriceCustomFieldAssert assertThat( + @Nullable final SetProductPriceCustomField updateAction) { + return new SetPriceCustomFieldAssert(updateAction); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/CategorySetAssetCustomFieldAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/CategorySetAssetCustomFieldAssert.java index 73f8b7c088..bfa1bca019 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/CategorySetAssetCustomFieldAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/CategorySetAssetCustomFieldAssert.java @@ -1,43 +1,42 @@ package com.commercetools.sync.commons.asserts.actions; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.categories.commands.updateactions.SetAssetCustomField; - import javax.annotation.Nonnull; import javax.annotation.Nullable; -import static org.assertj.core.api.Assertions.assertThat; - - public class CategorySetAssetCustomFieldAssert extends AbstractSetCustomFieldAssert { - CategorySetAssetCustomFieldAssert(@Nullable final SetAssetCustomField actual) { - super(actual, CategorySetAssetCustomFieldAssert.class); - } - - /** - * Verifies that the actual {@link SetAssetCustomField} value has identical fields as the ones supplied. - * - * @param actionName the update action name. - * @param assetId the asset Id the action is performed on. - * @param assetKey the asset key the action is performed on. - * @param customFieldName the custom field name to update. - * @param customFieldValue the new custom field name to update. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual fields do not match the supplied values. - */ - public CategorySetAssetCustomFieldAssert hasValues( - @Nonnull final String actionName, - @Nullable final String assetId, - @Nullable final String assetKey, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - - super.hasValues(actionName, customFieldName, customFieldValue); - assertThat(actual.getAssetId()).isEqualTo(assetId); - assertThat(actual.getAssetKey()).isEqualTo(assetKey); - return myself; - } + CategorySetAssetCustomFieldAssert(@Nullable final SetAssetCustomField actual) { + super(actual, CategorySetAssetCustomFieldAssert.class); + } + + /** + * Verifies that the actual {@link SetAssetCustomField} value has identical fields as the ones + * supplied. + * + * @param actionName the update action name. + * @param assetId the asset Id the action is performed on. + * @param assetKey the asset key the action is performed on. + * @param customFieldName the custom field name to update. + * @param customFieldValue the new custom field name to update. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual fields do not match the supplied values. + */ + public CategorySetAssetCustomFieldAssert hasValues( + @Nonnull final String actionName, + @Nullable final String assetId, + @Nullable final String assetKey, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + + super.hasValues(actionName, customFieldName, customFieldValue); + assertThat(actual.getAssetId()).isEqualTo(assetId); + assertThat(actual.getAssetKey()).isEqualTo(assetKey); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/CategorySetAssetCustomTypeAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/CategorySetAssetCustomTypeAssert.java index 312f94586b..e4f6d193b6 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/CategorySetAssetCustomTypeAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/CategorySetAssetCustomTypeAssert.java @@ -1,46 +1,45 @@ package com.commercetools.sync.commons.asserts.actions; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.categories.commands.updateactions.SetAssetCustomType; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.types.Type; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - public class CategorySetAssetCustomTypeAssert extends AbstractSetCustomTypeAssert { - CategorySetAssetCustomTypeAssert(@Nullable final SetAssetCustomType actual) { - super(actual, CategorySetAssetCustomTypeAssert.class); - } - - /** - * Verifies that the actual {@link SetAssetCustomType} value has identical fields as the ones supplied. - * - * @param actionName the update action name. - * @param assetId the asset Id the action is performed on. - * @param assetKey the asset key the action is performed on. - * @param customFields the new custom type fields. - * @param type the new custom type. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual fields do not match the supplied values. - */ - public CategorySetAssetCustomTypeAssert hasValues( - @Nonnull final String actionName, - @Nullable final String assetId, - @Nullable final String assetKey, - @Nullable final Map customFields, - @Nullable final ResourceIdentifier type) { - - super.hasValues(actionName, customFields, type); - assertThat(actual.getAssetId()).isEqualTo(assetId); - assertThat(actual.getAssetKey()).isEqualTo(assetKey); - return myself; - } + CategorySetAssetCustomTypeAssert(@Nullable final SetAssetCustomType actual) { + super(actual, CategorySetAssetCustomTypeAssert.class); + } + + /** + * Verifies that the actual {@link SetAssetCustomType} value has identical fields as the ones + * supplied. + * + * @param actionName the update action name. + * @param assetId the asset Id the action is performed on. + * @param assetKey the asset key the action is performed on. + * @param customFields the new custom type fields. + * @param type the new custom type. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual fields do not match the supplied values. + */ + public CategorySetAssetCustomTypeAssert hasValues( + @Nonnull final String actionName, + @Nullable final String assetId, + @Nullable final String assetKey, + @Nullable final Map customFields, + @Nullable final ResourceIdentifier type) { + + super.hasValues(actionName, customFields, type); + assertThat(actual.getAssetId()).isEqualTo(assetId); + assertThat(actual.getAssetKey()).isEqualTo(assetKey); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/ProductSetAssetCustomFieldAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/ProductSetAssetCustomFieldAssert.java index 74705e8348..05d81b37c5 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/ProductSetAssetCustomFieldAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/ProductSetAssetCustomFieldAssert.java @@ -1,50 +1,49 @@ package com.commercetools.sync.commons.asserts.actions; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.products.commands.updateactions.SetAssetCustomField; - import javax.annotation.Nonnull; import javax.annotation.Nullable; -import static org.assertj.core.api.Assertions.assertThat; - - public class ProductSetAssetCustomFieldAssert extends AbstractSetCustomFieldAssert { - ProductSetAssetCustomFieldAssert(@Nullable final SetAssetCustomField actual) { - super(actual, ProductSetAssetCustomFieldAssert.class); - } - - /** - * Verifies that the actual {@link SetAssetCustomField} value has identical fields as the ones supplied. - * - * @param actionName the update action name. - * @param assetId the asset Id the action is performed on. - * @param variantSku the variant sku that has the asset. - * @param variantId the variant id that has the asset. - * @param staged the staged flag of the action. - * @param customFieldName the custom field name to update. - * @param customFieldValue the new custom field name to update. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual fields do not match the supplied values. - */ - public ProductSetAssetCustomFieldAssert hasValues( - @Nonnull final String actionName, - @Nullable final String assetId, - @Nullable final String variantSku, - @Nullable final Integer variantId, - @Nullable final Boolean staged, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - - super.hasValues(actionName, customFieldName, customFieldValue); - - assertThat(actual.getAssetId()).isEqualTo(assetId); - assertThat(actual.getSku()).isEqualTo(variantSku); - assertThat(actual.getVariantId()).isEqualTo(variantId); - assertThat(actual.getStaged()).isEqualTo(staged); - return myself; - } + ProductSetAssetCustomFieldAssert(@Nullable final SetAssetCustomField actual) { + super(actual, ProductSetAssetCustomFieldAssert.class); + } + + /** + * Verifies that the actual {@link SetAssetCustomField} value has identical fields as the ones + * supplied. + * + * @param actionName the update action name. + * @param assetId the asset Id the action is performed on. + * @param variantSku the variant sku that has the asset. + * @param variantId the variant id that has the asset. + * @param staged the staged flag of the action. + * @param customFieldName the custom field name to update. + * @param customFieldValue the new custom field name to update. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual fields do not match the supplied values. + */ + public ProductSetAssetCustomFieldAssert hasValues( + @Nonnull final String actionName, + @Nullable final String assetId, + @Nullable final String variantSku, + @Nullable final Integer variantId, + @Nullable final Boolean staged, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + + super.hasValues(actionName, customFieldName, customFieldValue); + + assertThat(actual.getAssetId()).isEqualTo(assetId); + assertThat(actual.getSku()).isEqualTo(variantSku); + assertThat(actual.getVariantId()).isEqualTo(variantId); + assertThat(actual.getStaged()).isEqualTo(staged); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/ProductSetAssetCustomTypeAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/ProductSetAssetCustomTypeAssert.java index e8590ab7b3..cac7eacf8d 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/ProductSetAssetCustomTypeAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/ProductSetAssetCustomTypeAssert.java @@ -1,54 +1,53 @@ package com.commercetools.sync.commons.asserts.actions; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.commands.updateactions.SetAssetCustomType; import io.sphere.sdk.types.Type; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - public class ProductSetAssetCustomTypeAssert extends AbstractSetCustomTypeAssert { - ProductSetAssetCustomTypeAssert(@Nullable final SetAssetCustomType actual) { - super(actual, ProductSetAssetCustomTypeAssert.class); - } - - /** - * Verifies that the actual {@link io.sphere.sdk.products.commands.updateactions.SetAssetCustomType} value has - * identical fields as the ones supplied. - * - * @param actionName the update action name. - * @param assetId the asset Id the action is performed on. - * @param variantSku the variant sku that has the asset. - * @param variantId the variant id that has the asset. - * @param staged the staged flag of the action. - * @param customFields the new custom type fields. - * @param type the new custom type. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual fields do not match the supplied values. - */ - public ProductSetAssetCustomTypeAssert hasValues( - @Nonnull final String actionName, - @Nullable final String assetId, - @Nullable final String variantSku, - @Nullable final Integer variantId, - @Nullable final Boolean staged, - @Nullable final Map customFields, - @Nullable final ResourceIdentifier type) { - - super.hasValues(actionName, customFields, type); - - assertThat(actual.getAssetId()).isEqualTo(assetId); - assertThat(actual.getSku()).isEqualTo(variantSku); - assertThat(actual.getVariantId()).isEqualTo(variantId); - assertThat(actual.getStaged()).isEqualTo(staged); - return myself; - } + ProductSetAssetCustomTypeAssert(@Nullable final SetAssetCustomType actual) { + super(actual, ProductSetAssetCustomTypeAssert.class); + } + + /** + * Verifies that the actual {@link + * io.sphere.sdk.products.commands.updateactions.SetAssetCustomType} value has identical fields as + * the ones supplied. + * + * @param actionName the update action name. + * @param assetId the asset Id the action is performed on. + * @param variantSku the variant sku that has the asset. + * @param variantId the variant id that has the asset. + * @param staged the staged flag of the action. + * @param customFields the new custom type fields. + * @param type the new custom type. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual fields do not match the supplied values. + */ + public ProductSetAssetCustomTypeAssert hasValues( + @Nonnull final String actionName, + @Nullable final String assetId, + @Nullable final String variantSku, + @Nullable final Integer variantId, + @Nullable final Boolean staged, + @Nullable final Map customFields, + @Nullable final ResourceIdentifier type) { + + super.hasValues(actionName, customFields, type); + + assertThat(actual.getAssetId()).isEqualTo(assetId); + assertThat(actual.getSku()).isEqualTo(variantSku); + assertThat(actual.getVariantId()).isEqualTo(variantId); + assertThat(actual.getStaged()).isEqualTo(staged); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/SetCustomFieldAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/SetCustomFieldAssert.java index 6a8452e735..14514d37e1 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/SetCustomFieldAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/SetCustomFieldAssert.java @@ -1,14 +1,12 @@ package com.commercetools.sync.commons.asserts.actions; import io.sphere.sdk.types.customupdateactions.SetCustomFieldBase; - import javax.annotation.Nullable; - public final class SetCustomFieldAssert extends AbstractSetCustomFieldAssert { - SetCustomFieldAssert(@Nullable final SetCustomFieldBase actual) { - super(actual, SetCustomFieldAssert.class); - } + SetCustomFieldAssert(@Nullable final SetCustomFieldBase actual) { + super(actual, SetCustomFieldAssert.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/SetCustomTypeAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/SetCustomTypeAssert.java index b7127e81d0..dd4651c10a 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/SetCustomTypeAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/SetCustomTypeAssert.java @@ -1,13 +1,12 @@ package com.commercetools.sync.commons.asserts.actions; import io.sphere.sdk.types.customupdateactions.SetCustomTypeBase; - import javax.annotation.Nullable; public final class SetCustomTypeAssert extends AbstractSetCustomTypeAssert { - SetCustomTypeAssert(@Nullable final SetCustomTypeBase actual) { - super(actual, SetCustomTypeAssert.class); - } + SetCustomTypeAssert(@Nullable final SetCustomTypeBase actual) { + super(actual, SetCustomTypeAssert.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/SetPriceCustomFieldAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/SetPriceCustomFieldAssert.java index bb6b763dcd..3ea2265175 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/SetPriceCustomFieldAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/SetPriceCustomFieldAssert.java @@ -1,43 +1,42 @@ package com.commercetools.sync.commons.asserts.actions; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomField; - import javax.annotation.Nonnull; import javax.annotation.Nullable; -import static org.assertj.core.api.Assertions.assertThat; - - public class SetPriceCustomFieldAssert extends AbstractSetCustomFieldAssert { - SetPriceCustomFieldAssert(@Nullable final SetProductPriceCustomField actual) { - super(actual, SetPriceCustomFieldAssert.class); - } - - /** - * Verifies that the actual {@link SetProductPriceCustomField} value has identical fields as the ones supplied. - * - * @param actionName the update action name. - * @param priceId the price id that has the custom fields. - * @param staged the staged flag of the action. - * @param customFieldName the custom field name to update. - * @param customFieldValue the new custom field name to update. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual fields do not match the supplied values. - */ - public SetPriceCustomFieldAssert hasValues( - @Nonnull final String actionName, - @Nullable final Boolean staged, - @Nullable final String priceId, - @Nullable final String customFieldName, - @Nullable final JsonNode customFieldValue) { - - super.hasValues(actionName, customFieldName, customFieldValue); - assertThat(actual.getPriceId()).isEqualTo(priceId); - assertThat(actual.getStaged()).isEqualTo(staged); - return myself; - } + SetPriceCustomFieldAssert(@Nullable final SetProductPriceCustomField actual) { + super(actual, SetPriceCustomFieldAssert.class); + } + + /** + * Verifies that the actual {@link SetProductPriceCustomField} value has identical fields as the + * ones supplied. + * + * @param actionName the update action name. + * @param priceId the price id that has the custom fields. + * @param staged the staged flag of the action. + * @param customFieldName the custom field name to update. + * @param customFieldValue the new custom field name to update. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual fields do not match the supplied values. + */ + public SetPriceCustomFieldAssert hasValues( + @Nonnull final String actionName, + @Nullable final Boolean staged, + @Nullable final String priceId, + @Nullable final String customFieldName, + @Nullable final JsonNode customFieldValue) { + + super.hasValues(actionName, customFieldName, customFieldValue); + assertThat(actual.getPriceId()).isEqualTo(priceId); + assertThat(actual.getStaged()).isEqualTo(staged); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/actions/SetPriceCustomTypeAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/actions/SetPriceCustomTypeAssert.java index abd39877b7..f258947cf4 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/actions/SetPriceCustomTypeAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/actions/SetPriceCustomTypeAssert.java @@ -1,47 +1,46 @@ package com.commercetools.sync.commons.asserts.actions; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomType; import io.sphere.sdk.types.Type; - +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - public class SetPriceCustomTypeAssert extends AbstractSetCustomTypeAssert { - SetPriceCustomTypeAssert(@Nullable final SetProductPriceCustomType actual) { - super(actual, SetPriceCustomTypeAssert.class); - } - - /** - * Verifies that the actual {@link io.sphere.sdk.products.commands.updateactions.SetAssetCustomType} value has - * identical fields as the ones supplied. - * - * @param actionName the update action name. - * @param priceId the price id that has the custom fields. - * @param staged the staged flag of the action. - * @param customFields the new custom type fields. - * @param type the new custom type. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual fields do not match the supplied values. - */ - public SetPriceCustomTypeAssert hasValues( - @Nonnull final String actionName, - @Nullable final String priceId, - @Nullable final Boolean staged, - @Nullable final Map customFields, - @Nullable final ResourceIdentifier type) { - - super.hasValues(actionName, customFields, type); - assertThat(actual.getStaged()).isEqualTo(staged); - assertThat(actual.getPriceId()).isEqualTo(priceId); - return myself; - } + SetPriceCustomTypeAssert(@Nullable final SetProductPriceCustomType actual) { + super(actual, SetPriceCustomTypeAssert.class); + } + + /** + * Verifies that the actual {@link + * io.sphere.sdk.products.commands.updateactions.SetAssetCustomType} value has identical fields as + * the ones supplied. + * + * @param actionName the update action name. + * @param priceId the price id that has the custom fields. + * @param staged the staged flag of the action. + * @param customFields the new custom type fields. + * @param type the new custom type. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual fields do not match the supplied values. + */ + public SetPriceCustomTypeAssert hasValues( + @Nonnull final String actionName, + @Nullable final String priceId, + @Nullable final Boolean staged, + @Nullable final Map customFields, + @Nullable final ResourceIdentifier type) { + + super.hasValues(actionName, customFields, type); + assertThat(actual.getStaged()).isEqualTo(staged); + assertThat(actual.getPriceId()).isEqualTo(priceId); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/AbstractSyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/AbstractSyncStatisticsAssert.java index a2096a928b..126ac1340c 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/AbstractSyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/AbstractSyncStatisticsAssert.java @@ -1,39 +1,38 @@ package com.commercetools.sync.commons.asserts.statistics; -import com.commercetools.sync.commons.helpers.BaseSyncStatistics; -import org.assertj.core.api.AbstractAssert; +import static org.assertj.core.api.Assertions.assertThat; +import com.commercetools.sync.commons.helpers.BaseSyncStatistics; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.assertj.core.api.AbstractAssert; -import static org.assertj.core.api.Assertions.assertThat; - -class AbstractSyncStatisticsAssert, A extends BaseSyncStatistics> +class AbstractSyncStatisticsAssert< + S extends AbstractSyncStatisticsAssert, A extends BaseSyncStatistics> extends AbstractAssert { - AbstractSyncStatisticsAssert(@Nullable final A actual, @Nonnull final Class selfType) { - super(actual, selfType); - } + AbstractSyncStatisticsAssert(@Nullable final A actual, @Nonnull final Class selfType) { + super(actual, selfType); + } - /** - * Verifies that the actual {@link BaseSyncStatistics} value has identical statistics counters as the ones - * supplied. - * - * @param processed the number of processed resources. - * @param created the number of created resources. - * @param updated the number of updated resources. - * @param failed the number of failed resources. - * - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual statistics do not match the supplied values. - */ - public S hasValues(final int processed, final int created, final int updated, final int failed) { - assertThat(actual).isNotNull(); - assertThat(actual.getProcessed()).hasValue(processed); - assertThat(actual.getCreated()).hasValue(created); - assertThat(actual.getUpdated()).hasValue(updated); - assertThat(actual.getFailed()).hasValue(failed); - return myself; - } + /** + * Verifies that the actual {@link BaseSyncStatistics} value has identical statistics counters as + * the ones supplied. + * + * @param processed the number of processed resources. + * @param created the number of created resources. + * @param updated the number of updated resources. + * @param failed the number of failed resources. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual statistics do not match the supplied values. + */ + public S hasValues(final int processed, final int created, final int updated, final int failed) { + assertThat(actual).isNotNull(); + assertThat(actual.getProcessed()).hasValue(processed); + assertThat(actual.getCreated()).hasValue(created); + assertThat(actual.getUpdated()).hasValue(updated); + assertThat(actual.getFailed()).hasValue(failed); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/AssertionsForStatistics.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/AssertionsForStatistics.java index ab42fd47c3..9775d343b4 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/AssertionsForStatistics.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/AssertionsForStatistics.java @@ -11,132 +11,140 @@ import com.commercetools.sync.states.helpers.StateSyncStatistics; import com.commercetools.sync.taxcategories.helpers.TaxCategorySyncStatistics; import com.commercetools.sync.types.helpers.TypeSyncStatistics; - import javax.annotation.Nonnull; import javax.annotation.Nullable; public final class AssertionsForStatistics { - private AssertionsForStatistics() { - } + private AssertionsForStatistics() {} - /** - * Create assertion for {@link CategorySyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static CategorySyncStatisticsAssert assertThat(@Nullable final CategorySyncStatistics statistics) { - return new CategorySyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link CategorySyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static CategorySyncStatisticsAssert assertThat( + @Nullable final CategorySyncStatistics statistics) { + return new CategorySyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link ProductSyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static ProductSyncStatisticsAssert assertThat(@Nullable final ProductSyncStatistics statistics) { - return new ProductSyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link ProductSyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static ProductSyncStatisticsAssert assertThat( + @Nullable final ProductSyncStatistics statistics) { + return new ProductSyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link InventorySyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static InventorySyncStatisticsAssert assertThat(@Nullable final InventorySyncStatistics statistics) { - return new InventorySyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link InventorySyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static InventorySyncStatisticsAssert assertThat( + @Nullable final InventorySyncStatistics statistics) { + return new InventorySyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link ProductTypeSyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static ProductTypeSyncStatisticsAssert assertThat(@Nullable final ProductTypeSyncStatistics statistics) { - return new ProductTypeSyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link ProductTypeSyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static ProductTypeSyncStatisticsAssert assertThat( + @Nullable final ProductTypeSyncStatistics statistics) { + return new ProductTypeSyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link TypeSyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static TypeSyncStatisticsAssert assertThat(@Nullable final TypeSyncStatistics statistics) { - return new TypeSyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link TypeSyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static TypeSyncStatisticsAssert assertThat(@Nullable final TypeSyncStatistics statistics) { + return new TypeSyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link CartDiscountSyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static CartDiscountSyncStatisticsAssert assertThat(@Nullable final CartDiscountSyncStatistics statistics) { - return new CartDiscountSyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link CartDiscountSyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static CartDiscountSyncStatisticsAssert assertThat( + @Nullable final CartDiscountSyncStatistics statistics) { + return new CartDiscountSyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link StateSyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static StateSyncStatisticsAssert assertThat(@Nullable final StateSyncStatistics statistics) { - return new StateSyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link StateSyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static StateSyncStatisticsAssert assertThat( + @Nullable final StateSyncStatistics statistics) { + return new StateSyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link TaxCategorySyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static TaxCategorySyncStatisticsAssert assertThat(@Nullable final TaxCategorySyncStatistics statistics) { - return new TaxCategorySyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link TaxCategorySyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static TaxCategorySyncStatisticsAssert assertThat( + @Nullable final TaxCategorySyncStatistics statistics) { + return new TaxCategorySyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link CustomObjectSyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static CustomObjectSyncStatisticsAssert assertThat(@Nullable final CustomObjectSyncStatistics statistics) { - return new CustomObjectSyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link CustomObjectSyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static CustomObjectSyncStatisticsAssert assertThat( + @Nullable final CustomObjectSyncStatistics statistics) { + return new CustomObjectSyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link CustomerSyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static CustomerSyncStatisticsAssert assertThat(@Nullable final CustomerSyncStatistics statistics) { - return new CustomerSyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link CustomerSyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static CustomerSyncStatisticsAssert assertThat( + @Nullable final CustomerSyncStatistics statistics) { + return new CustomerSyncStatisticsAssert(statistics); + } - /** - * Create assertion for {@link ShoppingListSyncStatistics}. - * - * @param statistics the actual value. - * @return the created assertion object. - */ - @Nonnull - public static ShoppingListSyncStatisticsAssert assertThat(@Nullable final ShoppingListSyncStatistics statistics) { - return new ShoppingListSyncStatisticsAssert(statistics); - } + /** + * Create assertion for {@link ShoppingListSyncStatistics}. + * + * @param statistics the actual value. + * @return the created assertion object. + */ + @Nonnull + public static ShoppingListSyncStatisticsAssert assertThat( + @Nullable final ShoppingListSyncStatistics statistics) { + return new ShoppingListSyncStatisticsAssert(statistics); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/CartDiscountSyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/CartDiscountSyncStatisticsAssert.java index 3931dd1b6c..6375145f86 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/CartDiscountSyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/CartDiscountSyncStatisticsAssert.java @@ -1,13 +1,13 @@ package com.commercetools.sync.commons.asserts.statistics; import com.commercetools.sync.cartdiscounts.helpers.CartDiscountSyncStatistics; - import javax.annotation.Nullable; -public final class CartDiscountSyncStatisticsAssert extends - AbstractSyncStatisticsAssert { +public final class CartDiscountSyncStatisticsAssert + extends AbstractSyncStatisticsAssert< + CartDiscountSyncStatisticsAssert, CartDiscountSyncStatistics> { - CartDiscountSyncStatisticsAssert(@Nullable final CartDiscountSyncStatistics actual) { - super(actual, CartDiscountSyncStatisticsAssert.class); - } + CartDiscountSyncStatisticsAssert(@Nullable final CartDiscountSyncStatistics actual) { + super(actual, CartDiscountSyncStatisticsAssert.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/CategorySyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/CategorySyncStatisticsAssert.java index 044fdb7d15..00f28f28ad 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/CategorySyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/CategorySyncStatisticsAssert.java @@ -1,36 +1,37 @@ package com.commercetools.sync.commons.asserts.statistics; import com.commercetools.sync.categories.helpers.CategorySyncStatistics; - import javax.annotation.Nullable; public final class CategorySyncStatisticsAssert extends AbstractSyncStatisticsAssert { - CategorySyncStatisticsAssert(@Nullable final CategorySyncStatistics actual) { - super(actual, CategorySyncStatisticsAssert.class); - } + CategorySyncStatisticsAssert(@Nullable final CategorySyncStatistics actual) { + super(actual, CategorySyncStatisticsAssert.class); + } - /** - * Verifies that the actual {@link CategorySyncStatistics} value has identical statistics counters as the ones - * supplied. - * - * @param processed the number of processed categories. - * @param created the number of created categories. - * @param updated the number of updated categories. - * @param failed the number of failed categories. - * @param numberOfCategoriesWithMissingParents the number of categories with missing parents. - * - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual statistics do not match the supplied values. - */ - public CategorySyncStatisticsAssert hasValues(final int processed, final int created, - final int updated, final int failed, - final int numberOfCategoriesWithMissingParents) { - super.hasValues(processed, created, updated, failed); - org.assertj.core.api.Assertions.assertThat( - actual.getNumberOfCategoriesWithMissingParents()).isEqualTo(numberOfCategoriesWithMissingParents); - return myself; - } + /** + * Verifies that the actual {@link CategorySyncStatistics} value has identical statistics counters + * as the ones supplied. + * + * @param processed the number of processed categories. + * @param created the number of created categories. + * @param updated the number of updated categories. + * @param failed the number of failed categories. + * @param numberOfCategoriesWithMissingParents the number of categories with missing parents. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual statistics do not match the supplied values. + */ + public CategorySyncStatisticsAssert hasValues( + final int processed, + final int created, + final int updated, + final int failed, + final int numberOfCategoriesWithMissingParents) { + super.hasValues(processed, created, updated, failed); + org.assertj.core.api.Assertions.assertThat(actual.getNumberOfCategoriesWithMissingParents()) + .isEqualTo(numberOfCategoriesWithMissingParents); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/CustomObjectSyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/CustomObjectSyncStatisticsAssert.java index 63f43198d3..6aac75a8c4 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/CustomObjectSyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/CustomObjectSyncStatisticsAssert.java @@ -3,10 +3,11 @@ import com.commercetools.sync.customobjects.helpers.CustomObjectSyncStatistics; import javax.annotation.Nullable; -public final class CustomObjectSyncStatisticsAssert extends - AbstractSyncStatisticsAssert { +public final class CustomObjectSyncStatisticsAssert + extends AbstractSyncStatisticsAssert< + CustomObjectSyncStatisticsAssert, CustomObjectSyncStatistics> { - CustomObjectSyncStatisticsAssert(@Nullable final CustomObjectSyncStatistics actual) { - super(actual, CustomObjectSyncStatisticsAssert.class); - } + CustomObjectSyncStatisticsAssert(@Nullable final CustomObjectSyncStatistics actual) { + super(actual, CustomObjectSyncStatisticsAssert.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/CustomerSyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/CustomerSyncStatisticsAssert.java index 91ed32f61d..39aea6bdc0 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/CustomerSyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/CustomerSyncStatisticsAssert.java @@ -1,13 +1,12 @@ package com.commercetools.sync.commons.asserts.statistics; import com.commercetools.sync.customers.helpers.CustomerSyncStatistics; - import javax.annotation.Nullable; -public final class CustomerSyncStatisticsAssert extends - AbstractSyncStatisticsAssert { +public final class CustomerSyncStatisticsAssert + extends AbstractSyncStatisticsAssert { - CustomerSyncStatisticsAssert(@Nullable final CustomerSyncStatistics actual) { - super(actual, CustomerSyncStatisticsAssert.class); - } + CustomerSyncStatisticsAssert(@Nullable final CustomerSyncStatistics actual) { + super(actual, CustomerSyncStatisticsAssert.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/InventorySyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/InventorySyncStatisticsAssert.java index eedf01ab99..7eee9c2126 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/InventorySyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/InventorySyncStatisticsAssert.java @@ -1,13 +1,12 @@ package com.commercetools.sync.commons.asserts.statistics; import com.commercetools.sync.inventories.helpers.InventorySyncStatistics; - import javax.annotation.Nullable; -public final class InventorySyncStatisticsAssert extends - AbstractSyncStatisticsAssert { +public final class InventorySyncStatisticsAssert + extends AbstractSyncStatisticsAssert { - InventorySyncStatisticsAssert(@Nullable final InventorySyncStatistics actual) { - super(actual, InventorySyncStatisticsAssert.class); - } + InventorySyncStatisticsAssert(@Nullable final InventorySyncStatistics actual) { + super(actual, InventorySyncStatisticsAssert.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/ProductSyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/ProductSyncStatisticsAssert.java index 0f2ba1ddff..12908b27a8 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/ProductSyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/ProductSyncStatisticsAssert.java @@ -1,36 +1,37 @@ package com.commercetools.sync.commons.asserts.statistics; import com.commercetools.sync.products.helpers.ProductSyncStatistics; - import javax.annotation.Nullable; public final class ProductSyncStatisticsAssert extends AbstractSyncStatisticsAssert { - ProductSyncStatisticsAssert(@Nullable final ProductSyncStatistics actual) { - super(actual, ProductSyncStatisticsAssert.class); - } + ProductSyncStatisticsAssert(@Nullable final ProductSyncStatistics actual) { + super(actual, ProductSyncStatisticsAssert.class); + } - /** - * Verifies that the actual {@link ProductSyncStatistics} value has identical statistics counters as the ones - * supplied. - * - * @param processed the number of processed products. - * @param created the number of created products. - * @param updated the number of updated products. - * @param failed the number of failed products. - * @param numberOfProductsWithMissingParents the number of products with missing parents. - * - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual statistics do not match the supplied values. - */ - public ProductSyncStatisticsAssert hasValues(final int processed, final int created, - final int updated, final int failed, - final int numberOfProductsWithMissingParents) { - super.hasValues(processed, created, updated, failed); - org.assertj.core.api.Assertions.assertThat( - actual.getNumberOfProductsWithMissingParents()).isEqualTo(numberOfProductsWithMissingParents); - return myself; - } + /** + * Verifies that the actual {@link ProductSyncStatistics} value has identical statistics counters + * as the ones supplied. + * + * @param processed the number of processed products. + * @param created the number of created products. + * @param updated the number of updated products. + * @param failed the number of failed products. + * @param numberOfProductsWithMissingParents the number of products with missing parents. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual statistics do not match the supplied values. + */ + public ProductSyncStatisticsAssert hasValues( + final int processed, + final int created, + final int updated, + final int failed, + final int numberOfProductsWithMissingParents) { + super.hasValues(processed, created, updated, failed); + org.assertj.core.api.Assertions.assertThat(actual.getNumberOfProductsWithMissingParents()) + .isEqualTo(numberOfProductsWithMissingParents); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/ProductTypeSyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/ProductTypeSyncStatisticsAssert.java index 7e8d56c20e..997c878cce 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/ProductTypeSyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/ProductTypeSyncStatisticsAssert.java @@ -1,37 +1,40 @@ package com.commercetools.sync.commons.asserts.statistics; import com.commercetools.sync.producttypes.helpers.ProductTypeSyncStatistics; - import javax.annotation.Nullable; -public final class ProductTypeSyncStatisticsAssert extends - AbstractSyncStatisticsAssert { +public final class ProductTypeSyncStatisticsAssert + extends AbstractSyncStatisticsAssert< + ProductTypeSyncStatisticsAssert, ProductTypeSyncStatistics> { - ProductTypeSyncStatisticsAssert(@Nullable final ProductTypeSyncStatistics actual) { - super(actual, ProductTypeSyncStatisticsAssert.class); - } + ProductTypeSyncStatisticsAssert(@Nullable final ProductTypeSyncStatistics actual) { + super(actual, ProductTypeSyncStatisticsAssert.class); + } - /** - * Verifies that the actual {@link ProductTypeSyncStatistics} value has identical statistics counters as the ones - * supplied. - * - * @param processed the number of processed productTypes. - * @param created the number of created productTypes. - * @param updated the number of updated productTypes. - * @param failed the number of failed productTypes. - * @param numberOfProductTypesWithMissingNestedReferences the number of productTypes with missing nestedType - * references. - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual statistics do not match the supplied values. - */ - public ProductTypeSyncStatisticsAssert hasValues(final int processed, final int created, - final int updated, final int failed, - final int numberOfProductTypesWithMissingNestedReferences) { - super.hasValues(processed, created, updated, failed); - org.assertj.core.api.Assertions.assertThat( + /** + * Verifies that the actual {@link ProductTypeSyncStatistics} value has identical statistics + * counters as the ones supplied. + * + * @param processed the number of processed productTypes. + * @param created the number of created productTypes. + * @param updated the number of updated productTypes. + * @param failed the number of failed productTypes. + * @param numberOfProductTypesWithMissingNestedReferences the number of productTypes with missing + * nestedType references. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual statistics do not match the supplied values. + */ + public ProductTypeSyncStatisticsAssert hasValues( + final int processed, + final int created, + final int updated, + final int failed, + final int numberOfProductTypesWithMissingNestedReferences) { + super.hasValues(processed, created, updated, failed); + org.assertj.core.api.Assertions.assertThat( actual.getNumberOfProductTypesWithMissingNestedProductTypes()) - .isEqualTo(numberOfProductTypesWithMissingNestedReferences); - return myself; - } + .isEqualTo(numberOfProductTypesWithMissingNestedReferences); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/ShoppingListSyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/ShoppingListSyncStatisticsAssert.java index 00ddf35832..adb667bb62 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/ShoppingListSyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/ShoppingListSyncStatisticsAssert.java @@ -1,13 +1,13 @@ package com.commercetools.sync.commons.asserts.statistics; import com.commercetools.sync.shoppinglists.helpers.ShoppingListSyncStatistics; - import javax.annotation.Nullable; -public final class ShoppingListSyncStatisticsAssert extends - AbstractSyncStatisticsAssert { +public final class ShoppingListSyncStatisticsAssert + extends AbstractSyncStatisticsAssert< + ShoppingListSyncStatisticsAssert, ShoppingListSyncStatistics> { - ShoppingListSyncStatisticsAssert(@Nullable final ShoppingListSyncStatistics actual) { - super(actual, ShoppingListSyncStatisticsAssert.class); - } + ShoppingListSyncStatisticsAssert(@Nullable final ShoppingListSyncStatistics actual) { + super(actual, ShoppingListSyncStatisticsAssert.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/StateSyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/StateSyncStatisticsAssert.java index 13684680a3..623a7919ea 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/StateSyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/StateSyncStatisticsAssert.java @@ -1,37 +1,39 @@ package com.commercetools.sync.commons.asserts.statistics; -import com.commercetools.sync.states.helpers.StateSyncStatistics; +import static org.assertj.core.api.Assertions.assertThat; +import com.commercetools.sync.states.helpers.StateSyncStatistics; import javax.annotation.Nullable; -import static org.assertj.core.api.Assertions.assertThat; - public final class StateSyncStatisticsAssert extends AbstractSyncStatisticsAssert { - StateSyncStatisticsAssert(@Nullable final StateSyncStatistics actual) { - super(actual, StateSyncStatisticsAssert.class); - } + StateSyncStatisticsAssert(@Nullable final StateSyncStatistics actual) { + super(actual, StateSyncStatisticsAssert.class); + } - /** - * Verifies that the actual {@link StateSyncStatistics} value has identical statistics counters as the ones - * supplied. - * - * @param processed the number of processed states. - * @param created the number of created states. - * @param updated the number of updated states. - * @param failed the number of failed states. - * @param numberOfStatesWithMissingParents the number of states with missing parents. - * - * @return {@code this} assertion object. - * @throws AssertionError if the actual value is {@code null}. - * @throws AssertionError if the actual statistics do not match the supplied values. - */ - public StateSyncStatisticsAssert hasValues(final int processed, final int created, - final int updated, final int failed, - final int numberOfStatesWithMissingParents) { - super.hasValues(processed, created, updated, failed); - assertThat(actual.getNumberOfStatesWithMissingParents()).isEqualTo(numberOfStatesWithMissingParents); - return myself; - } + /** + * Verifies that the actual {@link StateSyncStatistics} value has identical statistics counters as + * the ones supplied. + * + * @param processed the number of processed states. + * @param created the number of created states. + * @param updated the number of updated states. + * @param failed the number of failed states. + * @param numberOfStatesWithMissingParents the number of states with missing parents. + * @return {@code this} assertion object. + * @throws AssertionError if the actual value is {@code null}. + * @throws AssertionError if the actual statistics do not match the supplied values. + */ + public StateSyncStatisticsAssert hasValues( + final int processed, + final int created, + final int updated, + final int failed, + final int numberOfStatesWithMissingParents) { + super.hasValues(processed, created, updated, failed); + assertThat(actual.getNumberOfStatesWithMissingParents()) + .isEqualTo(numberOfStatesWithMissingParents); + return myself; + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/TaxCategorySyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/TaxCategorySyncStatisticsAssert.java index 85872ecf24..1e91c64adf 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/TaxCategorySyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/TaxCategorySyncStatisticsAssert.java @@ -1,13 +1,13 @@ package com.commercetools.sync.commons.asserts.statistics; import com.commercetools.sync.taxcategories.helpers.TaxCategorySyncStatistics; - import javax.annotation.Nullable; -public final class TaxCategorySyncStatisticsAssert extends - AbstractSyncStatisticsAssert { +public final class TaxCategorySyncStatisticsAssert + extends AbstractSyncStatisticsAssert< + TaxCategorySyncStatisticsAssert, TaxCategorySyncStatistics> { - TaxCategorySyncStatisticsAssert(@Nullable final TaxCategorySyncStatistics actual) { - super(actual, TaxCategorySyncStatisticsAssert.class); - } + TaxCategorySyncStatisticsAssert(@Nullable final TaxCategorySyncStatistics actual) { + super(actual, TaxCategorySyncStatisticsAssert.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/asserts/statistics/TypeSyncStatisticsAssert.java b/src/test/java/com/commercetools/sync/commons/asserts/statistics/TypeSyncStatisticsAssert.java index 1b607497a5..a66fa08541 100644 --- a/src/test/java/com/commercetools/sync/commons/asserts/statistics/TypeSyncStatisticsAssert.java +++ b/src/test/java/com/commercetools/sync/commons/asserts/statistics/TypeSyncStatisticsAssert.java @@ -1,13 +1,12 @@ package com.commercetools.sync.commons.asserts.statistics; import com.commercetools.sync.types.helpers.TypeSyncStatistics; - import javax.annotation.Nullable; -public final class TypeSyncStatisticsAssert extends - AbstractSyncStatisticsAssert { +public final class TypeSyncStatisticsAssert + extends AbstractSyncStatisticsAssert { - TypeSyncStatisticsAssert(@Nullable final TypeSyncStatistics actual) { - super(actual, TypeSyncStatisticsAssert.class); - } + TypeSyncStatisticsAssert(@Nullable final TypeSyncStatistics actual) { + super(actual, TypeSyncStatisticsAssert.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/exceptions/BuildUpdateActionExceptionTest.java b/src/test/java/com/commercetools/sync/commons/exceptions/BuildUpdateActionExceptionTest.java index e4703c5d77..7bca938b0f 100644 --- a/src/test/java/com/commercetools/sync/commons/exceptions/BuildUpdateActionExceptionTest.java +++ b/src/test/java/com/commercetools/sync/commons/exceptions/BuildUpdateActionExceptionTest.java @@ -1,44 +1,48 @@ package com.commercetools.sync.commons.exceptions; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - class BuildUpdateActionExceptionTest { - @Test - void buildUpdateActionException_WithMessageOnly_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - assertThatThrownBy(() -> { - throw new BuildUpdateActionException(message); - }).isExactlyInstanceOf(BuildUpdateActionException.class) - .hasNoCause() - .hasMessage(message); - } - - @Test - void buildUpdateActionException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new BuildUpdateActionException(message, cause); - }).isExactlyInstanceOf(BuildUpdateActionException.class) - .hasCause(cause) - .hasMessage(message); - } - - @Test - void buildUpdateActionException_WithCauseOnly_ShouldBuildExceptionCorrectly() { - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new BuildUpdateActionException(cause); - }).isExactlyInstanceOf(BuildUpdateActionException.class) - .hasCause(cause) - .hasMessage(cause.toString()); - - } + @Test + void buildUpdateActionException_WithMessageOnly_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + assertThatThrownBy( + () -> { + throw new BuildUpdateActionException(message); + }) + .isExactlyInstanceOf(BuildUpdateActionException.class) + .hasNoCause() + .hasMessage(message); + } + + @Test + void buildUpdateActionException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new BuildUpdateActionException(message, cause); + }) + .isExactlyInstanceOf(BuildUpdateActionException.class) + .hasCause(cause) + .hasMessage(message); + } + + @Test + void buildUpdateActionException_WithCauseOnly_ShouldBuildExceptionCorrectly() { + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new BuildUpdateActionException(cause); + }) + .isExactlyInstanceOf(BuildUpdateActionException.class) + .hasCause(cause) + .hasMessage(cause.toString()); + } } diff --git a/src/test/java/com/commercetools/sync/commons/exceptions/DuplicateKeyExceptionTest.java b/src/test/java/com/commercetools/sync/commons/exceptions/DuplicateKeyExceptionTest.java index 41e231e789..c9465d3d2b 100644 --- a/src/test/java/com/commercetools/sync/commons/exceptions/DuplicateKeyExceptionTest.java +++ b/src/test/java/com/commercetools/sync/commons/exceptions/DuplicateKeyExceptionTest.java @@ -1,44 +1,48 @@ package com.commercetools.sync.commons.exceptions; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - class DuplicateKeyExceptionTest { - @Test - void duplicateKeyException_WithMessageOnly_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - assertThatThrownBy(() -> { - throw new DuplicateKeyException(message); - }).isExactlyInstanceOf(DuplicateKeyException.class) - .hasNoCause() - .hasMessage(message); - } - - @Test - void duplicateKeyException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new DuplicateKeyException(message, cause); - }).isExactlyInstanceOf(DuplicateKeyException.class) - .hasCause(cause) - .hasMessage(message); - } - - @Test - void duplicateKeyException_WithCauseOnly_ShouldBuildExceptionCorrectly() { - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new DuplicateKeyException(cause); - }).isExactlyInstanceOf(DuplicateKeyException.class) - .hasCause(cause) - .hasMessage(cause.toString()); - - } + @Test + void duplicateKeyException_WithMessageOnly_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + assertThatThrownBy( + () -> { + throw new DuplicateKeyException(message); + }) + .isExactlyInstanceOf(DuplicateKeyException.class) + .hasNoCause() + .hasMessage(message); + } + + @Test + void duplicateKeyException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new DuplicateKeyException(message, cause); + }) + .isExactlyInstanceOf(DuplicateKeyException.class) + .hasCause(cause) + .hasMessage(message); + } + + @Test + void duplicateKeyException_WithCauseOnly_ShouldBuildExceptionCorrectly() { + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new DuplicateKeyException(cause); + }) + .isExactlyInstanceOf(DuplicateKeyException.class) + .hasCause(cause) + .hasMessage(cause.toString()); + } } diff --git a/src/test/java/com/commercetools/sync/commons/exceptions/DuplicateNameExceptionTest.java b/src/test/java/com/commercetools/sync/commons/exceptions/DuplicateNameExceptionTest.java index 3dd69c22ee..b7e079bf48 100644 --- a/src/test/java/com/commercetools/sync/commons/exceptions/DuplicateNameExceptionTest.java +++ b/src/test/java/com/commercetools/sync/commons/exceptions/DuplicateNameExceptionTest.java @@ -1,43 +1,47 @@ package com.commercetools.sync.commons.exceptions; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - class DuplicateNameExceptionTest { - @Test - void duplicateNameException_WithMessageOnly_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - assertThatThrownBy(() -> { - throw new DuplicateNameException(message); - }).isExactlyInstanceOf(DuplicateNameException.class) - .hasNoCause() - .hasMessage(message); - } - - @Test - void duplicateNameException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new DuplicateNameException(message, cause); - }).isExactlyInstanceOf(DuplicateNameException.class) - .hasCause(cause) - .hasMessage(message); - } - - @Test - void duplicateNameException_WithCauseOnly_ShouldBuildExceptionCorrectly() { - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new DuplicateNameException(cause); - }).isExactlyInstanceOf(DuplicateNameException.class) - .hasCause(cause) - .hasMessage(cause.toString()); - - } + @Test + void duplicateNameException_WithMessageOnly_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + assertThatThrownBy( + () -> { + throw new DuplicateNameException(message); + }) + .isExactlyInstanceOf(DuplicateNameException.class) + .hasNoCause() + .hasMessage(message); + } + + @Test + void duplicateNameException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new DuplicateNameException(message, cause); + }) + .isExactlyInstanceOf(DuplicateNameException.class) + .hasCause(cause) + .hasMessage(message); + } + + @Test + void duplicateNameException_WithCauseOnly_ShouldBuildExceptionCorrectly() { + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new DuplicateNameException(cause); + }) + .isExactlyInstanceOf(DuplicateNameException.class) + .hasCause(cause) + .hasMessage(cause.toString()); + } } diff --git a/src/test/java/com/commercetools/sync/commons/exceptions/InvalidAttributeDefinitionExceptionTest.java b/src/test/java/com/commercetools/sync/commons/exceptions/InvalidAttributeDefinitionExceptionTest.java index 1f784c6645..4a8b2ab966 100644 --- a/src/test/java/com/commercetools/sync/commons/exceptions/InvalidAttributeDefinitionExceptionTest.java +++ b/src/test/java/com/commercetools/sync/commons/exceptions/InvalidAttributeDefinitionExceptionTest.java @@ -1,21 +1,23 @@ package com.commercetools.sync.commons.exceptions; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThatThrownBy; -class InvalidAttributeDefinitionExceptionTest { +import org.junit.jupiter.api.Test; - @Test - void invalidAttributeDefinitionException_WithMessageAndNoCauses_ShouldBuildExceptionCorrectly() { - final String message = "invalid attribute definition"; - final InvalidReferenceException invalidReferenceException = new InvalidReferenceException("invalid reference"); +class InvalidAttributeDefinitionExceptionTest { - assertThatThrownBy(() -> { - throw new InvalidAttributeDefinitionException(message, invalidReferenceException); - }).isExactlyInstanceOf(InvalidAttributeDefinitionException.class) - .hasCause(invalidReferenceException) - .hasMessage(message); - } + @Test + void invalidAttributeDefinitionException_WithMessageAndNoCauses_ShouldBuildExceptionCorrectly() { + final String message = "invalid attribute definition"; + final InvalidReferenceException invalidReferenceException = + new InvalidReferenceException("invalid reference"); -} \ No newline at end of file + assertThatThrownBy( + () -> { + throw new InvalidAttributeDefinitionException(message, invalidReferenceException); + }) + .isExactlyInstanceOf(InvalidAttributeDefinitionException.class) + .hasCause(invalidReferenceException) + .hasMessage(message); + } +} diff --git a/src/test/java/com/commercetools/sync/commons/exceptions/InvalidProductTypeExceptionTest.java b/src/test/java/com/commercetools/sync/commons/exceptions/InvalidProductTypeExceptionTest.java index 3828535681..473c9cbb61 100644 --- a/src/test/java/com/commercetools/sync/commons/exceptions/InvalidProductTypeExceptionTest.java +++ b/src/test/java/com/commercetools/sync/commons/exceptions/InvalidProductTypeExceptionTest.java @@ -1,98 +1,113 @@ package com.commercetools.sync.commons.exceptions; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - import static java.lang.String.format; import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -class InvalidProductTypeExceptionTest { +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import org.junit.jupiter.api.Test; - @Test - void invalidProductTypeException_WithMessageAndNoCauses_ShouldBuildExceptionCorrectly() { - final String message = "foo"; +class InvalidProductTypeExceptionTest { - assertThatThrownBy(() -> { - throw new InvalidProductTypeException(message, emptySet()); - }) - .isExactlyInstanceOf(InvalidProductTypeException.class) - .hasNoCause() - .hasMessage(message) - .satisfies(exception -> { - final InvalidProductTypeException invalidProductTypeException = (InvalidProductTypeException) exception; - assertThat(invalidProductTypeException.getCauses()).isEmpty(); + @Test + void invalidProductTypeException_WithMessageAndNoCauses_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + assertThatThrownBy( + () -> { + throw new InvalidProductTypeException(message, emptySet()); + }) + .isExactlyInstanceOf(InvalidProductTypeException.class) + .hasNoCause() + .hasMessage(message) + .satisfies( + exception -> { + final InvalidProductTypeException invalidProductTypeException = + (InvalidProductTypeException) exception; + assertThat(invalidProductTypeException.getCauses()).isEmpty(); }); - } - - @Test - void invalidProductTypeException_WithMessageAndCausesWithoutMessages_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - final IOException cause1 = new IOException(""); - final InvalidReferenceException cause2 = new InvalidReferenceException(""); - final Set causes = new HashSet<>(); - causes.add(cause1); - causes.add(cause2); - - assertThatThrownBy(() -> { - throw new InvalidProductTypeException(message, causes); - }) - .isExactlyInstanceOf(InvalidProductTypeException.class) - .hasNoCause() - .hasMessage(message) - .satisfies(exception -> { - final InvalidProductTypeException invalidProductTypeException = (InvalidProductTypeException) exception; - assertThat(invalidProductTypeException.getCauses()).containsExactlyInAnyOrder(cause1, cause2); + } + + @Test + void + invalidProductTypeException_WithMessageAndCausesWithoutMessages_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + final IOException cause1 = new IOException(""); + final InvalidReferenceException cause2 = new InvalidReferenceException(""); + final Set causes = new HashSet<>(); + causes.add(cause1); + causes.add(cause2); + + assertThatThrownBy( + () -> { + throw new InvalidProductTypeException(message, causes); + }) + .isExactlyInstanceOf(InvalidProductTypeException.class) + .hasNoCause() + .hasMessage(message) + .satisfies( + exception -> { + final InvalidProductTypeException invalidProductTypeException = + (InvalidProductTypeException) exception; + assertThat(invalidProductTypeException.getCauses()) + .containsExactlyInAnyOrder(cause1, cause2); }); - } - - @Test - void invalidProductTypeException_WithMessageAndCauseWithNullMessage_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - final IOException cause1 = new IOException(); - final Set causes = new HashSet<>(); - causes.add(cause1); - - assertThatThrownBy(() -> { - throw new InvalidProductTypeException(message, causes); - }) - .isExactlyInstanceOf(InvalidProductTypeException.class) - .hasNoCause() - .hasMessage(message) - .satisfies(exception -> { - final InvalidProductTypeException invalidProductTypeException = (InvalidProductTypeException) exception; - assertThat(invalidProductTypeException.getCauses()).containsExactly(cause1); + } + + @Test + void + invalidProductTypeException_WithMessageAndCauseWithNullMessage_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + final IOException cause1 = new IOException(); + final Set causes = new HashSet<>(); + causes.add(cause1); + + assertThatThrownBy( + () -> { + throw new InvalidProductTypeException(message, causes); + }) + .isExactlyInstanceOf(InvalidProductTypeException.class) + .hasNoCause() + .hasMessage(message) + .satisfies( + exception -> { + final InvalidProductTypeException invalidProductTypeException = + (InvalidProductTypeException) exception; + assertThat(invalidProductTypeException.getCauses()).containsExactly(cause1); }); - } - - @Test - void invalidProductTypeException_WithMessageAndCausesWithNonBlankMessages_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - final IOException cause1 = new IOException("cause1"); - final InvalidReferenceException cause2 = new InvalidReferenceException("cause2"); - final Set causes = new HashSet<>(); - causes.add(cause1); - causes.add(cause2); - - assertThatThrownBy(() -> { - throw new InvalidProductTypeException(message, causes); - }) - .isExactlyInstanceOf(InvalidProductTypeException.class) - .hasNoCause() - .hasMessageContaining(format("%s Causes:%n", message)) - .hasMessageContaining(format("%n\t\t%s", cause1.getMessage())) - .hasMessageContaining(format("%n\t\t%s", cause2.getMessage())) - .satisfies(exception -> { - final InvalidProductTypeException invalidProductTypeException = (InvalidProductTypeException) exception; - assertThat(invalidProductTypeException.getCauses()).containsExactlyInAnyOrder(cause1, cause2); + } + + @Test + void + invalidProductTypeException_WithMessageAndCausesWithNonBlankMessages_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + final IOException cause1 = new IOException("cause1"); + final InvalidReferenceException cause2 = new InvalidReferenceException("cause2"); + final Set causes = new HashSet<>(); + causes.add(cause1); + causes.add(cause2); + + assertThatThrownBy( + () -> { + throw new InvalidProductTypeException(message, causes); + }) + .isExactlyInstanceOf(InvalidProductTypeException.class) + .hasNoCause() + .hasMessageContaining(format("%s Causes:%n", message)) + .hasMessageContaining(format("%n\t\t%s", cause1.getMessage())) + .hasMessageContaining(format("%n\t\t%s", cause2.getMessage())) + .satisfies( + exception -> { + final InvalidProductTypeException invalidProductTypeException = + (InvalidProductTypeException) exception; + assertThat(invalidProductTypeException.getCauses()) + .containsExactlyInAnyOrder(cause1, cause2); }); - } - -} \ No newline at end of file + } +} diff --git a/src/test/java/com/commercetools/sync/commons/exceptions/InvalidReferenceExceptionTest.java b/src/test/java/com/commercetools/sync/commons/exceptions/InvalidReferenceExceptionTest.java index e86b418a87..a60a6e6c48 100644 --- a/src/test/java/com/commercetools/sync/commons/exceptions/InvalidReferenceExceptionTest.java +++ b/src/test/java/com/commercetools/sync/commons/exceptions/InvalidReferenceExceptionTest.java @@ -1,19 +1,21 @@ package com.commercetools.sync.commons.exceptions; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.Test; + class InvalidReferenceExceptionTest { - @Test - void invalidReferenceException_WithMessage_ShouldBuildExceptionCorrectly() { - final String message = "foo"; + @Test + void invalidReferenceException_WithMessage_ShouldBuildExceptionCorrectly() { + final String message = "foo"; - assertThatThrownBy(() -> { - throw new InvalidReferenceException(message); - }).isExactlyInstanceOf(InvalidReferenceException.class) - .hasNoCause() - .hasMessage(message); - } -} \ No newline at end of file + assertThatThrownBy( + () -> { + throw new InvalidReferenceException(message); + }) + .isExactlyInstanceOf(InvalidReferenceException.class) + .hasNoCause() + .hasMessage(message); + } +} diff --git a/src/test/java/com/commercetools/sync/commons/exceptions/ReferenceReplacementExceptionTest.java b/src/test/java/com/commercetools/sync/commons/exceptions/ReferenceReplacementExceptionTest.java index 018d503ed4..5e1b9fabe9 100644 --- a/src/test/java/com/commercetools/sync/commons/exceptions/ReferenceReplacementExceptionTest.java +++ b/src/test/java/com/commercetools/sync/commons/exceptions/ReferenceReplacementExceptionTest.java @@ -1,106 +1,117 @@ package com.commercetools.sync.commons.exceptions; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - import static java.lang.String.format; import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import org.junit.jupiter.api.Test; + class ReferenceReplacementExceptionTest { - @Test - void referenceReplacementException_WithMessageAndNoCauses_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - assertThatThrownBy(() -> { - throw new ReferenceReplacementException(message, emptySet()); - }) - .isExactlyInstanceOf(ReferenceReplacementException.class) - .hasNoCause() - .hasMessage(message) - .satisfies(exception -> { - final ReferenceReplacementException referenceReplacementException = - (ReferenceReplacementException) exception; - assertThat(referenceReplacementException.getCauses()).isEmpty(); + @Test + void referenceReplacementException_WithMessageAndNoCauses_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + assertThatThrownBy( + () -> { + throw new ReferenceReplacementException(message, emptySet()); + }) + .isExactlyInstanceOf(ReferenceReplacementException.class) + .hasNoCause() + .hasMessage(message) + .satisfies( + exception -> { + final ReferenceReplacementException referenceReplacementException = + (ReferenceReplacementException) exception; + assertThat(referenceReplacementException.getCauses()).isEmpty(); }); - } - - @Test - void referenceReplacementException_WithMessageAndCausesWithoutMessages_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - final IOException cause1 = new IOException(""); - final InvalidReferenceException cause2 = new InvalidReferenceException(""); - final Set causes = new HashSet<>(); - causes.add(cause1); - causes.add(cause2); - - assertThatThrownBy(() -> { - throw new ReferenceReplacementException(message, causes); - }) - .isExactlyInstanceOf(ReferenceReplacementException.class) - .hasNoCause() - .hasMessage(message) - .satisfies(exception -> { - final ReferenceReplacementException referenceReplacementException = - (ReferenceReplacementException) exception; - assertThat(referenceReplacementException.getCauses()).containsExactlyInAnyOrder(cause1, cause2); + } + + @Test + void + referenceReplacementException_WithMessageAndCausesWithoutMessages_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + final IOException cause1 = new IOException(""); + final InvalidReferenceException cause2 = new InvalidReferenceException(""); + final Set causes = new HashSet<>(); + causes.add(cause1); + causes.add(cause2); + + assertThatThrownBy( + () -> { + throw new ReferenceReplacementException(message, causes); + }) + .isExactlyInstanceOf(ReferenceReplacementException.class) + .hasNoCause() + .hasMessage(message) + .satisfies( + exception -> { + final ReferenceReplacementException referenceReplacementException = + (ReferenceReplacementException) exception; + assertThat(referenceReplacementException.getCauses()) + .containsExactlyInAnyOrder(cause1, cause2); }); - } - - @Test - void referenceReplacementException_WithMessageAndCauseWithNullMessage_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - final IOException cause1 = new IOException(); - final Set causes = new HashSet<>(); - causes.add(cause1); - - assertThatThrownBy(() -> { - throw new ReferenceReplacementException(message, causes); - }) - .isExactlyInstanceOf(ReferenceReplacementException.class) - .hasNoCause() - .hasMessage(message) - .satisfies(exception -> { - final ReferenceReplacementException referenceReplacementException = - (ReferenceReplacementException) exception; - assertThat(referenceReplacementException.getCauses()).containsExactly(cause1); + } + + @Test + void + referenceReplacementException_WithMessageAndCauseWithNullMessage_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + final IOException cause1 = new IOException(); + final Set causes = new HashSet<>(); + causes.add(cause1); + + assertThatThrownBy( + () -> { + throw new ReferenceReplacementException(message, causes); + }) + .isExactlyInstanceOf(ReferenceReplacementException.class) + .hasNoCause() + .hasMessage(message) + .satisfies( + exception -> { + final ReferenceReplacementException referenceReplacementException = + (ReferenceReplacementException) exception; + assertThat(referenceReplacementException.getCauses()).containsExactly(cause1); }); - } - - @Test - void referenceReplacementException_WithMessageAndCausesWithNonBlankMessages_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - final InvalidReferenceException rootCause = new InvalidReferenceException("root cause"); - - final Set rootCauses = new HashSet<>(); - rootCauses.add(rootCause); - - final InvalidProductTypeException cause1 = new InvalidProductTypeException("Failed", rootCauses); - - final Set causes = new HashSet<>(); - causes.add(cause1); - - - assertThatThrownBy(() -> { - throw new ReferenceReplacementException(message, causes); - }) - .isExactlyInstanceOf(ReferenceReplacementException.class) - .hasNoCause() - .hasMessageContaining(format("%s Causes:%n", message)) - .hasMessageContaining(format("%n\t%s", cause1.getMessage())) - .satisfies(exception -> { - final ReferenceReplacementException referenceReplacementException = - (ReferenceReplacementException) exception; - assertThat(referenceReplacementException.getCauses()).containsExactlyInAnyOrder(cause1); + } + + @Test + void + referenceReplacementException_WithMessageAndCausesWithNonBlankMessages_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + final InvalidReferenceException rootCause = new InvalidReferenceException("root cause"); + + final Set rootCauses = new HashSet<>(); + rootCauses.add(rootCause); + + final InvalidProductTypeException cause1 = + new InvalidProductTypeException("Failed", rootCauses); + + final Set causes = new HashSet<>(); + causes.add(cause1); + + assertThatThrownBy( + () -> { + throw new ReferenceReplacementException(message, causes); + }) + .isExactlyInstanceOf(ReferenceReplacementException.class) + .hasNoCause() + .hasMessageContaining(format("%s Causes:%n", message)) + .hasMessageContaining(format("%n\t%s", cause1.getMessage())) + .satisfies( + exception -> { + final ReferenceReplacementException referenceReplacementException = + (ReferenceReplacementException) exception; + assertThat(referenceReplacementException.getCauses()) + .containsExactlyInAnyOrder(cause1); }); - } - -} \ No newline at end of file + } +} diff --git a/src/test/java/com/commercetools/sync/commons/exceptions/ReferenceResolutionExceptionTest.java b/src/test/java/com/commercetools/sync/commons/exceptions/ReferenceResolutionExceptionTest.java index a67e730c38..71a175852b 100644 --- a/src/test/java/com/commercetools/sync/commons/exceptions/ReferenceResolutionExceptionTest.java +++ b/src/test/java/com/commercetools/sync/commons/exceptions/ReferenceResolutionExceptionTest.java @@ -1,43 +1,47 @@ package com.commercetools.sync.commons.exceptions; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - class ReferenceResolutionExceptionTest { - @Test - void referenceResolutionException_WithMessageOnly_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - assertThatThrownBy(() -> { - throw new ReferenceResolutionException(message); - }).isExactlyInstanceOf(ReferenceResolutionException.class) - .hasNoCause() - .hasMessage(message); - } - - @Test - void referenceResolutionException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new ReferenceResolutionException(message, cause); - }).isExactlyInstanceOf(ReferenceResolutionException.class) - .hasCause(cause) - .hasMessage(message); - } - - @Test - void referenceResolutionException_WithCauseOnly_ShouldBuildExceptionCorrectly() { - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new ReferenceResolutionException(cause); - }).isExactlyInstanceOf(ReferenceResolutionException.class) - .hasCause(cause) - .hasMessage(cause.toString()); - - } + @Test + void referenceResolutionException_WithMessageOnly_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + assertThatThrownBy( + () -> { + throw new ReferenceResolutionException(message); + }) + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasNoCause() + .hasMessage(message); + } + + @Test + void referenceResolutionException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new ReferenceResolutionException(message, cause); + }) + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasCause(cause) + .hasMessage(message); + } + + @Test + void referenceResolutionException_WithCauseOnly_ShouldBuildExceptionCorrectly() { + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new ReferenceResolutionException(cause); + }) + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasCause(cause) + .hasMessage(cause.toString()); + } } diff --git a/src/test/java/com/commercetools/sync/commons/exceptions/SyncExceptionTest.java b/src/test/java/com/commercetools/sync/commons/exceptions/SyncExceptionTest.java index 7ff107a4b0..69a73c3cde 100644 --- a/src/test/java/com/commercetools/sync/commons/exceptions/SyncExceptionTest.java +++ b/src/test/java/com/commercetools/sync/commons/exceptions/SyncExceptionTest.java @@ -1,42 +1,48 @@ package com.commercetools.sync.commons.exceptions; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.Test; + class SyncExceptionTest { - @Test - void syncException_WithMessageOnly_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - - assertThatThrownBy(() -> { - throw new SyncException(message); - }).isExactlyInstanceOf(SyncException.class) - .hasNoCause() - .hasMessage(message); - } - - @Test - void syncException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { - final String message = "foo"; - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new SyncException(message, cause); - }).isExactlyInstanceOf(SyncException.class) - .hasCause(cause) - .hasMessage(message); - } - - @Test - void syncException_WithCauseOnly_ShouldBuildExceptionCorrectly() { - final IllegalArgumentException cause = new IllegalArgumentException(); - - assertThatThrownBy(() -> { - throw new SyncException(cause); - }).isExactlyInstanceOf(SyncException.class) - .hasCause(cause) - .hasMessage(cause.toString()); - } + @Test + void syncException_WithMessageOnly_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + + assertThatThrownBy( + () -> { + throw new SyncException(message); + }) + .isExactlyInstanceOf(SyncException.class) + .hasNoCause() + .hasMessage(message); + } + + @Test + void syncException_WithMessageAndCause_ShouldBuildExceptionCorrectly() { + final String message = "foo"; + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new SyncException(message, cause); + }) + .isExactlyInstanceOf(SyncException.class) + .hasCause(cause) + .hasMessage(message); + } + + @Test + void syncException_WithCauseOnly_ShouldBuildExceptionCorrectly() { + final IllegalArgumentException cause = new IllegalArgumentException(); + + assertThatThrownBy( + () -> { + throw new SyncException(cause); + }) + .isExactlyInstanceOf(SyncException.class) + .hasCause(cause) + .hasMessage(cause.toString()); + } } diff --git a/src/test/java/com/commercetools/sync/commons/helpers/AssetReferenceResolverTest.java b/src/test/java/com/commercetools/sync/commons/helpers/AssetReferenceResolverTest.java index 8ea7028257..801825e492 100644 --- a/src/test/java/com/commercetools/sync/commons/helpers/AssetReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/commons/helpers/AssetReferenceResolverTest.java @@ -1,5 +1,18 @@ package com.commercetools.sync.commons.helpers; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.helpers.AssetReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -11,151 +24,157 @@ import io.sphere.sdk.models.SphereException; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.helpers.AssetReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class AssetReferenceResolverTest { - private TypeService typeService; - private ProductSyncOptions syncOptions; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - typeService = getMockTypeService(); - syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - } - - @Test - void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { - final String customTypeKey = "customTypeKey"; - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); - final AssetDraftBuilder assetDraftBuilder = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .key("assetKey") - .custom(customFieldsDraft); - - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final AssetReferenceResolver assetReferenceResolver = new AssetReferenceResolver(syncOptions, typeService); - - // Test and assertion - final String expectedExceptionMessage = - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, assetDraftBuilder.getKey()); - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); - assertThat(assetReferenceResolver.resolveCustomTypeReference(assetDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveCustomTypeReference_WithNullIdOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { - final CustomFieldsDraft customFieldsDraft = mock(CustomFieldsDraft.class); - final ResourceIdentifier typeReference = ResourceIdentifier.ofId(null); - when(customFieldsDraft.getType()).thenReturn(typeReference); - - final AssetDraftBuilder assetDraftBuilder = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .key("assetKey") - .custom(customFieldsDraft); - - final AssetReferenceResolver assetReferenceResolver = new AssetReferenceResolver(syncOptions, typeService); - - assertThat(assetReferenceResolver.resolveCustomTypeReference(assetDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on AssetDraft with key:'%s'. Reason: %s", + private TypeService typeService; + private ProductSyncOptions syncOptions; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + typeService = getMockTypeService(); + syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + } + + @Test + void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { + final String customTypeKey = "customTypeKey"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); + final AssetDraftBuilder assetDraftBuilder = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) + .key("assetKey") + .custom(customFieldsDraft); + + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final AssetReferenceResolver assetReferenceResolver = + new AssetReferenceResolver(syncOptions, typeService); + + // Test and assertion + final String expectedExceptionMessage = + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, assetDraftBuilder.getKey()); + final String expectedMessageWithCause = + format( + "%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); + assertThat(assetReferenceResolver.resolveCustomTypeReference(assetDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void + resolveCustomTypeReference_WithNullIdOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { + final CustomFieldsDraft customFieldsDraft = mock(CustomFieldsDraft.class); + final ResourceIdentifier typeReference = ResourceIdentifier.ofId(null); + when(customFieldsDraft.getType()).thenReturn(typeReference); + + final AssetDraftBuilder assetDraftBuilder = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) + .key("assetKey") + .custom(customFieldsDraft); + + final AssetReferenceResolver assetReferenceResolver = + new AssetReferenceResolver(syncOptions, typeService); + + assertThat(assetReferenceResolver.resolveCustomTypeReference(assetDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on AssetDraft with key:'%s'. Reason: %s", assetDraftBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - public void resolveCustomTypeReference_WithNonNullIdOnCustomTypeResId_ShouldResolveCustomTypeReference() { - // Preparation - final String customTypeId = UUID.randomUUID().toString(); - final AssetDraftBuilder assetDraftBuilder = - AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .key("assetKey") - .custom(CustomFieldsDraft.ofTypeIdAndJson(customTypeId, emptyMap())); - - final AssetReferenceResolver assetReferenceResolver = new AssetReferenceResolver(syncOptions, typeService); - - // Test - final AssetDraftBuilder resolvedDraftBuilder = - assetReferenceResolver.resolveCustomTypeReference(assetDraftBuilder) - .toCompletableFuture().join(); - - // Assertion - assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); - assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo(customTypeId); - } - - @Test - void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeReference_ShouldCompleteExceptionally() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft.ofTypeKeyAndJson("", new HashMap<>()); - final AssetDraftBuilder assetDraftBuilder = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .key("assetKey") - .custom(customFieldsDraft); - - final AssetReferenceResolver assetReferenceResolver = new AssetReferenceResolver(syncOptions, typeService); - - assertThat(assetReferenceResolver.resolveCustomTypeReference(assetDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on AssetDraft with key:'%s'. Reason: %s", + } + + @Test + public void + resolveCustomTypeReference_WithNonNullIdOnCustomTypeResId_ShouldResolveCustomTypeReference() { + // Preparation + final String customTypeId = UUID.randomUUID().toString(); + final AssetDraftBuilder assetDraftBuilder = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) + .key("assetKey") + .custom(CustomFieldsDraft.ofTypeIdAndJson(customTypeId, emptyMap())); + + final AssetReferenceResolver assetReferenceResolver = + new AssetReferenceResolver(syncOptions, typeService); + + // Test + final AssetDraftBuilder resolvedDraftBuilder = + assetReferenceResolver + .resolveCustomTypeReference(assetDraftBuilder) + .toCompletableFuture() + .join(); + + // Assertion + assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); + assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo(customTypeId); + } + + @Test + void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeReference_ShouldCompleteExceptionally() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("", new HashMap<>()); + final AssetDraftBuilder assetDraftBuilder = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) + .key("assetKey") + .custom(customFieldsDraft); + + final AssetReferenceResolver assetReferenceResolver = + new AssetReferenceResolver(syncOptions, typeService); + + assertThat(assetReferenceResolver.resolveCustomTypeReference(assetDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on AssetDraft with key:'%s'. Reason: %s", assetDraftBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { - final String customTypeKey = "customTypeKey"; - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); - final AssetDraftBuilder assetDraftBuilder = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .key("assetKey") - .custom(customFieldsDraft); - - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(typeService.fetchCachedTypeId(anyString())).thenReturn(futureThrowingSphereException); - - final AssetReferenceResolver assetReferenceResolver = new AssetReferenceResolver(syncOptions, typeService); - - assertThat(assetReferenceResolver.resolveCustomTypeReference(assetDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveReferences_WithNoCustomTypeReference_ShouldNotResolveReferences() { - final AssetDraft assetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .key("assetKey") - .build(); - - final AssetReferenceResolver assetReferenceResolver = new AssetReferenceResolver(syncOptions, typeService); - - final AssetDraft referencesResolvedDraft = assetReferenceResolver.resolveReferences(assetDraft) - .toCompletableFuture().join(); - - assertThat(referencesResolvedDraft.getCustom()).isNull(); - } + } + + @Test + void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { + final String customTypeKey = "customTypeKey"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); + final AssetDraftBuilder assetDraftBuilder = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) + .key("assetKey") + .custom(customFieldsDraft); + + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(typeService.fetchCachedTypeId(anyString())).thenReturn(futureThrowingSphereException); + + final AssetReferenceResolver assetReferenceResolver = + new AssetReferenceResolver(syncOptions, typeService); + + assertThat(assetReferenceResolver.resolveCustomTypeReference(assetDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveReferences_WithNoCustomTypeReference_ShouldNotResolveReferences() { + final AssetDraft assetDraft = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")).key("assetKey").build(); + + final AssetReferenceResolver assetReferenceResolver = + new AssetReferenceResolver(syncOptions, typeService); + + final AssetDraft referencesResolvedDraft = + assetReferenceResolver.resolveReferences(assetDraft).toCompletableFuture().join(); + + assertThat(referencesResolvedDraft.getCustom()).isNull(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/helpers/BaseSyncStatisticsTest.java b/src/test/java/com/commercetools/sync/commons/helpers/BaseSyncStatisticsTest.java index d9e236aa5d..89e5899850 100644 --- a/src/test/java/com/commercetools/sync/commons/helpers/BaseSyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/commons/helpers/BaseSyncStatisticsTest.java @@ -1,119 +1,123 @@ package com.commercetools.sync.commons.helpers; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.categories.helpers.CategorySyncStatistics; import io.netty.util.internal.StringUtil; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.concurrent.TimeUnit; - -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; - class BaseSyncStatisticsTest { - private BaseSyncStatistics baseSyncStatistics; - - @BeforeEach - void setup() { - baseSyncStatistics = new CategorySyncStatistics(); - } - - @Test - void getUpdated_WithNoUpdated_ShouldReturnZero() { - assertThat(baseSyncStatistics.getUpdated()).hasValue(0); - } - - @Test - void incrementUpdated_WithNoSpecifiedTimes_ShouldIncrementUpdatedValue() { - baseSyncStatistics.incrementUpdated(); - assertThat(baseSyncStatistics.getUpdated()).hasValue(1); - } - - @Test - void incrementUpdated_WithSpecifiedTimes_ShouldIncrementUpdatedValue() { - baseSyncStatistics.incrementUpdated(5); - assertThat(baseSyncStatistics.getUpdated()).hasValue(5); - } - - @Test - void getCreated_WithNoCreated_ShouldReturnZero() { - assertThat(baseSyncStatistics.getCreated()).hasValue(0); - } - - @Test - void incrementCreated_WithNoSpecifiedTimes_ShouldIncrementCreatedValue() { - baseSyncStatistics.incrementCreated(); - assertThat(baseSyncStatistics.getCreated()).hasValue(1); - } - - @Test - void incrementCreated_WithSpecifiedTimes_ShouldIncrementCreatedValue() { - baseSyncStatistics.incrementCreated(2); - assertThat(baseSyncStatistics.getCreated()).hasValue(2); - } - - @Test - void getProcessed_WithNoProcessed_ShouldReturnZero() { - assertThat(baseSyncStatistics.getProcessed()).hasValue(0); - } - - @Test - void incrementProcessed_WithNoSpecifiedTimes_ShouldIncrementProcessedValue() { - baseSyncStatistics.incrementProcessed(); - assertThat(baseSyncStatistics.getProcessed()).hasValue(1); - } - - @Test - void incrementProcessed_WithSpecifiedTimes_ShouldIncrementProcessedValue() { - baseSyncStatistics.incrementProcessed(2); - assertThat(baseSyncStatistics.getProcessed()).hasValue(2); - } - - @Test - void getFailed_WithNoFailed_ShouldReturnZero() { - assertThat(baseSyncStatistics.getFailed()).hasValue(0); - } - - @Test - void incrementFailed_WithNoSpecifiedTimes_ShouldIncrementFailedValue() { - baseSyncStatistics.incrementFailed(); - assertThat(baseSyncStatistics.getFailed()).hasValue(1); - } - - @Test - void incrementFailed_WithSpecifiedTimes_ShouldIncrementFailedValue() { - baseSyncStatistics.incrementFailed(3); - assertThat(baseSyncStatistics.getFailed()).hasValue(3); - } - - @Test - void calculateProcessingTime_ShouldSetProcessingTimeInAllUnitsAndHumanReadableString() throws - InterruptedException { - assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInMillis()).isEqualTo(0); - assertThat(baseSyncStatistics.getLatestBatchHumanReadableProcessingTime()).isEqualTo(StringUtil.EMPTY_STRING); - - final int waitingTimeInMillis = 100; - Thread.sleep(waitingTimeInMillis); - baseSyncStatistics.calculateProcessingTime(); - - assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInDays()).isGreaterThanOrEqualTo(0); - assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInHours()).isGreaterThanOrEqualTo(0); - assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInMinutes()).isGreaterThanOrEqualTo(0); - assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInSeconds()) - .isGreaterThanOrEqualTo(waitingTimeInMillis / 1000); - assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInMillis()) - .isGreaterThanOrEqualTo(waitingTimeInMillis); - - final long remainingMillis = baseSyncStatistics.getLatestBatchProcessingTimeInMillis() + private BaseSyncStatistics baseSyncStatistics; + + @BeforeEach + void setup() { + baseSyncStatistics = new CategorySyncStatistics(); + } + + @Test + void getUpdated_WithNoUpdated_ShouldReturnZero() { + assertThat(baseSyncStatistics.getUpdated()).hasValue(0); + } + + @Test + void incrementUpdated_WithNoSpecifiedTimes_ShouldIncrementUpdatedValue() { + baseSyncStatistics.incrementUpdated(); + assertThat(baseSyncStatistics.getUpdated()).hasValue(1); + } + + @Test + void incrementUpdated_WithSpecifiedTimes_ShouldIncrementUpdatedValue() { + baseSyncStatistics.incrementUpdated(5); + assertThat(baseSyncStatistics.getUpdated()).hasValue(5); + } + + @Test + void getCreated_WithNoCreated_ShouldReturnZero() { + assertThat(baseSyncStatistics.getCreated()).hasValue(0); + } + + @Test + void incrementCreated_WithNoSpecifiedTimes_ShouldIncrementCreatedValue() { + baseSyncStatistics.incrementCreated(); + assertThat(baseSyncStatistics.getCreated()).hasValue(1); + } + + @Test + void incrementCreated_WithSpecifiedTimes_ShouldIncrementCreatedValue() { + baseSyncStatistics.incrementCreated(2); + assertThat(baseSyncStatistics.getCreated()).hasValue(2); + } + + @Test + void getProcessed_WithNoProcessed_ShouldReturnZero() { + assertThat(baseSyncStatistics.getProcessed()).hasValue(0); + } + + @Test + void incrementProcessed_WithNoSpecifiedTimes_ShouldIncrementProcessedValue() { + baseSyncStatistics.incrementProcessed(); + assertThat(baseSyncStatistics.getProcessed()).hasValue(1); + } + + @Test + void incrementProcessed_WithSpecifiedTimes_ShouldIncrementProcessedValue() { + baseSyncStatistics.incrementProcessed(2); + assertThat(baseSyncStatistics.getProcessed()).hasValue(2); + } + + @Test + void getFailed_WithNoFailed_ShouldReturnZero() { + assertThat(baseSyncStatistics.getFailed()).hasValue(0); + } + + @Test + void incrementFailed_WithNoSpecifiedTimes_ShouldIncrementFailedValue() { + baseSyncStatistics.incrementFailed(); + assertThat(baseSyncStatistics.getFailed()).hasValue(1); + } + + @Test + void incrementFailed_WithSpecifiedTimes_ShouldIncrementFailedValue() { + baseSyncStatistics.incrementFailed(3); + assertThat(baseSyncStatistics.getFailed()).hasValue(3); + } + + @Test + void calculateProcessingTime_ShouldSetProcessingTimeInAllUnitsAndHumanReadableString() + throws InterruptedException { + assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInMillis()).isEqualTo(0); + assertThat(baseSyncStatistics.getLatestBatchHumanReadableProcessingTime()) + .isEqualTo(StringUtil.EMPTY_STRING); + + final int waitingTimeInMillis = 100; + Thread.sleep(waitingTimeInMillis); + baseSyncStatistics.calculateProcessingTime(); + + assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInDays()).isGreaterThanOrEqualTo(0); + assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInHours()).isGreaterThanOrEqualTo(0); + assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInMinutes()) + .isGreaterThanOrEqualTo(0); + assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInSeconds()) + .isGreaterThanOrEqualTo(waitingTimeInMillis / 1000); + assertThat(baseSyncStatistics.getLatestBatchProcessingTimeInMillis()) + .isGreaterThanOrEqualTo(waitingTimeInMillis); + + final long remainingMillis = + baseSyncStatistics.getLatestBatchProcessingTimeInMillis() - TimeUnit.SECONDS.toMillis(baseSyncStatistics.getLatestBatchProcessingTimeInSeconds()); - assertThat(baseSyncStatistics.getLatestBatchHumanReadableProcessingTime()) - .contains(format(", %dms", remainingMillis)); - } - - @Test - void getDefaultReportMessageForResource_withResourceString_ShouldBuildCorrectSummary() { - String message = baseSyncStatistics.getDefaultReportMessageForResource("resources"); - assertThat(message).isEqualTo("Summary: 0 resources were processed in total (0 created, 0 updated and 0 " - + "failed to sync)."); - } + assertThat(baseSyncStatistics.getLatestBatchHumanReadableProcessingTime()) + .contains(format(", %dms", remainingMillis)); + } + + @Test + void getDefaultReportMessageForResource_withResourceString_ShouldBuildCorrectSummary() { + String message = baseSyncStatistics.getDefaultReportMessageForResource("resources"); + assertThat(message) + .isEqualTo( + "Summary: 0 resources were processed in total (0 created, 0 updated and 0 " + + "failed to sync)."); + } } diff --git a/src/test/java/com/commercetools/sync/commons/helpers/CategoryReferencePairTest.java b/src/test/java/com/commercetools/sync/commons/helpers/CategoryReferencePairTest.java index 56938fdaf6..3737238c6e 100644 --- a/src/test/java/com/commercetools/sync/commons/helpers/CategoryReferencePairTest.java +++ b/src/test/java/com/commercetools/sync/commons/helpers/CategoryReferencePairTest.java @@ -1,42 +1,41 @@ package com.commercetools.sync.commons.helpers; +import static org.assertj.core.api.Assertions.assertThat; + import io.sphere.sdk.categories.Category; import io.sphere.sdk.models.Reference; import io.sphere.sdk.products.CategoryOrderHints; -import org.junit.jupiter.api.Test; - import java.util.Arrays; import java.util.HashSet; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class CategoryReferencePairTest { - @Test - void of_WithBothReferencesAndCategoryOrderHints_ShouldSetBoth() { - final List> categoryReferences = - Arrays.asList(Category.referenceOfId("cat1"), Category.referenceOfId("cat2")); - final CategoryOrderHints categoryOrderHints = CategoryOrderHints.of("cat1", "0.12"); - final CategoryReferencePair categoryReferencePair = - CategoryReferencePair.of(new HashSet<>(categoryReferences), categoryOrderHints); - - assertThat(categoryReferencePair).isNotNull(); - assertThat(categoryReferencePair.getCategoryResourceIdentifiers()) - .containsExactlyInAnyOrderElementsOf(categoryReferences); - assertThat(categoryReferencePair.getCategoryOrderHints()).isEqualTo(categoryOrderHints); - } - - @Test - void of_WithNullCategoryOrderHints_ShouldSetReferencesOnly() { - final List> categoryReferences = - Arrays.asList(Category.referenceOfId("cat1"), Category.referenceOfId("cat2")); - final CategoryReferencePair categoryReferencePair = - CategoryReferencePair.of(new HashSet<>(categoryReferences), null); - - assertThat(categoryReferencePair).isNotNull(); - assertThat(categoryReferencePair.getCategoryResourceIdentifiers()) - .containsExactlyInAnyOrderElementsOf(categoryReferences); - assertThat(categoryReferencePair.getCategoryOrderHints()).isNull(); - } + @Test + void of_WithBothReferencesAndCategoryOrderHints_ShouldSetBoth() { + final List> categoryReferences = + Arrays.asList(Category.referenceOfId("cat1"), Category.referenceOfId("cat2")); + final CategoryOrderHints categoryOrderHints = CategoryOrderHints.of("cat1", "0.12"); + final CategoryReferencePair categoryReferencePair = + CategoryReferencePair.of(new HashSet<>(categoryReferences), categoryOrderHints); + + assertThat(categoryReferencePair).isNotNull(); + assertThat(categoryReferencePair.getCategoryResourceIdentifiers()) + .containsExactlyInAnyOrderElementsOf(categoryReferences); + assertThat(categoryReferencePair.getCategoryOrderHints()).isEqualTo(categoryOrderHints); + } + + @Test + void of_WithNullCategoryOrderHints_ShouldSetReferencesOnly() { + final List> categoryReferences = + Arrays.asList(Category.referenceOfId("cat1"), Category.referenceOfId("cat2")); + final CategoryReferencePair categoryReferencePair = + CategoryReferencePair.of(new HashSet<>(categoryReferences), null); + + assertThat(categoryReferencePair).isNotNull(); + assertThat(categoryReferencePair.getCategoryResourceIdentifiers()) + .containsExactlyInAnyOrderElementsOf(categoryReferences); + assertThat(categoryReferencePair.getCategoryOrderHints()).isNull(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequestTest.java b/src/test/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequestTest.java index eb32fdb7c2..f4868cc29e 100644 --- a/src/test/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequestTest.java +++ b/src/test/java/com/commercetools/sync/commons/helpers/ResourceKeyIdGraphQlRequestTest.java @@ -1,5 +1,9 @@ package com.commercetools.sync.commons.helpers; +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.commercetools.sync.commons.models.GraphQlBaseRequest; import com.commercetools.sync.commons.models.GraphQlQueryResources; import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; @@ -8,213 +12,211 @@ import io.sphere.sdk.http.HttpMethod; import io.sphere.sdk.http.HttpResponse; import io.sphere.sdk.http.StringHttpRequestBody; -import org.junit.jupiter.api.Test; - import java.util.Collections; import java.util.HashSet; import java.util.Set; - -import static java.util.Collections.singleton; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.Test; class ResourceKeyIdGraphQlRequestTest { - @Test - void newGraphQlRequest_WithNullKeys_ShouldThrowNullPointerException() { - //test & assertion - assertThatThrownBy( + @Test + void newGraphQlRequest_WithNullKeys_ShouldThrowNullPointerException() { + // test & assertion + assertThatThrownBy( () -> new ResourceKeyIdGraphQlRequest(null, GraphQlQueryResources.CATEGORIES)) - .isExactlyInstanceOf(NullPointerException.class); - } - - @Test - void httpRequestIntent_WithEmptyKeys_ShouldReturnQueryStringWithEmptyKeysClause() { - //preparation - final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(Collections.emptySet(), GraphQlQueryResources.CATEGORIES); - - //test - final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); - - //assertions - assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); - assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); - final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); - assertThat(requestBody).isNotNull(); - assertThat(requestBody.getString()) - .isEqualTo("{\"query\": \"{categories(limit: 500, where: \\\"key" + .isExactlyInstanceOf(NullPointerException.class); + } + + @Test + void httpRequestIntent_WithEmptyKeys_ShouldReturnQueryStringWithEmptyKeysClause() { + // preparation + final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(Collections.emptySet(), GraphQlQueryResources.CATEGORIES); + + // test + final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); + + // assertions + assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); + assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); + final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); + assertThat(requestBody).isNotNull(); + assertThat(requestBody.getString()) + .isEqualTo( + "{\"query\": \"{categories(limit: 500, where: \\\"key" + " in (\\\\\\\"\\\\\\\")\\\", sort: [\\\"id asc\\\"]) { results { id key } }}\"}"); - } - - @Test - void httpRequestIntent_WithKeys_ShouldReturnCorrectQueryString() { - //preparation - final Set keysToSearch = new HashSet<>(); - keysToSearch.add("key1"); - keysToSearch.add("key2"); - final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(keysToSearch, GraphQlQueryResources.CATEGORIES); - - //test - final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); - - //assertions - assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); - assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); - final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); - assertThat(requestBody).isNotNull(); - assertThat(requestBody.getString()) - .isEqualTo("{\"query\": \"{categories(limit: 500, where: \\\"key" + } + + @Test + void httpRequestIntent_WithKeys_ShouldReturnCorrectQueryString() { + // preparation + final Set keysToSearch = new HashSet<>(); + keysToSearch.add("key1"); + keysToSearch.add("key2"); + final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(keysToSearch, GraphQlQueryResources.CATEGORIES); + + // test + final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); + + // assertions + assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); + assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); + final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); + assertThat(requestBody).isNotNull(); + assertThat(requestBody.getString()) + .isEqualTo( + "{\"query\": \"{categories(limit: 500, where: \\\"key" + " in (\\\\\\\"key1\\\\\\\", \\\\\\\"key2\\\\\\\")\\\", sort: [\\\"id asc\\\"]) { results { id key } " + "}}\"}"); - } - - @Test - void httpRequestIntent_WithSomeEmptyAndNullKeys_ShouldReturnCorrectQueryString() { - //preparation - final Set keysToSearch = new HashSet<>(); - keysToSearch.add("key1"); - keysToSearch.add(""); - keysToSearch.add("key2"); - keysToSearch.add(null); - final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(keysToSearch, GraphQlQueryResources.CATEGORIES); - - //test - final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); - - //assertions - assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); - assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); - final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); - assertThat(requestBody).isNotNull(); - assertThat(requestBody.getString()) - .isEqualTo("{\"query\": \"{categories(limit: 500, where: \\\"key" + } + + @Test + void httpRequestIntent_WithSomeEmptyAndNullKeys_ShouldReturnCorrectQueryString() { + // preparation + final Set keysToSearch = new HashSet<>(); + keysToSearch.add("key1"); + keysToSearch.add(""); + keysToSearch.add("key2"); + keysToSearch.add(null); + final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(keysToSearch, GraphQlQueryResources.CATEGORIES); + + // test + final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); + + // assertions + assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); + assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); + final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); + assertThat(requestBody).isNotNull(); + assertThat(requestBody.getString()) + .isEqualTo( + "{\"query\": \"{categories(limit: 500, where: \\\"key" + " in (\\\\\\\"key1\\\\\\\", \\\\\\\"key2\\\\\\\")\\\", sort: [\\\"id asc\\\"]) { results { id key } " + "}}\"}"); - } - - @Test - void httpRequestIntent_WithKeyAndExplicitLimit_ShouldReturnCorrectQueryString() { - //preparation - final GraphQlBaseRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(singleton("key1"), GraphQlQueryResources.CATEGORIES).withLimit(10); - - //test - final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); - - //assertions - assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); - assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); - final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); - assertThat(requestBody).isNotNull(); - assertThat(requestBody.getString()) - .isEqualTo("{\"query\": \"{categories(limit: 10, where: \\\"key" + } + + @Test + void httpRequestIntent_WithKeyAndExplicitLimit_ShouldReturnCorrectQueryString() { + // preparation + final GraphQlBaseRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(singleton("key1"), GraphQlQueryResources.CATEGORIES) + .withLimit(10); + + // test + final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); + + // assertions + assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); + assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); + final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); + assertThat(requestBody).isNotNull(); + assertThat(requestBody.getString()) + .isEqualTo( + "{\"query\": \"{categories(limit: 10, where: \\\"key" + " in (\\\\\\\"key1\\\\\\\")\\\", sort: [\\\"id asc\\\"]) { results { id key } " + "}}\"}"); - } - - @Test - void httpRequestIntent_WithKeyAndPredicate_ShouldReturnCorrectQueryString() { - //preparation - final GraphQlBaseRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(singleton("key1"), GraphQlQueryResources.CATEGORIES) - .withPredicate("id > \\\\\\\"id" - + "\\\\\\\""); - - //test - final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); - - //assertions - assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); - assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); - final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); - assertThat(requestBody).isNotNull(); - assertThat(requestBody.getString()) - .isEqualTo("{\"query\": \"{categories(limit: 500, where: \\\"key" + } + + @Test + void httpRequestIntent_WithKeyAndPredicate_ShouldReturnCorrectQueryString() { + // preparation + final GraphQlBaseRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(singleton("key1"), GraphQlQueryResources.CATEGORIES) + .withPredicate("id > \\\\\\\"id" + "\\\\\\\""); + + // test + final HttpRequestIntent httpRequestIntent = resourceKeyIdGraphQlRequest.httpRequestIntent(); + + // assertions + assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); + assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); + final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); + assertThat(requestBody).isNotNull(); + assertThat(requestBody.getString()) + .isEqualTo( + "{\"query\": \"{categories(limit: 500, where: \\\"key" + " in (\\\\\\\"key1\\\\\\\") AND id > \\\\\\\"id\\\\\\\"\\\", sort: [\\\"id asc\\\"])" + " { results { id key } }}\"}"); - } - - @Test - void deserialize_WithEmptyResult_ShouldReturnNull() throws JsonProcessingException { - //preparation - final HttpResponse httpResponse = HttpResponse.of(200, "null"); - final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(singleton("key-1"), GraphQlQueryResources.CATEGORIES); - - //test - final ResourceKeyIdGraphQlResult result = resourceKeyIdGraphQlRequest.deserialize(httpResponse); - - //assertions - assertThat(result).isNull(); - } - - @Test - void deserialize_WithEmptyResult_ShouldDeserializeCorrectly() throws JsonProcessingException { - //preparation - String jsonAsString = "{\"data\":{\"categories\":{\"results\":[]}}}"; - - final HttpResponse httpResponse = HttpResponse.of(200, jsonAsString); - - final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(singleton("key-1"), GraphQlQueryResources.CATEGORIES); - - //test - final ResourceKeyIdGraphQlResult result = resourceKeyIdGraphQlRequest.deserialize(httpResponse); - - //assertions - assertThat(result).isNotNull(); - assertThat(result.getResults()).isEmpty(); - } - - @Test - void deserialize_WithSingleResult_ShouldReturnSingletonMap() throws JsonProcessingException { - //preparation - String jsonAsString = "{\"data\":{\"categories\":{\"results\":[{\"id\":\"id-1\",\"key\":\"key-1\"}]}}}"; - - final HttpResponse httpResponse = HttpResponse.of(200, jsonAsString); - - final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(singleton("key-1"), GraphQlQueryResources.CATEGORIES); - - //test - final ResourceKeyIdGraphQlResult result = resourceKeyIdGraphQlRequest.deserialize(httpResponse); - - //assertions - assertThat(result).isNotNull(); - assertThat(result.getResults()).hasSize(1); - assertThat(result.getResults()) - .extracting("key") - .containsExactly("key-1"); - assertThat(result.getResults()) - .extracting("id") - .containsExactly("id-1"); - } - - @Test - void deserialize_WithMultipleResults_ShouldReturnCorrectResult() throws JsonProcessingException { - //preparation - String jsonAsString = "{\"data\":{\"categories\":{\"results\":[{\"id\":\"id-1\",\"key\":\"key-1\"}," + } + + @Test + void deserialize_WithEmptyResult_ShouldReturnNull() throws JsonProcessingException { + // preparation + final HttpResponse httpResponse = HttpResponse.of(200, "null"); + final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(singleton("key-1"), GraphQlQueryResources.CATEGORIES); + + // test + final ResourceKeyIdGraphQlResult result = resourceKeyIdGraphQlRequest.deserialize(httpResponse); + + // assertions + assertThat(result).isNull(); + } + + @Test + void deserialize_WithEmptyResult_ShouldDeserializeCorrectly() throws JsonProcessingException { + // preparation + String jsonAsString = "{\"data\":{\"categories\":{\"results\":[]}}}"; + + final HttpResponse httpResponse = HttpResponse.of(200, jsonAsString); + + final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(singleton("key-1"), GraphQlQueryResources.CATEGORIES); + + // test + final ResourceKeyIdGraphQlResult result = resourceKeyIdGraphQlRequest.deserialize(httpResponse); + + // assertions + assertThat(result).isNotNull(); + assertThat(result.getResults()).isEmpty(); + } + + @Test + void deserialize_WithSingleResult_ShouldReturnSingletonMap() throws JsonProcessingException { + // preparation + String jsonAsString = + "{\"data\":{\"categories\":{\"results\":[{\"id\":\"id-1\",\"key\":\"key-1\"}]}}}"; + + final HttpResponse httpResponse = HttpResponse.of(200, jsonAsString); + + final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(singleton("key-1"), GraphQlQueryResources.CATEGORIES); + + // test + final ResourceKeyIdGraphQlResult result = resourceKeyIdGraphQlRequest.deserialize(httpResponse); + + // assertions + assertThat(result).isNotNull(); + assertThat(result.getResults()).hasSize(1); + assertThat(result.getResults()).extracting("key").containsExactly("key-1"); + assertThat(result.getResults()).extracting("id").containsExactly("id-1"); + } + + @Test + void deserialize_WithMultipleResults_ShouldReturnCorrectResult() throws JsonProcessingException { + // preparation + String jsonAsString = + "{\"data\":{\"categories\":{\"results\":[{\"id\":\"id-1\",\"key\":\"key-1\"}," + "{\"id\":\"id-2\",\"key\":\"key-2\"},{\"id\":\"id-3\",\"key\":\"key-3\"}]}}}"; - final HttpResponse httpResponse = HttpResponse.of(200, jsonAsString); + final HttpResponse httpResponse = HttpResponse.of(200, jsonAsString); - final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(singleton("key-1"), GraphQlQueryResources.CATEGORIES); + final ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(singleton("key-1"), GraphQlQueryResources.CATEGORIES); - //test - final ResourceKeyIdGraphQlResult result = resourceKeyIdGraphQlRequest.deserialize(httpResponse); + // test + final ResourceKeyIdGraphQlResult result = resourceKeyIdGraphQlRequest.deserialize(httpResponse); - //assertions - assertThat(result).isNotNull(); - assertThat(result.getResults()).hasSize(3); - assertThat(result.getResults()) - .extracting("key") - .containsExactlyInAnyOrder("key-1", "key-2", "key-3"); - assertThat(result.getResults()) - .extracting("id") - .containsExactlyInAnyOrder("id-1", "id-2", "id-3"); - } + // assertions + assertThat(result).isNotNull(); + assertThat(result.getResults()).hasSize(3); + assertThat(result.getResults()) + .extracting("key") + .containsExactlyInAnyOrder("key-1", "key-2", "key-3"); + assertThat(result.getResults()) + .extracting("id") + .containsExactlyInAnyOrder("id-1", "id-2", "id-3"); + } } diff --git a/src/test/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequestTest.java b/src/test/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequestTest.java index 5d11d6f857..42d6e4a66d 100644 --- a/src/test/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequestTest.java +++ b/src/test/java/com/commercetools/sync/commons/models/FetchCustomObjectsGraphQlRequestTest.java @@ -1,84 +1,86 @@ package com.commercetools.sync.commons.models; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.core.JsonProcessingException; import io.sphere.sdk.client.HttpRequestIntent; import io.sphere.sdk.http.HttpMethod; import io.sphere.sdk.http.HttpResponse; import io.sphere.sdk.http.StringHttpRequestBody; -import org.junit.jupiter.api.Test; - import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class FetchCustomObjectsGraphQlRequestTest { - @Test - void httpRequestIntent_WithoutPredicate_ShouldReturnCorrectQueryString() { - //preparation - final Instant lastModifiedAt = Instant.parse("2021-01-07T00:00:00Z"); - final FetchCustomObjectsGraphQlRequest request = - new FetchCustomObjectsGraphQlRequest("container", lastModifiedAt); + @Test + void httpRequestIntent_WithoutPredicate_ShouldReturnCorrectQueryString() { + // preparation + final Instant lastModifiedAt = Instant.parse("2021-01-07T00:00:00Z"); + final FetchCustomObjectsGraphQlRequest request = + new FetchCustomObjectsGraphQlRequest("container", lastModifiedAt); - //test - final HttpRequestIntent httpRequestIntent = request.httpRequestIntent(); + // test + final HttpRequestIntent httpRequestIntent = request.httpRequestIntent(); - //assertions - assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); - assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); - final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); - assertThat(requestBody).isNotNull(); - assertThat(requestBody.getString()) - .isEqualTo("{\"query\": \"{customObjects(container: \\\"container\\\", limit: 500, " + // assertions + assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); + assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); + final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); + assertThat(requestBody).isNotNull(); + assertThat(requestBody.getString()) + .isEqualTo( + "{\"query\": \"{customObjects(container: \\\"container\\\", limit: 500, " + "where: \\\"lastModifiedAt < \\\\\\\"2021-01-07T00:00:00Z\\\\\\\"\\\", " + "sort: [\\\"id asc\\\"]) { results { id key } }}\"}"); - } + } - @Test - void httpRequestIntent_WithPredicate_ShouldReturnCorrectQueryString() { - //preparation - final Instant lastModifiedAt = Instant.parse("2021-01-07T00:00:00Z"); - final GraphQlBaseRequest request = - new FetchCustomObjectsGraphQlRequest("container", lastModifiedAt) - .withPredicate("id > \\\\\\\"id\\\\\\\""); + @Test + void httpRequestIntent_WithPredicate_ShouldReturnCorrectQueryString() { + // preparation + final Instant lastModifiedAt = Instant.parse("2021-01-07T00:00:00Z"); + final GraphQlBaseRequest request = + new FetchCustomObjectsGraphQlRequest("container", lastModifiedAt) + .withPredicate("id > \\\\\\\"id\\\\\\\""); - //test - final HttpRequestIntent httpRequestIntent = request.httpRequestIntent(); + // test + final HttpRequestIntent httpRequestIntent = request.httpRequestIntent(); - //assertions - assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); - assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); - final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); - assertThat(requestBody).isNotNull(); - assertThat(requestBody.getString()) - .isEqualTo("{\"query\": \"{customObjects(container: \\\"container\\\", limit: 500, " + // assertions + assertThat(httpRequestIntent.getBody()).isExactlyInstanceOf(StringHttpRequestBody.class); + assertThat(httpRequestIntent.getHttpMethod()).isEqualByComparingTo(HttpMethod.POST); + final StringHttpRequestBody requestBody = (StringHttpRequestBody) httpRequestIntent.getBody(); + assertThat(requestBody).isNotNull(); + assertThat(requestBody.getString()) + .isEqualTo( + "{\"query\": \"{customObjects(container: \\\"container\\\", limit: 500, " + "where: \\\"lastModifiedAt < \\\\\\\"2021-01-07T00:00:00Z\\\\\\\"" + " AND id > \\\\\\\"id\\\\\\\"\\\", sort: [\\\"id asc\\\"])" + " { results { id key } }}\"}"); - } + } - @Test - void deserialize_WithMultipleResults_ShouldReturnCorrectResult() throws JsonProcessingException { - //preparation - String jsonAsString = "{\"data\":{\"customObjects\":{\"results\":[{\"id\":\"id-1\",\"key\":\"key-1\"}," + @Test + void deserialize_WithMultipleResults_ShouldReturnCorrectResult() throws JsonProcessingException { + // preparation + String jsonAsString = + "{\"data\":{\"customObjects\":{\"results\":[{\"id\":\"id-1\",\"key\":\"key-1\"}," + "{\"id\":\"id-2\",\"key\":\"key-2\"},{\"id\":\"id-3\",\"key\":\"key-3\"}]}}}"; - final HttpResponse httpResponse = HttpResponse.of(200, jsonAsString); + final HttpResponse httpResponse = HttpResponse.of(200, jsonAsString); - final FetchCustomObjectsGraphQlRequest request = - new FetchCustomObjectsGraphQlRequest("containerName", Instant.now()); + final FetchCustomObjectsGraphQlRequest request = + new FetchCustomObjectsGraphQlRequest("containerName", Instant.now()); - //test - final ResourceKeyIdGraphQlResult result = request.deserialize(httpResponse); + // test + final ResourceKeyIdGraphQlResult result = request.deserialize(httpResponse); - //assertions - assertThat(result).isNotNull(); - assertThat(result.getResults()).hasSize(3); - assertThat(result.getResults()) - .extracting("key") - .containsExactlyInAnyOrder("key-1", "key-2", "key-3"); - assertThat(result.getResults()) - .extracting("id") - .containsExactlyInAnyOrder("id-1", "id-2", "id-3"); - } + // assertions + assertThat(result).isNotNull(); + assertThat(result.getResults()).hasSize(3); + assertThat(result.getResults()) + .extracting("key") + .containsExactlyInAnyOrder("key-1", "key-2", "key-3"); + assertThat(result.getResults()) + .extracting("id") + .containsExactlyInAnyOrder("id-1", "id-2", "id-3"); + } } diff --git a/src/test/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTest.java b/src/test/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTest.java index fac436e4e5..0b2d8760ec 100644 --- a/src/test/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTest.java +++ b/src/test/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTest.java @@ -1,10 +1,5 @@ package com.commercetools.sync.commons.models; -import io.sphere.sdk.products.ProductDraft; -import org.junit.jupiter.api.Test; - -import java.util.HashSet; - import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; @@ -15,215 +10,213 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class WaitingToBeResolvedTest { +import io.sphere.sdk.products.ProductDraft; +import java.util.HashSet; +import org.junit.jupiter.api.Test; +class WaitingToBeResolvedTest { - @Test - void setProductDraft_WithNonNullProductDraft_ShouldSetProductDraft() { - // preparation - final ProductDraft productDraft = mock(ProductDraft.class); - final WaitingToBeResolved waitingToBeResolved = new WaitingToBeResolved(); - - // test - waitingToBeResolved.setProductDraft(productDraft); - - // assertions - assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft); - } - - @Test - void setMissingReferencedProductKeys_WithNonNullSet_ShouldSetTheSet() { - // preparation - final WaitingToBeResolved waitingToBeResolved = new WaitingToBeResolved(); - - // test - waitingToBeResolved.setMissingReferencedProductKeys(emptySet()); - - // assertions - assertThat(waitingToBeResolved.getMissingReferencedProductKeys()).isEqualTo(emptySet()); - } - - @Test - void equals_WithSameRef_ShouldReturnTrue() { - // preparation - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); - final WaitingToBeResolved other = waitingToBeResolved; - - // test - boolean result = waitingToBeResolved.equals(other); - - // assertions - assertTrue(result); - } - - @Test - void equals_WithDiffType_ShouldReturnFalse() { - // preparation - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); - final Object other = new Object(); - - // test - boolean result = waitingToBeResolved.equals(other); - - // assertions - assertFalse(result); - } - - - @Test - void equals_WithEqualObjects_ShouldReturnTrue() { - // preparation - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); - final WaitingToBeResolved other = - new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); - - // test - boolean result = waitingToBeResolved.equals(other); - - // assertions - assertTrue(result); - } - - @Test - void equals_WithDifferentMissingRefKeys_ShouldReturnFalse() { - // preparation - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); - final WaitingToBeResolved other = - new WaitingToBeResolved(mock(ProductDraft.class), singleton("foo")); - - // test - boolean result = waitingToBeResolved.equals(other); - - // assertions - assertFalse(result); - } - - @Test - void equals_WithDifferentProductDraft_ShouldReturnFalse() { - // preparation - final ProductDraft productDraft = mock(ProductDraft.class); - final ProductDraft productDraft1 = mock(ProductDraft.class); - when(productDraft1.getKey()).thenReturn("foo"); - - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(productDraft, new HashSet<>()); - final WaitingToBeResolved other = - new WaitingToBeResolved(productDraft1, new HashSet<>()); - - // test - boolean result = waitingToBeResolved.equals(other); - - // assertions - assertFalse(result); - } - - @Test - void equals_WithCompletelyDifferentFieldValues_ShouldReturnFalse() { - // preparation - final ProductDraft productDraft = mock(ProductDraft.class); - final ProductDraft productDraft1 = mock(ProductDraft.class); - when(productDraft1.getKey()).thenReturn("foo"); - - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(productDraft, singleton("foo")); - final WaitingToBeResolved other = - new WaitingToBeResolved(productDraft1, singleton("bar")); - - // test - boolean result = waitingToBeResolved.equals(other); - - // assertions - assertFalse(result); - } - - @Test - void hashCode_withSameInstances_ShouldBeEquals() { - // preparation - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); - final WaitingToBeResolved other = waitingToBeResolved; - - // test - final int hash1 = waitingToBeResolved.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertEquals(hash1, hash2); - } - - @Test - void hashCode_withSameProductKeyAndSameRefSet_ShouldBeEquals() { - // preparation - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); - final WaitingToBeResolved other = - new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); - - // test - final int hash1 = waitingToBeResolved.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertEquals(hash1, hash2); - } - - @Test - void hashCode_withDifferentProductKeyAndSameRefSet_ShouldNotBeEquals() { - // preparation - final ProductDraft productDraft = mock(ProductDraft.class); - final ProductDraft productDraft1 = mock(ProductDraft.class); - when(productDraft1.getKey()).thenReturn("foo"); - - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(productDraft, new HashSet<>()); - final WaitingToBeResolved other = - new WaitingToBeResolved(productDraft1, new HashSet<>()); - - // test - final int hash1 = waitingToBeResolved.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertNotEquals(hash1, hash2); - } - - @Test - void hashCode_withSameProductKeyAndDiffRefSet_ShouldNotBeEquals() { - // preparation - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); - final WaitingToBeResolved other = - new WaitingToBeResolved(mock(ProductDraft.class), singleton("foo")); - - // test - final int hash1 = waitingToBeResolved.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertNotEquals(hash1, hash2); - } - - @Test - void hashCode_withCompletelyDifferentFields_ShouldNotBeEquals() { - // preparation - final ProductDraft productDraft = mock(ProductDraft.class); - final ProductDraft productDraft1 = mock(ProductDraft.class); - when(productDraft1.getKey()).thenReturn("foo"); - - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(productDraft, singleton("foo")); - final WaitingToBeResolved other = - new WaitingToBeResolved(productDraft1, singleton("bar")); - - // test - final int hash1 = waitingToBeResolved.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertNotEquals(hash1, hash2); - } -} \ No newline at end of file + @Test + void setProductDraft_WithNonNullProductDraft_ShouldSetProductDraft() { + // preparation + final ProductDraft productDraft = mock(ProductDraft.class); + final WaitingToBeResolved waitingToBeResolved = new WaitingToBeResolved(); + + // test + waitingToBeResolved.setProductDraft(productDraft); + + // assertions + assertThat(waitingToBeResolved.getProductDraft()).isEqualTo(productDraft); + } + + @Test + void setMissingReferencedProductKeys_WithNonNullSet_ShouldSetTheSet() { + // preparation + final WaitingToBeResolved waitingToBeResolved = new WaitingToBeResolved(); + + // test + waitingToBeResolved.setMissingReferencedProductKeys(emptySet()); + + // assertions + assertThat(waitingToBeResolved.getMissingReferencedProductKeys()).isEqualTo(emptySet()); + } + + @Test + void equals_WithSameRef_ShouldReturnTrue() { + // preparation + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); + final WaitingToBeResolved other = waitingToBeResolved; + + // test + boolean result = waitingToBeResolved.equals(other); + + // assertions + assertTrue(result); + } + + @Test + void equals_WithDiffType_ShouldReturnFalse() { + // preparation + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); + final Object other = new Object(); + + // test + boolean result = waitingToBeResolved.equals(other); + + // assertions + assertFalse(result); + } + + @Test + void equals_WithEqualObjects_ShouldReturnTrue() { + // preparation + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); + final WaitingToBeResolved other = + new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); + + // test + boolean result = waitingToBeResolved.equals(other); + + // assertions + assertTrue(result); + } + + @Test + void equals_WithDifferentMissingRefKeys_ShouldReturnFalse() { + // preparation + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); + final WaitingToBeResolved other = + new WaitingToBeResolved(mock(ProductDraft.class), singleton("foo")); + + // test + boolean result = waitingToBeResolved.equals(other); + + // assertions + assertFalse(result); + } + + @Test + void equals_WithDifferentProductDraft_ShouldReturnFalse() { + // preparation + final ProductDraft productDraft = mock(ProductDraft.class); + final ProductDraft productDraft1 = mock(ProductDraft.class); + when(productDraft1.getKey()).thenReturn("foo"); + + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(productDraft, new HashSet<>()); + final WaitingToBeResolved other = new WaitingToBeResolved(productDraft1, new HashSet<>()); + + // test + boolean result = waitingToBeResolved.equals(other); + + // assertions + assertFalse(result); + } + + @Test + void equals_WithCompletelyDifferentFieldValues_ShouldReturnFalse() { + // preparation + final ProductDraft productDraft = mock(ProductDraft.class); + final ProductDraft productDraft1 = mock(ProductDraft.class); + when(productDraft1.getKey()).thenReturn("foo"); + + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(productDraft, singleton("foo")); + final WaitingToBeResolved other = new WaitingToBeResolved(productDraft1, singleton("bar")); + + // test + boolean result = waitingToBeResolved.equals(other); + + // assertions + assertFalse(result); + } + + @Test + void hashCode_withSameInstances_ShouldBeEquals() { + // preparation + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); + final WaitingToBeResolved other = waitingToBeResolved; + + // test + final int hash1 = waitingToBeResolved.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertEquals(hash1, hash2); + } + + @Test + void hashCode_withSameProductKeyAndSameRefSet_ShouldBeEquals() { + // preparation + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); + final WaitingToBeResolved other = + new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); + + // test + final int hash1 = waitingToBeResolved.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertEquals(hash1, hash2); + } + + @Test + void hashCode_withDifferentProductKeyAndSameRefSet_ShouldNotBeEquals() { + // preparation + final ProductDraft productDraft = mock(ProductDraft.class); + final ProductDraft productDraft1 = mock(ProductDraft.class); + when(productDraft1.getKey()).thenReturn("foo"); + + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(productDraft, new HashSet<>()); + final WaitingToBeResolved other = new WaitingToBeResolved(productDraft1, new HashSet<>()); + + // test + final int hash1 = waitingToBeResolved.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertNotEquals(hash1, hash2); + } + + @Test + void hashCode_withSameProductKeyAndDiffRefSet_ShouldNotBeEquals() { + // preparation + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(mock(ProductDraft.class), new HashSet<>()); + final WaitingToBeResolved other = + new WaitingToBeResolved(mock(ProductDraft.class), singleton("foo")); + + // test + final int hash1 = waitingToBeResolved.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertNotEquals(hash1, hash2); + } + + @Test + void hashCode_withCompletelyDifferentFields_ShouldNotBeEquals() { + // preparation + final ProductDraft productDraft = mock(ProductDraft.class); + final ProductDraft productDraft1 = mock(ProductDraft.class); + when(productDraft1.getKey()).thenReturn("foo"); + + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(productDraft, singleton("foo")); + final WaitingToBeResolved other = new WaitingToBeResolved(productDraft1, singleton("bar")); + + // test + final int hash1 = waitingToBeResolved.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertNotEquals(hash1, hash2); + } +} diff --git a/src/test/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTransitionsTest.java b/src/test/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTransitionsTest.java index aa657d041a..3beeca0d41 100644 --- a/src/test/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTransitionsTest.java +++ b/src/test/java/com/commercetools/sync/commons/models/WaitingToBeResolvedTransitionsTest.java @@ -1,10 +1,5 @@ package com.commercetools.sync.commons.models; -import io.sphere.sdk.states.StateDraft; -import org.junit.jupiter.api.Test; - -import java.util.HashSet; - import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; @@ -15,215 +10,219 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class WaitingToBeResolvedTransitionsTest { +import io.sphere.sdk.states.StateDraft; +import java.util.HashSet; +import org.junit.jupiter.api.Test; +class WaitingToBeResolvedTransitionsTest { - @Test - void setProductDraft_WithNonNullProductDraft_ShouldSetProductDraft() { - // preparation - final StateDraft stateDraft = mock(StateDraft.class); - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = new WaitingToBeResolvedTransitions(); - - // test - waitingToBeResolvedTransition.setStateDraft(stateDraft); - - // assertions - assertThat(waitingToBeResolvedTransition.getStateDraft()).isEqualTo(stateDraft); - } - - @Test - void setMissingReferencedProductKeys_WithNonNullSet_ShouldSetTheSet() { - // preparation - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = new WaitingToBeResolvedTransitions(); - - // test - waitingToBeResolvedTransition.setMissingTransitionStateKeys(emptySet()); - - // assertions - assertThat(waitingToBeResolvedTransition.getMissingTransitionStateKeys()).isEqualTo(emptySet()); - } - - @Test - void equals_WithSameRef_ShouldReturnTrue() { - // preparation - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); - final WaitingToBeResolvedTransitions other = waitingToBeResolvedTransition; - - // test - boolean result = waitingToBeResolvedTransition.equals(other); - - // assertions - assertTrue(result); - } - - @Test - void equals_WithDiffType_ShouldReturnFalse() { - // preparation - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); - final Object other = new Object(); - - // test - boolean result = waitingToBeResolvedTransition.equals(other); - - // assertions - assertFalse(result); - } - - - @Test - void equals_WithEqualObjects_ShouldReturnTrue() { - // preparation - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); - final WaitingToBeResolvedTransitions other = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); - - // test - boolean result = waitingToBeResolvedTransition.equals(other); - - // assertions - assertTrue(result); - } - - @Test - void equals_WithDifferentMissingRefKeys_ShouldReturnFalse() { - // preparation - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); - final WaitingToBeResolvedTransitions other = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), singleton("foo")); - - // test - boolean result = waitingToBeResolvedTransition.equals(other); - - // assertions - assertFalse(result); - } - - @Test - void equals_WithDifferentProductDraft_ShouldReturnFalse() { - // preparation - final StateDraft stateDraft = mock(StateDraft.class); - final StateDraft stateDraft1 = mock(StateDraft.class); - when(stateDraft1.getKey()).thenReturn("foo"); - - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(stateDraft, new HashSet<>()); - final WaitingToBeResolvedTransitions other = - new WaitingToBeResolvedTransitions(stateDraft1, new HashSet<>()); - - // test - boolean result = waitingToBeResolvedTransition.equals(other); - - // assertions - assertFalse(result); - } - - @Test - void equals_WithCompletelyDifferentFieldValues_ShouldReturnFalse() { - // preparation - final StateDraft stateDraft = mock(StateDraft.class); - final StateDraft stateDraft1 = mock(StateDraft.class); - when(stateDraft1.getKey()).thenReturn("foo"); - - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(stateDraft, singleton("foo")); - final WaitingToBeResolvedTransitions other = - new WaitingToBeResolvedTransitions(stateDraft1, singleton("bar")); - - // test - boolean result = waitingToBeResolvedTransition.equals(other); - - // assertions - assertFalse(result); - } - - @Test - void hashCode_withSameInstances_ShouldBeEquals() { - // preparation - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); - final WaitingToBeResolvedTransitions other = waitingToBeResolvedTransition; - - // test - final int hash1 = waitingToBeResolvedTransition.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertEquals(hash1, hash2); - } - - @Test - void hashCode_withSameProductKeyAndSameRefSet_ShouldBeEquals() { - // preparation - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); - final WaitingToBeResolvedTransitions other = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); - - // test - final int hash1 = waitingToBeResolvedTransition.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertEquals(hash1, hash2); - } - - @Test - void hashCode_withDifferentProductKeyAndSameRefSet_ShouldNotBeEquals() { - // preparation - final StateDraft stateDraft = mock(StateDraft.class); - final StateDraft stateDraft1 = mock(StateDraft.class); - when(stateDraft1.getKey()).thenReturn("foo"); - - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(stateDraft, new HashSet<>()); - final WaitingToBeResolvedTransitions other = - new WaitingToBeResolvedTransitions(stateDraft1, new HashSet<>()); - - // test - final int hash1 = waitingToBeResolvedTransition.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertNotEquals(hash1, hash2); - } - - @Test - void hashCode_withSameProductKeyAndDiffRefSet_ShouldNotBeEquals() { - // preparation - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); - final WaitingToBeResolvedTransitions other = - new WaitingToBeResolvedTransitions(mock(StateDraft.class), singleton("foo")); - - // test - final int hash1 = waitingToBeResolvedTransition.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertNotEquals(hash1, hash2); - } - - @Test - void hashCode_withCompletelyDifferentFields_ShouldNotBeEquals() { - // preparation - final StateDraft stateDraft = mock(StateDraft.class); - final StateDraft stateDraft1 = mock(StateDraft.class); - when(stateDraft1.getKey()).thenReturn("foo"); - - final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = - new WaitingToBeResolvedTransitions(stateDraft, singleton("foo")); - final WaitingToBeResolvedTransitions other = - new WaitingToBeResolvedTransitions(stateDraft1, singleton("bar")); - - // test - final int hash1 = waitingToBeResolvedTransition.hashCode(); - final int hash2 = other.hashCode(); - - // assertions - assertNotEquals(hash1, hash2); - } + @Test + void setProductDraft_WithNonNullProductDraft_ShouldSetProductDraft() { + // preparation + final StateDraft stateDraft = mock(StateDraft.class); + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(); + + // test + waitingToBeResolvedTransition.setStateDraft(stateDraft); + + // assertions + assertThat(waitingToBeResolvedTransition.getStateDraft()).isEqualTo(stateDraft); + } + + @Test + void setMissingReferencedProductKeys_WithNonNullSet_ShouldSetTheSet() { + // preparation + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(); + + // test + waitingToBeResolvedTransition.setMissingTransitionStateKeys(emptySet()); + + // assertions + assertThat(waitingToBeResolvedTransition.getMissingTransitionStateKeys()).isEqualTo(emptySet()); + } + + @Test + void equals_WithSameRef_ShouldReturnTrue() { + // preparation + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); + final WaitingToBeResolvedTransitions other = waitingToBeResolvedTransition; + + // test + boolean result = waitingToBeResolvedTransition.equals(other); + + // assertions + assertTrue(result); + } + + @Test + void equals_WithDiffType_ShouldReturnFalse() { + // preparation + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); + final Object other = new Object(); + + // test + boolean result = waitingToBeResolvedTransition.equals(other); + + // assertions + assertFalse(result); + } + + @Test + void equals_WithEqualObjects_ShouldReturnTrue() { + // preparation + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); + final WaitingToBeResolvedTransitions other = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); + + // test + boolean result = waitingToBeResolvedTransition.equals(other); + + // assertions + assertTrue(result); + } + + @Test + void equals_WithDifferentMissingRefKeys_ShouldReturnFalse() { + // preparation + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); + final WaitingToBeResolvedTransitions other = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), singleton("foo")); + + // test + boolean result = waitingToBeResolvedTransition.equals(other); + + // assertions + assertFalse(result); + } + + @Test + void equals_WithDifferentProductDraft_ShouldReturnFalse() { + // preparation + final StateDraft stateDraft = mock(StateDraft.class); + final StateDraft stateDraft1 = mock(StateDraft.class); + when(stateDraft1.getKey()).thenReturn("foo"); + + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(stateDraft, new HashSet<>()); + final WaitingToBeResolvedTransitions other = + new WaitingToBeResolvedTransitions(stateDraft1, new HashSet<>()); + + // test + boolean result = waitingToBeResolvedTransition.equals(other); + + // assertions + assertFalse(result); + } + + @Test + void equals_WithCompletelyDifferentFieldValues_ShouldReturnFalse() { + // preparation + final StateDraft stateDraft = mock(StateDraft.class); + final StateDraft stateDraft1 = mock(StateDraft.class); + when(stateDraft1.getKey()).thenReturn("foo"); + + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(stateDraft, singleton("foo")); + final WaitingToBeResolvedTransitions other = + new WaitingToBeResolvedTransitions(stateDraft1, singleton("bar")); + + // test + boolean result = waitingToBeResolvedTransition.equals(other); + + // assertions + assertFalse(result); + } + + @Test + void hashCode_withSameInstances_ShouldBeEquals() { + // preparation + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); + final WaitingToBeResolvedTransitions other = waitingToBeResolvedTransition; + + // test + final int hash1 = waitingToBeResolvedTransition.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertEquals(hash1, hash2); + } + + @Test + void hashCode_withSameProductKeyAndSameRefSet_ShouldBeEquals() { + // preparation + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); + final WaitingToBeResolvedTransitions other = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); + + // test + final int hash1 = waitingToBeResolvedTransition.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertEquals(hash1, hash2); + } + + @Test + void hashCode_withDifferentProductKeyAndSameRefSet_ShouldNotBeEquals() { + // preparation + final StateDraft stateDraft = mock(StateDraft.class); + final StateDraft stateDraft1 = mock(StateDraft.class); + when(stateDraft1.getKey()).thenReturn("foo"); + + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(stateDraft, new HashSet<>()); + final WaitingToBeResolvedTransitions other = + new WaitingToBeResolvedTransitions(stateDraft1, new HashSet<>()); + + // test + final int hash1 = waitingToBeResolvedTransition.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertNotEquals(hash1, hash2); + } + + @Test + void hashCode_withSameProductKeyAndDiffRefSet_ShouldNotBeEquals() { + // preparation + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), new HashSet<>()); + final WaitingToBeResolvedTransitions other = + new WaitingToBeResolvedTransitions(mock(StateDraft.class), singleton("foo")); + + // test + final int hash1 = waitingToBeResolvedTransition.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertNotEquals(hash1, hash2); + } + + @Test + void hashCode_withCompletelyDifferentFields_ShouldNotBeEquals() { + // preparation + final StateDraft stateDraft = mock(StateDraft.class); + final StateDraft stateDraft1 = mock(StateDraft.class); + when(stateDraft1.getKey()).thenReturn("foo"); + + final WaitingToBeResolvedTransitions waitingToBeResolvedTransition = + new WaitingToBeResolvedTransitions(stateDraft, singleton("foo")); + final WaitingToBeResolvedTransitions other = + new WaitingToBeResolvedTransitions(stateDraft1, singleton("bar")); + + // test + final int hash1 = waitingToBeResolvedTransition.hashCode(); + final int hash2 = other.hashCode(); + + // assertions + assertNotEquals(hash1, hash2); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/AssetReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/AssetReferenceResolutionUtilsTest.java index f29e169b5b..d5e5979c3c 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/AssetReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/AssetReferenceResolutionUtilsTest.java @@ -1,14 +1,5 @@ package com.commercetools.sync.commons.utils; -import io.sphere.sdk.models.Asset; -import io.sphere.sdk.models.AssetDraft; -import io.sphere.sdk.models.Reference; -import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.UUID; - import static com.commercetools.sync.commons.MockUtils.getAssetMockWithCustomFields; import static com.commercetools.sync.commons.MockUtils.getTypeMock; import static com.commercetools.sync.commons.utils.AssetReferenceResolutionUtils.mapToAssetDrafts; @@ -16,67 +7,79 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; -class AssetReferenceResolutionUtilsTest { - - @Test - void mapToAssetDrafts_WithAllExpandedReferences_ShouldReturnReferencesWithReplacedKeys() { - // preparation - final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - - final Asset asset = - getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType)); - - // test - final List referenceReplacedDrafts = mapToAssetDrafts(singletonList(asset)); - - // assertion - referenceReplacedDrafts - .forEach(referenceReplacedDraft -> { - assertThat(referenceReplacedDraft.getCustom()).isNotNull(); - assertThat(referenceReplacedDraft.getCustom().getType().getKey()).isEqualTo(customType.getKey()); - }); - } - - @Test - void mapToAssetDrafts_WithSomeExpandedReferences_ShouldReplaceOnlyExpandedRefs() { - // preparation - final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - - final Asset asset1 = - getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType)); - final Asset asset2 = - getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), - UUID.randomUUID().toString())); - - // test - final List referenceReplacedDrafts = mapToAssetDrafts(asList(asset1, asset2)); - - // assertion - assertThat(referenceReplacedDrafts).hasSize(2); - assertThat(referenceReplacedDrafts.get(0).getCustom()).isNotNull(); - assertThat(referenceReplacedDrafts.get(0).getCustom().getType().getKey()).isEqualTo(customType.getKey()); - assertThat(referenceReplacedDrafts.get(1).getCustom()).isNotNull(); - assertThat(referenceReplacedDrafts.get(1).getCustom().getType().getId()) - .isEqualTo(asset2.getCustom().getType().getId()); - } +import io.sphere.sdk.models.Asset; +import io.sphere.sdk.models.AssetDraft; +import io.sphere.sdk.models.Reference; +import io.sphere.sdk.types.Type; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.Test; - @Test - void mapToAssetDrafts_WithNonExpandedRefs_ShouldReturnReferencesWithoutReplacedKeys() { - // preparation - final String customTypeId = UUID.randomUUID().toString(); +class AssetReferenceResolutionUtilsTest { - final Asset asset = getAssetMockWithCustomFields( + @Test + void mapToAssetDrafts_WithAllExpandedReferences_ShouldReturnReferencesWithReplacedKeys() { + // preparation + final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + + final Asset asset = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType)); + + // test + final List referenceReplacedDrafts = mapToAssetDrafts(singletonList(asset)); + + // assertion + referenceReplacedDrafts.forEach( + referenceReplacedDraft -> { + assertThat(referenceReplacedDraft.getCustom()).isNotNull(); + assertThat(referenceReplacedDraft.getCustom().getType().getKey()) + .isEqualTo(customType.getKey()); + }); + } + + @Test + void mapToAssetDrafts_WithSomeExpandedReferences_ShouldReplaceOnlyExpandedRefs() { + // preparation + final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + + final Asset asset1 = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType)); + final Asset asset2 = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), UUID.randomUUID().toString())); + + // test + final List referenceReplacedDrafts = mapToAssetDrafts(asList(asset1, asset2)); + + // assertion + assertThat(referenceReplacedDrafts).hasSize(2); + assertThat(referenceReplacedDrafts.get(0).getCustom()).isNotNull(); + assertThat(referenceReplacedDrafts.get(0).getCustom().getType().getKey()) + .isEqualTo(customType.getKey()); + assertThat(referenceReplacedDrafts.get(1).getCustom()).isNotNull(); + assertThat(referenceReplacedDrafts.get(1).getCustom().getType().getId()) + .isEqualTo(asset2.getCustom().getType().getId()); + } + + @Test + void mapToAssetDrafts_WithNonExpandedRefs_ShouldReturnReferencesWithoutReplacedKeys() { + // preparation + final String customTypeId = UUID.randomUUID().toString(); + + final Asset asset = + getAssetMockWithCustomFields( Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), customTypeId)); - // test - final List referenceReplacedDrafts = mapToAssetDrafts(singletonList(asset)); - - // assertion - referenceReplacedDrafts - .forEach(referenceReplacedDraft -> { - assertThat(referenceReplacedDraft.getCustom()).isNotNull(); - assertThat(referenceReplacedDraft.getCustom().getType().getId()).isEqualTo(customTypeId); - }); - } + // test + final List referenceReplacedDrafts = mapToAssetDrafts(singletonList(asset)); + // assertion + referenceReplacedDrafts.forEach( + referenceReplacedDraft -> { + assertThat(referenceReplacedDraft.getCustom()).isNotNull(); + assertThat(referenceReplacedDraft.getCustom().getType().getId()).isEqualTo(customTypeId); + }); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/CartDiscountCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CartDiscountCustomUpdateActionUtilsTest.java index b56628b1a5..34c0883f9e 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CartDiscountCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CartDiscountCustomUpdateActionUtilsTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; +import static io.sphere.sdk.models.ResourceIdentifier.ofId; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; import com.commercetools.sync.cartdiscounts.helpers.CartDiscountCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -9,51 +15,55 @@ import io.sphere.sdk.cartdiscounts.commands.updateactions.SetCustomType; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.UUID; - -import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; -import static io.sphere.sdk.models.ResourceIdentifier.ofId; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.Test; class CartDiscountCustomUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithCartDiscountResource_ShouldBuildCartDiscountUpdateAction() { - final String newCustomTypeId = UUID.randomUUID().toString(); + @Test + void + buildTypedSetCustomTypeUpdateAction_WithCartDiscountResource_ShouldBuildCartDiscountUpdateAction() { + final String newCustomTypeId = UUID.randomUUID().toString(); - final UpdateAction updateAction = - GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction(newCustomTypeId, new HashMap<>(), - mock(CartDiscount.class), new CartDiscountCustomActionBuilder(), null, CartDiscount::getId, - cartDiscountResource -> cartDiscountResource.toReference().getTypeId(), cartDiscountResource -> null, - CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()).orElse(null); + final UpdateAction updateAction = + GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), + mock(CartDiscount.class), + new CartDiscountCustomActionBuilder(), + null, + CartDiscount::getId, + cartDiscountResource -> cartDiscountResource.toReference().getTypeId(), + cartDiscountResource -> null, + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); - } + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction) + .hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); + } - @Test - void buildRemoveCustomTypeAction_WithCartDiscountResource_ShouldBuildCartDiscountUpdateAction() { - final UpdateAction updateAction = - new CartDiscountCustomActionBuilder().buildRemoveCustomTypeAction(null, null); + @Test + void buildRemoveCustomTypeAction_WithCartDiscountResource_ShouldBuildCartDiscountUpdateAction() { + final UpdateAction updateAction = + new CartDiscountCustomActionBuilder().buildRemoveCustomTypeAction(null, null); - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); - } + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); + } - @Test - void buildSetCustomFieldAction_WithCartDiscountResource_ShouldBuildCartDiscountUpdateAction() { - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final String customFieldName = "name"; + @Test + void buildSetCustomFieldAction_WithCartDiscountResource_ShouldBuildCartDiscountUpdateAction() { + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + final String customFieldName = "name"; - final UpdateAction updateAction = new CartDiscountCustomActionBuilder() + final UpdateAction updateAction = + new CartDiscountCustomActionBuilder() .buildSetCustomFieldAction(null, null, customFieldName, customFieldValue); - assertThat(updateAction).isInstanceOf(SetCustomField.class); - assertThat((SetCustomField) updateAction).hasValues("setCustomField", customFieldName, customFieldValue); - } + assertThat(updateAction).isInstanceOf(SetCustomField.class); + assertThat((SetCustomField) updateAction) + .hasValues("setCustomField", customFieldName, customFieldValue); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/CategoryAssetCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CategoryAssetCustomUpdateActionUtilsTest.java index 6d3a38ae10..a0511129bb 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CategoryAssetCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CategoryAssetCustomUpdateActionUtilsTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; +import static io.sphere.sdk.models.ResourceIdentifier.ofId; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.CategorySyncOptionsBuilder; import com.commercetools.sync.categories.helpers.AssetCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -10,62 +17,62 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.Asset; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.UUID; - -import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; -import static io.sphere.sdk.models.ResourceIdentifier.ofId; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CategoryAssetCustomUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithCategoryAsset_ShouldBuildCategoryUpdateAction() { - final Asset asset = mock(Asset.class); - when(asset.getKey()).thenReturn("assetKey"); - final String newCustomTypeId = UUID.randomUUID().toString(); + @Test + void buildTypedSetCustomTypeUpdateAction_WithCategoryAsset_ShouldBuildCategoryUpdateAction() { + final Asset asset = mock(Asset.class); + when(asset.getKey()).thenReturn("assetKey"); + final String newCustomTypeId = UUID.randomUUID().toString(); - final UpdateAction updateAction = - GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction(newCustomTypeId, - new HashMap<>(), asset, new AssetCustomActionBuilder(), 1, - Asset::getId, assetResource -> Asset.resourceTypeId(), Asset::getKey, + final UpdateAction updateAction = + GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), + asset, + new AssetCustomActionBuilder(), + 1, + Asset::getId, + assetResource -> Asset.resourceTypeId(), + Asset::getKey, CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build()) - .orElse(null); + .orElse(null); - assertThat(updateAction).isInstanceOf(SetAssetCustomType.class); - assertThat((SetAssetCustomType) updateAction) - .hasValues("setAssetCustomType", asset.getId(), asset.getKey(), emptyMap(), ofId(newCustomTypeId)); - } + assertThat(updateAction).isInstanceOf(SetAssetCustomType.class); + assertThat((SetAssetCustomType) updateAction) + .hasValues( + "setAssetCustomType", asset.getId(), asset.getKey(), emptyMap(), ofId(newCustomTypeId)); + } - @Test - void buildRemoveCustomTypeAction_WithCategoryAsset_ShouldBuildChannelUpdateAction() { - final String assetKey = "assetKey"; + @Test + void buildRemoveCustomTypeAction_WithCategoryAsset_ShouldBuildChannelUpdateAction() { + final String assetKey = "assetKey"; - final UpdateAction updateAction = - new AssetCustomActionBuilder().buildRemoveCustomTypeAction(1, assetKey); + final UpdateAction updateAction = + new AssetCustomActionBuilder().buildRemoveCustomTypeAction(1, assetKey); - assertThat(updateAction).isInstanceOf(SetAssetCustomType.class); - assertThat((SetAssetCustomType) updateAction) - .hasValues("setAssetCustomType", null, assetKey, null, ofId(null)); - } + assertThat(updateAction).isInstanceOf(SetAssetCustomType.class); + assertThat((SetAssetCustomType) updateAction) + .hasValues("setAssetCustomType", null, assetKey, null, ofId(null)); + } - @Test - void buildSetCustomFieldAction_WithCategoryAsset_ShouldBuildCategoryUpdateAction() { - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final String customFieldName = "name"; - final String assetKey = "assetKey"; - final int variantId = 1; + @Test + void buildSetCustomFieldAction_WithCategoryAsset_ShouldBuildCategoryUpdateAction() { + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + final String customFieldName = "name"; + final String assetKey = "assetKey"; + final int variantId = 1; - final UpdateAction updateAction = new AssetCustomActionBuilder() + final UpdateAction updateAction = + new AssetCustomActionBuilder() .buildSetCustomFieldAction(variantId, assetKey, customFieldName, customFieldValue); - assertThat(updateAction).isInstanceOf(SetAssetCustomField.class); - assertThat((SetAssetCustomField) updateAction) - .hasValues("setAssetCustomField", null, assetKey, customFieldName, customFieldValue); - } + assertThat(updateAction).isInstanceOf(SetAssetCustomField.class); + assertThat((SetAssetCustomField) updateAction) + .hasValues("setAssetCustomField", null, assetKey, customFieldName, customFieldValue); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/CategoryCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CategoryCustomUpdateActionUtilsTest.java index 66aa1506c8..243426748b 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CategoryCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CategoryCustomUpdateActionUtilsTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; +import static io.sphere.sdk.models.ResourceIdentifier.ofId; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.categories.CategorySyncOptionsBuilder; import com.commercetools.sync.categories.helpers.CategoryCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -9,51 +15,54 @@ import io.sphere.sdk.categories.commands.updateactions.SetCustomType; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.UUID; - -import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; -import static io.sphere.sdk.models.ResourceIdentifier.ofId; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.Test; class CategoryCustomUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithCategoryResource_ShouldBuildCategoryUpdateAction() { - final String newCustomTypeId = UUID.randomUUID().toString(); + @Test + void buildTypedSetCustomTypeUpdateAction_WithCategoryResource_ShouldBuildCategoryUpdateAction() { + final String newCustomTypeId = UUID.randomUUID().toString(); - final UpdateAction updateAction = - GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction(newCustomTypeId, new HashMap<>(), - mock(Category.class), new CategoryCustomActionBuilder(), null, Category::getId, - categoryResource -> categoryResource.toReference().getTypeId(), categoryResource -> null, - CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build()).orElse(null); + final UpdateAction updateAction = + GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + Category::getId, + categoryResource -> categoryResource.toReference().getTypeId(), + categoryResource -> null, + CategorySyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); - } + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction) + .hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); + } - @Test - void buildRemoveCustomTypeAction_WithCategoryResource_ShouldBuildCategoryUpdateAction() { - final UpdateAction updateAction = - new CategoryCustomActionBuilder().buildRemoveCustomTypeAction(null, null); + @Test + void buildRemoveCustomTypeAction_WithCategoryResource_ShouldBuildCategoryUpdateAction() { + final UpdateAction updateAction = + new CategoryCustomActionBuilder().buildRemoveCustomTypeAction(null, null); - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); - } + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); + } - @Test - void buildSetCustomFieldAction_WithCategoryResource_ShouldBuildCategoryUpdateAction() { - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final String customFieldName = "name"; + @Test + void buildSetCustomFieldAction_WithCategoryResource_ShouldBuildCategoryUpdateAction() { + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + final String customFieldName = "name"; - final UpdateAction updateAction = new CategoryCustomActionBuilder() + final UpdateAction updateAction = + new CategoryCustomActionBuilder() .buildSetCustomFieldAction(null, null, customFieldName, customFieldValue); - assertThat(updateAction).isInstanceOf(SetCustomField.class); - assertThat((SetCustomField) updateAction).hasValues("setCustomField", customFieldName, customFieldValue); - } + assertThat(updateAction).isInstanceOf(SetCustomField.class); + assertThat((SetCustomField) updateAction) + .hasValues("setCustomField", customFieldName, customFieldValue); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/ChannelCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/ChannelCustomUpdateActionUtilsTest.java index 376789b30f..98bb59b03a 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/ChannelCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/ChannelCustomUpdateActionUtilsTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; +import static io.sphere.sdk.models.ResourceIdentifier.ofId; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.channels.helpers.ChannelCustomActionBuilder; import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -9,55 +15,57 @@ import io.sphere.sdk.channels.commands.updateactions.SetCustomType; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.Map; import java.util.UUID; - -import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; -import static io.sphere.sdk.models.ResourceIdentifier.ofId; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.Test; class ChannelCustomUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithChannelResource_ShouldBuildChannelUpdateAction() { - final Channel channel = mock(Channel.class); - final Map fieldsJsonMap = new HashMap<>(); - final String customTypeId = UUID.randomUUID().toString(); + @Test + void buildTypedSetCustomTypeUpdateAction_WithChannelResource_ShouldBuildChannelUpdateAction() { + final Channel channel = mock(Channel.class); + final Map fieldsJsonMap = new HashMap<>(); + final String customTypeId = UUID.randomUUID().toString(); - final UpdateAction updateAction = - GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction(customTypeId, - fieldsJsonMap, channel, new ChannelCustomActionBuilder(), null, Channel::getId, + final UpdateAction updateAction = + GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction( + customTypeId, + fieldsJsonMap, + channel, + new ChannelCustomActionBuilder(), + null, + Channel::getId, channelResource -> channelResource.toReference().getTypeId(), channelResource -> null, - InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()).orElse(null); + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", emptyMap(), ofId(customTypeId)); - } + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction) + .hasValues("setCustomType", emptyMap(), ofId(customTypeId)); + } - @Test - void buildRemoveCustomTypeAction_WithChannelResource_ShouldBuildChannelUpdateAction() { - final UpdateAction updateAction = - new ChannelCustomActionBuilder().buildRemoveCustomTypeAction(null, null); + @Test + void buildRemoveCustomTypeAction_WithChannelResource_ShouldBuildChannelUpdateAction() { + final UpdateAction updateAction = + new ChannelCustomActionBuilder().buildRemoveCustomTypeAction(null, null); - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); - } + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); + } - @Test - void buildSetCustomFieldAction_WithChannelResource_ShouldBuildChannelUpdateAction() { - final String customFieldName = "name"; - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + @Test + void buildSetCustomFieldAction_WithChannelResource_ShouldBuildChannelUpdateAction() { + final String customFieldName = "name"; + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final UpdateAction updateAction = - new ChannelCustomActionBuilder().buildSetCustomFieldAction(null, null, customFieldName, customFieldValue); + final UpdateAction updateAction = + new ChannelCustomActionBuilder() + .buildSetCustomFieldAction(null, null, customFieldName, customFieldValue); - assertThat(updateAction).isInstanceOf(SetCustomField.class); - assertThat((SetCustomField) updateAction).hasValues("setCustomField", customFieldName, customFieldValue); - } + assertThat(updateAction).isInstanceOf(SetCustomField.class); + assertThat((SetCustomField) updateAction) + .hasValues("setCustomField", customFieldName, customFieldValue); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/ClientConfigurationUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/ClientConfigurationUtilsTest.java index 528b9d11e8..78cb2379da 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/ClientConfigurationUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/ClientConfigurationUtilsTest.java @@ -1,36 +1,35 @@ package com.commercetools.sync.commons.utils; +import static org.assertj.core.api.Assertions.assertThat; + import io.sphere.sdk.client.BlockingSphereClient; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.client.SphereClientConfig; -import org.junit.jupiter.api.Test; - import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class ClientConfigurationUtilsTest { - private static final long TIMEOUT = 20000; - private static final TimeUnit TIMEOUT_TIME_UNIT = TimeUnit.MILLISECONDS; + private static final long TIMEOUT = 20000; + private static final TimeUnit TIMEOUT_TIME_UNIT = TimeUnit.MILLISECONDS; - @Test - void createClient_WithConfig_ReturnsSphereClient() { - final SphereClientConfig clientConfig = - SphereClientConfig.of("project-key", "client-id", "client-secret"); - final SphereClient sphereClient = ClientConfigurationUtils.createClient(clientConfig); + @Test + void createClient_WithConfig_ReturnsSphereClient() { + final SphereClientConfig clientConfig = + SphereClientConfig.of("project-key", "client-id", "client-secret"); + final SphereClient sphereClient = ClientConfigurationUtils.createClient(clientConfig); - assertThat(sphereClient.getConfig().getProjectKey()).isEqualTo("project-key"); - } + assertThat(sphereClient.getConfig().getProjectKey()).isEqualTo("project-key"); + } - @Test - void createClient_WithConfig_ReturnsBlockingSphereClient() { - final SphereClientConfig clientConfig = - SphereClientConfig.of("project-key", "client-id", "client-secret"); - final SphereClient sphereClient = - ClientConfigurationUtils.createClient(clientConfig, TIMEOUT, TIMEOUT_TIME_UNIT); + @Test + void createClient_WithConfig_ReturnsBlockingSphereClient() { + final SphereClientConfig clientConfig = + SphereClientConfig.of("project-key", "client-id", "client-secret"); + final SphereClient sphereClient = + ClientConfigurationUtils.createClient(clientConfig, TIMEOUT, TIMEOUT_TIME_UNIT); - assertThat(sphereClient instanceof BlockingSphereClient).isTrue(); + assertThat(sphereClient instanceof BlockingSphereClient).isTrue(); - assertThat(sphereClient.getConfig().getProjectKey()).isEqualTo("project-key"); - } + assertThat(sphereClient.getConfig().getProjectKey()).isEqualTo("project-key"); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/CollectionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CollectionUtilsTest.java index c073f5f5c2..3eca035da0 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CollectionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CollectionUtilsTest.java @@ -1,7 +1,12 @@ package com.commercetools.sync.commons.utils; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.Test; +import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToMap; +import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToSet; +import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; +import static com.commercetools.sync.commons.utils.CollectionUtils.filterCollection; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import java.util.ArrayList; import java.util.Collection; @@ -11,168 +16,195 @@ import java.util.List; import java.util.Map; import java.util.Set; - -import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToMap; -import static com.commercetools.sync.commons.utils.CollectionUtils.collectionToSet; -import static com.commercetools.sync.commons.utils.CollectionUtils.emptyIfNull; -import static com.commercetools.sync.commons.utils.CollectionUtils.filterCollection; -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; class CollectionUtilsTest { - @Test - void filterCollection_emptyCases() { - assertThat(filterCollection(null, i -> true)).isEmpty(); - assertThat(filterCollection(Collections.emptyList(), i -> true)).isEmpty(); - } - - @Test - void filterCollection_normalCases() { - List abcd = asList("a", "b", "c", "d"); - assertThat(filterCollection(abcd, s -> true)).containsExactlyInAnyOrder("a", "b", "c", "d"); - assertThat(filterCollection(abcd, s -> false)).isEmpty(); - assertThat(filterCollection(abcd, s -> s.charAt(0) > 'b')).containsExactlyInAnyOrder("c", "d"); - } - - @Test - void filterCollection_duplicates() { - List abcd = asList("a", "b", "c", "d", "d", "c", "g", "a"); - assertThat(filterCollection(abcd, s -> true)).containsExactlyInAnyOrder("a", "a", "b", "c", "c", "d", "d", "g"); - assertThat(filterCollection(abcd, s -> false)).isEmpty(); - assertThat(filterCollection(abcd, s -> s.charAt(0) > 'b')).containsExactlyInAnyOrder("c", "c", "d", "d", "g"); - } - - @Test - void collectionToSet_emptyCases() { - assertThat(collectionToSet(null, i -> i)).isEmpty(); - assertThat(collectionToSet(Collections.emptyList(), i -> i)).isEmpty(); - } - - @Test - void collectionToSet_normalCases() { - List> abcd = asList( - Pair.of("a", 1), Pair.of("b", 2), Pair.of("c", 3), Pair.of("d", 4)); - assertThat(collectionToSet(abcd, Pair::getKey)).containsExactlyInAnyOrder("a", "b", "c", "d"); - assertThat(collectionToSet(abcd, Pair::getValue)).containsExactlyInAnyOrder(1, 2, 3, 4); - } - - @Test - void collectionToSet_duplicates() { - List> abcd = asList( - Pair.of("a", 1), Pair.of("b", 2), Pair.of("c", 3), Pair.of("d", 4), Pair.of("d", 5), Pair.of("b", 6)); - // 2 duplicate keys should be suppressed - assertThat(collectionToSet(abcd, Pair::getKey)).containsExactlyInAnyOrder("a", "b", "c", "d"); - // no duplicate values - nothing should be suppressed - assertThat(collectionToSet(abcd, Pair::getValue)).containsExactlyInAnyOrder(1, 2, 3, 4, 5, 6); - } - - @Test - void collectionToMap_emptyCases() { - assertThat(collectionToMap(null, k -> k)).isEmpty(); - assertThat(collectionToMap(null, k -> k, v -> v)).isEmpty(); - assertThat(collectionToMap(Collections.emptyList(), k -> k)).isEmpty(); - assertThat(collectionToMap(Collections.emptyList(), k -> k, v -> v)).isEmpty(); - } - - @Test - void collectionToMap_normalCases() { - List> abcd = asList( - Pair.of("a", 1), Pair.of("b", 2), Pair.of("c", 3), Pair.of("d", 4)); - - assertThat(collectionToMap(abcd, Pair::getKey, Pair::getValue)) - .containsOnly(Pair.of("a", 1), Pair.of("b", 2), Pair.of("c", 3), Pair.of("d", 4)); - - assertThat(collectionToMap(abcd, Pair::getValue, Pair::getKey)) - .containsOnly(Pair.of(1, "a"), Pair.of(2, "b"), Pair.of(3, "c"), Pair.of(4, "d")); - - assertThat(collectionToMap(abcd, Pair::getKey)) // with default value mapper - .containsOnly(Pair.of("a", Pair.of("a", 1)), Pair.of("b", Pair.of("b", 2)), - Pair.of("c", Pair.of("c", 3)), Pair.of("d", Pair.of("d", 4))); - - assertThat(collectionToMap(abcd, Pair::getValue)) // with default value mapper - .containsOnly(Pair.of(1, Pair.of("a", 1)), Pair.of(2, Pair.of("b", 2)), Pair.of(3, Pair.of("c", 3)), - Pair.of(4, Pair.of("d", 4))); - } - - @Test - void collectionToMap_duplicates() { - List> abcd = asList( - Pair.of("a", 1), Pair.of("b", 2), Pair.of("c", 3), Pair.of("d", 4), Pair.of("d", 5), Pair.of("b", 6)); - - // 2 duplicate keys should be suppressed - assertThat(collectionToMap(abcd, Pair::getKey, Pair::getValue)).containsOnlyKeys("a", "b", "c", "d"); - assertThat(collectionToMap(abcd, Pair::getKey)) // with default value mapper - .containsOnlyKeys("a", "b", "c", "d"); - - // no duplicate values - nothing should be suppressed - assertThat(collectionToMap(abcd, Pair::getValue, Pair::getKey)) - .containsOnly(Pair.of(1, "a"), Pair.of(2, "b"), Pair.of(3, "c"), Pair.of(4, "d"), - Pair.of(5, "d"), Pair.of(6, "b")); - - assertThat(collectionToMap(abcd, Pair::getValue)) // with default value mapper - .containsOnly(Pair.of(1, Pair.of("a", 1)), Pair.of(2, Pair.of("b", 2)), Pair.of(3, Pair.of("c", 3)), - Pair.of(4, Pair.of("d", 4)), Pair.of(5, Pair.of("d", 5)), Pair.of(6, Pair.of("b", 6))); - } - - @Test - void emptyIfNull_withNullInstances_returnsNonNullInstances() { - Collection nonNullEmptyCollection = emptyIfNull((Collection)null); - assertThat(nonNullEmptyCollection).isNotNull(); - assertThat(nonNullEmptyCollection).isEmpty(); - - Set nonNullEmptySet = emptyIfNull((Set)null); - assertThat(nonNullEmptySet).isNotNull(); - assertThat(nonNullEmptySet).isEmpty(); - - List nonNullEmptyList = emptyIfNull((List)null); - assertThat(nonNullEmptyList).isNotNull(); - assertThat(nonNullEmptyList).isEmpty(); - - Map nonNullEmptyMap = emptyIfNull((Map)null); - assertThat(nonNullEmptyMap).isNotNull(); - assertThat(nonNullEmptyMap).isEmpty(); - } - - @Test - void emptyIfNull_withArrayList_returnsSameInstance() { - ArrayList arrayListCollection = new ArrayList<>(); - arrayListCollection.add("a"); - arrayListCollection.add("b"); - - List actualCollection = emptyIfNull(arrayListCollection); - assertThat(actualCollection).isSameAs(arrayListCollection); - assertThat(actualCollection).containsExactly("a", "b"); // verify ArrayList is not mutated - } - - @Test - void emptyIfNull_withHashSet_returnsSameInstance() { - HashSet arrayListCollection = new HashSet<>(); - arrayListCollection.add("woot"); - arrayListCollection.add("hack"); - - Set actualCollection = emptyIfNull(arrayListCollection); - assertThat(actualCollection).isSameAs(arrayListCollection); - assertThat(actualCollection).containsExactlyInAnyOrder("woot", "hack"); // verify HashSet is not mutated - } - - @Test - void emptyIfNull_withListInstances_returnsNonNullList() { - List originalListToTest = asList("a", "b"); - - List actualListToTest = emptyIfNull(originalListToTest); - assertThat(actualListToTest).isSameAs(originalListToTest); - assertThat(actualListToTest).containsExactly("a", "b"); // verify list is not mutated - } - - @Test - void emptyIfNull_withMapInstances_returnsNonNullMap() { - Map mapToTest = new HashMap<>(); - mapToTest.put("X", 10); - mapToTest.put("Y", 11); - - assertThat(emptyIfNull(mapToTest)).isSameAs(mapToTest); - assertThat(mapToTest).containsExactly(entry("X", 10), entry("Y", 11)); // verify map is not mutated - } -} \ No newline at end of file + @Test + void filterCollection_emptyCases() { + assertThat(filterCollection(null, i -> true)).isEmpty(); + assertThat(filterCollection(Collections.emptyList(), i -> true)).isEmpty(); + } + + @Test + void filterCollection_normalCases() { + List abcd = asList("a", "b", "c", "d"); + assertThat(filterCollection(abcd, s -> true)).containsExactlyInAnyOrder("a", "b", "c", "d"); + assertThat(filterCollection(abcd, s -> false)).isEmpty(); + assertThat(filterCollection(abcd, s -> s.charAt(0) > 'b')).containsExactlyInAnyOrder("c", "d"); + } + + @Test + void filterCollection_duplicates() { + List abcd = asList("a", "b", "c", "d", "d", "c", "g", "a"); + assertThat(filterCollection(abcd, s -> true)) + .containsExactlyInAnyOrder("a", "a", "b", "c", "c", "d", "d", "g"); + assertThat(filterCollection(abcd, s -> false)).isEmpty(); + assertThat(filterCollection(abcd, s -> s.charAt(0) > 'b')) + .containsExactlyInAnyOrder("c", "c", "d", "d", "g"); + } + + @Test + void collectionToSet_emptyCases() { + assertThat(collectionToSet(null, i -> i)).isEmpty(); + assertThat(collectionToSet(Collections.emptyList(), i -> i)).isEmpty(); + } + + @Test + void collectionToSet_normalCases() { + List> abcd = + asList(Pair.of("a", 1), Pair.of("b", 2), Pair.of("c", 3), Pair.of("d", 4)); + assertThat(collectionToSet(abcd, Pair::getKey)).containsExactlyInAnyOrder("a", "b", "c", "d"); + assertThat(collectionToSet(abcd, Pair::getValue)).containsExactlyInAnyOrder(1, 2, 3, 4); + } + + @Test + void collectionToSet_duplicates() { + List> abcd = + asList( + Pair.of("a", 1), + Pair.of("b", 2), + Pair.of("c", 3), + Pair.of("d", 4), + Pair.of("d", 5), + Pair.of("b", 6)); + // 2 duplicate keys should be suppressed + assertThat(collectionToSet(abcd, Pair::getKey)).containsExactlyInAnyOrder("a", "b", "c", "d"); + // no duplicate values - nothing should be suppressed + assertThat(collectionToSet(abcd, Pair::getValue)).containsExactlyInAnyOrder(1, 2, 3, 4, 5, 6); + } + + @Test + void collectionToMap_emptyCases() { + assertThat(collectionToMap(null, k -> k)).isEmpty(); + assertThat(collectionToMap(null, k -> k, v -> v)).isEmpty(); + assertThat(collectionToMap(Collections.emptyList(), k -> k)).isEmpty(); + assertThat(collectionToMap(Collections.emptyList(), k -> k, v -> v)).isEmpty(); + } + + @Test + void collectionToMap_normalCases() { + List> abcd = + asList(Pair.of("a", 1), Pair.of("b", 2), Pair.of("c", 3), Pair.of("d", 4)); + + assertThat(collectionToMap(abcd, Pair::getKey, Pair::getValue)) + .containsOnly(Pair.of("a", 1), Pair.of("b", 2), Pair.of("c", 3), Pair.of("d", 4)); + + assertThat(collectionToMap(abcd, Pair::getValue, Pair::getKey)) + .containsOnly(Pair.of(1, "a"), Pair.of(2, "b"), Pair.of(3, "c"), Pair.of(4, "d")); + + assertThat(collectionToMap(abcd, Pair::getKey)) // with default value mapper + .containsOnly( + Pair.of("a", Pair.of("a", 1)), + Pair.of("b", Pair.of("b", 2)), + Pair.of("c", Pair.of("c", 3)), + Pair.of("d", Pair.of("d", 4))); + + assertThat(collectionToMap(abcd, Pair::getValue)) // with default value mapper + .containsOnly( + Pair.of(1, Pair.of("a", 1)), + Pair.of(2, Pair.of("b", 2)), + Pair.of(3, Pair.of("c", 3)), + Pair.of(4, Pair.of("d", 4))); + } + + @Test + void collectionToMap_duplicates() { + List> abcd = + asList( + Pair.of("a", 1), + Pair.of("b", 2), + Pair.of("c", 3), + Pair.of("d", 4), + Pair.of("d", 5), + Pair.of("b", 6)); + + // 2 duplicate keys should be suppressed + assertThat(collectionToMap(abcd, Pair::getKey, Pair::getValue)) + .containsOnlyKeys("a", "b", "c", "d"); + assertThat(collectionToMap(abcd, Pair::getKey)) // with default value mapper + .containsOnlyKeys("a", "b", "c", "d"); + + // no duplicate values - nothing should be suppressed + assertThat(collectionToMap(abcd, Pair::getValue, Pair::getKey)) + .containsOnly( + Pair.of(1, "a"), + Pair.of(2, "b"), + Pair.of(3, "c"), + Pair.of(4, "d"), + Pair.of(5, "d"), + Pair.of(6, "b")); + + assertThat(collectionToMap(abcd, Pair::getValue)) // with default value mapper + .containsOnly( + Pair.of(1, Pair.of("a", 1)), + Pair.of(2, Pair.of("b", 2)), + Pair.of(3, Pair.of("c", 3)), + Pair.of(4, Pair.of("d", 4)), + Pair.of(5, Pair.of("d", 5)), + Pair.of(6, Pair.of("b", 6))); + } + + @Test + void emptyIfNull_withNullInstances_returnsNonNullInstances() { + Collection nonNullEmptyCollection = emptyIfNull((Collection) null); + assertThat(nonNullEmptyCollection).isNotNull(); + assertThat(nonNullEmptyCollection).isEmpty(); + + Set nonNullEmptySet = emptyIfNull((Set) null); + assertThat(nonNullEmptySet).isNotNull(); + assertThat(nonNullEmptySet).isEmpty(); + + List nonNullEmptyList = emptyIfNull((List) null); + assertThat(nonNullEmptyList).isNotNull(); + assertThat(nonNullEmptyList).isEmpty(); + + Map nonNullEmptyMap = emptyIfNull((Map) null); + assertThat(nonNullEmptyMap).isNotNull(); + assertThat(nonNullEmptyMap).isEmpty(); + } + + @Test + void emptyIfNull_withArrayList_returnsSameInstance() { + ArrayList arrayListCollection = new ArrayList<>(); + arrayListCollection.add("a"); + arrayListCollection.add("b"); + + List actualCollection = emptyIfNull(arrayListCollection); + assertThat(actualCollection).isSameAs(arrayListCollection); + assertThat(actualCollection).containsExactly("a", "b"); // verify ArrayList is not mutated + } + + @Test + void emptyIfNull_withHashSet_returnsSameInstance() { + HashSet arrayListCollection = new HashSet<>(); + arrayListCollection.add("woot"); + arrayListCollection.add("hack"); + + Set actualCollection = emptyIfNull(arrayListCollection); + assertThat(actualCollection).isSameAs(arrayListCollection); + assertThat(actualCollection) + .containsExactlyInAnyOrder("woot", "hack"); // verify HashSet is not mutated + } + + @Test + void emptyIfNull_withListInstances_returnsNonNullList() { + List originalListToTest = asList("a", "b"); + + List actualListToTest = emptyIfNull(originalListToTest); + assertThat(actualListToTest).isSameAs(originalListToTest); + assertThat(actualListToTest).containsExactly("a", "b"); // verify list is not mutated + } + + @Test + void emptyIfNull_withMapInstances_returnsNonNullMap() { + Map mapToTest = new HashMap<>(); + mapToTest.put("X", 10); + mapToTest.put("Y", 11); + + assertThat(emptyIfNull(mapToTest)).isSameAs(mapToTest); + assertThat(mapToTest) + .containsExactly(entry("X", 10), entry("Y", 11)); // verify map is not mutated + } +} diff --git a/src/test/java/com/commercetools/sync/commons/utils/CommonTypeUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CommonTypeUpdateActionUtilsTest.java index e62dd320e2..325796f2f2 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CommonTypeUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CommonTypeUpdateActionUtilsTest.java @@ -1,323 +1,329 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.areResourceIdentifiersEqual; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; +import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; +import static org.assertj.core.api.Assertions.assertThat; + import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.commands.updateactions.ChangeName; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.areResourceIdentifiersEqual; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateAction; -import static com.commercetools.sync.commons.utils.CommonTypeUpdateActionUtils.buildUpdateActionForReferences; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class CommonTypeUpdateActionUtilsTest { - private static final Locale LOCALE = Locale.GERMAN; - private static final String MOCK_OLD_CATEGORY_NAME = "categoryName"; - - @Test - void buildUpdateActionForLocalizedStrings_WithDifferentFieldValues_ShouldBuildUpdateAction() { - final LocalizedString oldLocalisedString = LocalizedString.of(LOCALE, "Apfel"); - final LocalizedString newLocalisedString = LocalizedString.of(LOCALE, "Milch"); - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForLocalizedStrings = - buildUpdateAction(oldLocalisedString, newLocalisedString, () -> mockUpdateAction); - - UpdateAction categoryUpdateAction = updateActionForLocalizedStrings.orElse(null); - assertThat(categoryUpdateAction).isNotNull(); - assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); - } - - @Test - void buildUpdateActionForLocalizedStrings_WithEmptyOldFields_ShouldBuildUpdateAction() { - final LocalizedString oldLocalisedString = LocalizedString.of(new HashMap<>()); - final LocalizedString newLocalisedString = LocalizedString.of(LOCALE, "Milch"); - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForLocalizedStrings = - buildUpdateAction(oldLocalisedString, newLocalisedString, () -> mockUpdateAction); - - UpdateAction categoryUpdateAction = updateActionForLocalizedStrings.orElse(null); - assertThat(categoryUpdateAction).isNotNull(); - assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); - } - - @Test - void buildUpdateActionForLocalizedStrings_WithEmptyFields_ShouldNotBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - final Optional> updateActionForLocalizedStrings = - buildUpdateAction(LocalizedString.of(new HashMap<>()), - LocalizedString.of(new HashMap<>()), () -> mockUpdateAction); - - assertThat(updateActionForLocalizedStrings).isNotNull(); - assertThat(updateActionForLocalizedStrings).isNotPresent(); - } - - @Test - void buildUpdateActionForLocalizedStrings_WithSameValues_ShouldNotBuildUpdateAction() { - final LocalizedString oldLocalisedString = LocalizedString.of(LOCALE, "Milch"); - final LocalizedString newLocalisedString = LocalizedString.of(LOCALE, "Milch"); - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForLocalizedStrings = - buildUpdateAction(oldLocalisedString, newLocalisedString, () -> mockUpdateAction); - - assertThat(updateActionForLocalizedStrings).isNotNull(); - assertThat(updateActionForLocalizedStrings).isNotPresent(); - } - - @Test - void buildUpdateActionForLocalizedStrings_WithSameValuesButDifferentFieldOrder_ShouldNotBuildUpdateAction() { - final LocalizedString oldLocalisedString = LocalizedString.of(LOCALE, "Milch") - .plus(Locale.FRENCH, "Lait") - .plus(Locale.ENGLISH, "Milk"); - - final LocalizedString newLocalisedString = LocalizedString.of(LOCALE, "Milch") - .plus(Locale.ENGLISH, "Milk") - .plus(Locale.FRENCH, "Lait"); - - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForReferences = - buildUpdateAction(oldLocalisedString, newLocalisedString, () -> mockUpdateAction); - - assertThat(updateActionForReferences).isNotNull(); - assertThat(updateActionForReferences).isNotPresent(); - } - - @Test - void buildUpdateActionForLocalizedStrings_WithNullValues_ShouldNotBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName - .of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - final Optional> updateActionForReferences = - buildUpdateAction(null, null, () -> mockUpdateAction); - - assertThat(updateActionForReferences).isNotNull(); - assertThat(updateActionForReferences).isNotPresent(); - } - - @Test - void buildUpdateActionForLocalizedStrings_WithNullUpdateAction_ShouldNotBuildUpdateAction() { - final Optional> updateActionForReferences = - buildUpdateAction(null, null, () -> null); - - assertThat(updateActionForReferences).isNotNull(); - assertThat(updateActionForReferences).isNotPresent(); - } - - @Test - void buildUpdateActionForReferences_WithDifferentTypeReferenceIds_ShouldBuildUpdateAction() { - final Reference oldCategoryReference = Category.referenceOfId("1"); - Reference newCategoryReference = Category.referenceOfId("2"); - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForReferences = - buildUpdateAction(oldCategoryReference, newCategoryReference, () -> mockUpdateAction); - - UpdateAction categoryUpdateAction = updateActionForReferences.orElse(null); - assertThat(categoryUpdateAction).isNotNull(); - assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); - } - - @Test - void buildUpdateActionForReferences_WithOneNullReference_ShouldBuildUpdateAction() { - final Reference categoryReference = Category.referenceOfId("1"); - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForReferences = - buildUpdateAction(null, categoryReference, () -> mockUpdateAction); - - UpdateAction categoryUpdateAction = updateActionForReferences.orElse(null); - assertThat(categoryUpdateAction).isNotNull(); - assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); - } - - @Test - void buildUpdateActionForReferences_WithSameCategoryReferences_ShouldNotBuildUpdateAction() { - final Reference categoryReference = Category.referenceOfId("1"); - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForReferences = - buildUpdateAction(categoryReference, categoryReference, () -> mockUpdateAction); - - assertThat(updateActionForReferences).isNotNull(); - assertThat(updateActionForReferences).isNotPresent(); - } - - @Test - void buildUpdateActionForReferences_WithNullReferences_ShouldNotBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - final Optional> updateActionForReferences = - buildUpdateAction(null, null, () -> mockUpdateAction); - - assertThat(updateActionForReferences).isNotNull(); - assertThat(updateActionForReferences).isNotPresent(); - } - - @Test - void buildUpdateActionForReferences_WithNullUpdateAction_ShouldNotBuildUpdateAction() { - final Optional> updateActionForReferences = - buildUpdateAction(null, null, () -> null); - - assertThat(updateActionForReferences).isNotNull(); - assertThat(updateActionForReferences).isNotPresent(); - } - - @Test - void buildUpdateActionForStrings_WithDifferentStrings_ShouldBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForStrings = - buildUpdateAction("1", "2", () -> mockUpdateAction); - - UpdateAction categoryUpdateAction = updateActionForStrings.orElse(null); - assertThat(categoryUpdateAction).isNotNull(); - assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); - } - - @Test - void buildUpdateActionForStrings_WithOneNullString_ShouldBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForStrings = - buildUpdateAction(null, "2", () -> mockUpdateAction); - - UpdateAction categoryUpdateAction = updateActionForStrings.orElse(null); - assertThat(categoryUpdateAction).isNotNull(); - assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); - } - - @Test - void buildUpdateActionForStrings_WithSameStrings_ShouldNotBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForStrings = - buildUpdateAction("1", "1", () -> mockUpdateAction); - - assertThat(updateActionForStrings).isNotNull(); - assertThat(updateActionForStrings).isNotPresent(); - } - - @Test - void buildUpdateActionForStrings_WithNullStrings_ShouldNotBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - final Optional> updateActionForStrings = - buildUpdateAction(null, null, () -> mockUpdateAction); - - assertThat(updateActionForStrings).isNotNull(); - assertThat(updateActionForStrings).isNotPresent(); - } - - @Test - void buildUpdateActionForStrings_WithNullUpdateAction_ShouldNotBuildUpdateAction() { - final Optional> updateActionForStrings = - buildUpdateAction(null, null, () -> null); - - assertThat(updateActionForStrings).isNotNull(); - assertThat(updateActionForStrings).isNotPresent(); - } - - @Test - void buildUpdateActionForReferences_WithSameValues_ShouldNotBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForStrings = - buildUpdateActionForReferences(ResourceIdentifier.ofId("foo"), ResourceIdentifier.ofId("foo"), - () -> mockUpdateAction); - - assertThat(updateActionForStrings).isNotNull(); - assertThat(updateActionForStrings).isNotPresent(); - } - - @Test - void buildUpdateActionForReferences_WithDiffValues_ShouldBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForStrings = - buildUpdateActionForReferences(ResourceIdentifier.ofId("foo"), ResourceIdentifier.ofId("bar"), - () -> mockUpdateAction); - - assertThat(updateActionForStrings).isNotNull(); - assertThat(updateActionForStrings).contains(mockUpdateAction); - } - - @Test - void buildUpdateActionForReferences_WithSameValuesDifferentInterface_ShouldNotBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForStrings = - buildUpdateActionForReferences(ResourceIdentifier.ofId("foo"), Category.referenceOfId("foo"), - () -> mockUpdateAction); - - assertThat(updateActionForStrings).isNotNull(); - assertThat(updateActionForStrings).isEmpty(); - } - - @Test - void buildUpdateActionForReferences_WithDiffValuesDifferentInterface_ShouldNotBuildUpdateAction() { - final UpdateAction mockUpdateAction = ChangeName.of( - LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); - - final Optional> updateActionForStrings = - buildUpdateActionForReferences(ResourceIdentifier.ofId("foo"), Category.referenceOfId("bar"), - () -> mockUpdateAction); - - assertThat(updateActionForStrings).isNotNull(); - assertThat(updateActionForStrings).contains(mockUpdateAction); - } - - @Test - void areResourceIdentifiersEqual_WithDiffValues_ShouldBeFalse() { - final boolean result = areResourceIdentifiersEqual( - ResourceIdentifier.ofId("foo"), ResourceIdentifier.ofId("bar")); - - assertThat(result).isFalse(); - } - - @Test - void areResourceIdentifiersEqual_WithSameValues_ShouldBeTrue() { - final boolean result = areResourceIdentifiersEqual( - ResourceIdentifier.ofId("foo"), ResourceIdentifier.ofId("foo")); - - assertThat(result).isTrue(); - } - - @Test - void areResourceIdentifiersEqual_WithDiffValuesDifferentInterface_ShouldBeFalse() { - final boolean result = areResourceIdentifiersEqual( - ResourceIdentifier.ofId("foo"), Category.referenceOfId("bar")); - - assertThat(result).isFalse(); - } - - @Test - void areResourceIdentifiersEqual_WithSameValuesDifferentInterface_ShouldBeTrue() { - final boolean result = areResourceIdentifiersEqual( - ResourceIdentifier.ofId("foo"), Category.referenceOfId("foo")); - - assertThat(result).isTrue(); - } + private static final Locale LOCALE = Locale.GERMAN; + private static final String MOCK_OLD_CATEGORY_NAME = "categoryName"; + + @Test + void buildUpdateActionForLocalizedStrings_WithDifferentFieldValues_ShouldBuildUpdateAction() { + final LocalizedString oldLocalisedString = LocalizedString.of(LOCALE, "Apfel"); + final LocalizedString newLocalisedString = LocalizedString.of(LOCALE, "Milch"); + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForLocalizedStrings = + buildUpdateAction(oldLocalisedString, newLocalisedString, () -> mockUpdateAction); + + UpdateAction categoryUpdateAction = updateActionForLocalizedStrings.orElse(null); + assertThat(categoryUpdateAction).isNotNull(); + assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); + } + + @Test + void buildUpdateActionForLocalizedStrings_WithEmptyOldFields_ShouldBuildUpdateAction() { + final LocalizedString oldLocalisedString = LocalizedString.of(new HashMap<>()); + final LocalizedString newLocalisedString = LocalizedString.of(LOCALE, "Milch"); + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForLocalizedStrings = + buildUpdateAction(oldLocalisedString, newLocalisedString, () -> mockUpdateAction); + + UpdateAction categoryUpdateAction = updateActionForLocalizedStrings.orElse(null); + assertThat(categoryUpdateAction).isNotNull(); + assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); + } + + @Test + void buildUpdateActionForLocalizedStrings_WithEmptyFields_ShouldNotBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + final Optional> updateActionForLocalizedStrings = + buildUpdateAction( + LocalizedString.of(new HashMap<>()), + LocalizedString.of(new HashMap<>()), + () -> mockUpdateAction); + + assertThat(updateActionForLocalizedStrings).isNotNull(); + assertThat(updateActionForLocalizedStrings).isNotPresent(); + } + + @Test + void buildUpdateActionForLocalizedStrings_WithSameValues_ShouldNotBuildUpdateAction() { + final LocalizedString oldLocalisedString = LocalizedString.of(LOCALE, "Milch"); + final LocalizedString newLocalisedString = LocalizedString.of(LOCALE, "Milch"); + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForLocalizedStrings = + buildUpdateAction(oldLocalisedString, newLocalisedString, () -> mockUpdateAction); + + assertThat(updateActionForLocalizedStrings).isNotNull(); + assertThat(updateActionForLocalizedStrings).isNotPresent(); + } + + @Test + void + buildUpdateActionForLocalizedStrings_WithSameValuesButDifferentFieldOrder_ShouldNotBuildUpdateAction() { + final LocalizedString oldLocalisedString = + LocalizedString.of(LOCALE, "Milch") + .plus(Locale.FRENCH, "Lait") + .plus(Locale.ENGLISH, "Milk"); + + final LocalizedString newLocalisedString = + LocalizedString.of(LOCALE, "Milch") + .plus(Locale.ENGLISH, "Milk") + .plus(Locale.FRENCH, "Lait"); + + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForReferences = + buildUpdateAction(oldLocalisedString, newLocalisedString, () -> mockUpdateAction); + + assertThat(updateActionForReferences).isNotNull(); + assertThat(updateActionForReferences).isNotPresent(); + } + + @Test + void buildUpdateActionForLocalizedStrings_WithNullValues_ShouldNotBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + final Optional> updateActionForReferences = + buildUpdateAction(null, null, () -> mockUpdateAction); + + assertThat(updateActionForReferences).isNotNull(); + assertThat(updateActionForReferences).isNotPresent(); + } + + @Test + void buildUpdateActionForLocalizedStrings_WithNullUpdateAction_ShouldNotBuildUpdateAction() { + final Optional> updateActionForReferences = + buildUpdateAction(null, null, () -> null); + + assertThat(updateActionForReferences).isNotNull(); + assertThat(updateActionForReferences).isNotPresent(); + } + + @Test + void buildUpdateActionForReferences_WithDifferentTypeReferenceIds_ShouldBuildUpdateAction() { + final Reference oldCategoryReference = Category.referenceOfId("1"); + Reference newCategoryReference = Category.referenceOfId("2"); + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForReferences = + buildUpdateAction(oldCategoryReference, newCategoryReference, () -> mockUpdateAction); + + UpdateAction categoryUpdateAction = updateActionForReferences.orElse(null); + assertThat(categoryUpdateAction).isNotNull(); + assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); + } + + @Test + void buildUpdateActionForReferences_WithOneNullReference_ShouldBuildUpdateAction() { + final Reference categoryReference = Category.referenceOfId("1"); + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForReferences = + buildUpdateAction(null, categoryReference, () -> mockUpdateAction); + + UpdateAction categoryUpdateAction = updateActionForReferences.orElse(null); + assertThat(categoryUpdateAction).isNotNull(); + assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); + } + + @Test + void buildUpdateActionForReferences_WithSameCategoryReferences_ShouldNotBuildUpdateAction() { + final Reference categoryReference = Category.referenceOfId("1"); + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForReferences = + buildUpdateAction(categoryReference, categoryReference, () -> mockUpdateAction); + + assertThat(updateActionForReferences).isNotNull(); + assertThat(updateActionForReferences).isNotPresent(); + } + + @Test + void buildUpdateActionForReferences_WithNullReferences_ShouldNotBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + final Optional> updateActionForReferences = + buildUpdateAction(null, null, () -> mockUpdateAction); + + assertThat(updateActionForReferences).isNotNull(); + assertThat(updateActionForReferences).isNotPresent(); + } + + @Test + void buildUpdateActionForReferences_WithNullUpdateAction_ShouldNotBuildUpdateAction() { + final Optional> updateActionForReferences = + buildUpdateAction(null, null, () -> null); + + assertThat(updateActionForReferences).isNotNull(); + assertThat(updateActionForReferences).isNotPresent(); + } + + @Test + void buildUpdateActionForStrings_WithDifferentStrings_ShouldBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForStrings = + buildUpdateAction("1", "2", () -> mockUpdateAction); + + UpdateAction categoryUpdateAction = updateActionForStrings.orElse(null); + assertThat(categoryUpdateAction).isNotNull(); + assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); + } + + @Test + void buildUpdateActionForStrings_WithOneNullString_ShouldBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForStrings = + buildUpdateAction(null, "2", () -> mockUpdateAction); + + UpdateAction categoryUpdateAction = updateActionForStrings.orElse(null); + assertThat(categoryUpdateAction).isNotNull(); + assertThat(categoryUpdateAction.getAction()).isEqualTo("changeName"); + } + + @Test + void buildUpdateActionForStrings_WithSameStrings_ShouldNotBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForStrings = + buildUpdateAction("1", "1", () -> mockUpdateAction); + + assertThat(updateActionForStrings).isNotNull(); + assertThat(updateActionForStrings).isNotPresent(); + } + + @Test + void buildUpdateActionForStrings_WithNullStrings_ShouldNotBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + final Optional> updateActionForStrings = + buildUpdateAction(null, null, () -> mockUpdateAction); + + assertThat(updateActionForStrings).isNotNull(); + assertThat(updateActionForStrings).isNotPresent(); + } + + @Test + void buildUpdateActionForStrings_WithNullUpdateAction_ShouldNotBuildUpdateAction() { + final Optional> updateActionForStrings = + buildUpdateAction(null, null, () -> null); + + assertThat(updateActionForStrings).isNotNull(); + assertThat(updateActionForStrings).isNotPresent(); + } + + @Test + void buildUpdateActionForReferences_WithSameValues_ShouldNotBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForStrings = + buildUpdateActionForReferences( + ResourceIdentifier.ofId("foo"), ResourceIdentifier.ofId("foo"), () -> mockUpdateAction); + + assertThat(updateActionForStrings).isNotNull(); + assertThat(updateActionForStrings).isNotPresent(); + } + + @Test + void buildUpdateActionForReferences_WithDiffValues_ShouldBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForStrings = + buildUpdateActionForReferences( + ResourceIdentifier.ofId("foo"), ResourceIdentifier.ofId("bar"), () -> mockUpdateAction); + + assertThat(updateActionForStrings).isNotNull(); + assertThat(updateActionForStrings).contains(mockUpdateAction); + } + + @Test + void + buildUpdateActionForReferences_WithSameValuesDifferentInterface_ShouldNotBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForStrings = + buildUpdateActionForReferences( + ResourceIdentifier.ofId("foo"), Category.referenceOfId("foo"), () -> mockUpdateAction); + + assertThat(updateActionForStrings).isNotNull(); + assertThat(updateActionForStrings).isEmpty(); + } + + @Test + void + buildUpdateActionForReferences_WithDiffValuesDifferentInterface_ShouldNotBuildUpdateAction() { + final UpdateAction mockUpdateAction = + ChangeName.of(LocalizedString.of(LOCALE, MOCK_OLD_CATEGORY_NAME)); + + final Optional> updateActionForStrings = + buildUpdateActionForReferences( + ResourceIdentifier.ofId("foo"), Category.referenceOfId("bar"), () -> mockUpdateAction); + + assertThat(updateActionForStrings).isNotNull(); + assertThat(updateActionForStrings).contains(mockUpdateAction); + } + + @Test + void areResourceIdentifiersEqual_WithDiffValues_ShouldBeFalse() { + final boolean result = + areResourceIdentifiersEqual(ResourceIdentifier.ofId("foo"), ResourceIdentifier.ofId("bar")); + + assertThat(result).isFalse(); + } + + @Test + void areResourceIdentifiersEqual_WithSameValues_ShouldBeTrue() { + final boolean result = + areResourceIdentifiersEqual(ResourceIdentifier.ofId("foo"), ResourceIdentifier.ofId("foo")); + + assertThat(result).isTrue(); + } + + @Test + void areResourceIdentifiersEqual_WithDiffValuesDifferentInterface_ShouldBeFalse() { + final boolean result = + areResourceIdentifiersEqual(ResourceIdentifier.ofId("foo"), Category.referenceOfId("bar")); + + assertThat(result).isFalse(); + } + + @Test + void areResourceIdentifiersEqual_WithSameValuesDifferentInterface_ShouldBeTrue() { + final boolean result = + areResourceIdentifiersEqual(ResourceIdentifier.ofId("foo"), Category.referenceOfId("foo")); + + assertThat(result).isTrue(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/CtpQueryUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CtpQueryUtilsTest.java index 325698b41c..9141e37c6f 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CtpQueryUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CtpQueryUtilsTest.java @@ -1,10 +1,21 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.CtpQueryUtils.queryAll; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.function.Function.identity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.GraphQlBaseRequest; -import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; import com.commercetools.sync.commons.models.GraphQlQueryResources; import com.commercetools.sync.commons.models.ResourceKeyId; +import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.queries.CategoryQuery; import io.sphere.sdk.categories.queries.CategoryQueryModel; @@ -12,6 +23,13 @@ import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.queries.QueryPredicate; import io.sphere.sdk.queries.QuerySort; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.annotation.Nonnull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -20,194 +38,177 @@ import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; -import javax.annotation.Nonnull; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static com.commercetools.sync.commons.utils.CtpQueryUtils.queryAll; -import static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.function.Function.identity; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) class CtpQueryUtilsTest { - @Captor - private ArgumentCaptor sphereRequestArgumentCaptor; - @Captor - private ArgumentCaptor> graphQlRequestArgumentCaptor; - - @BeforeEach - void init() { - MockitoAnnotations.initMocks(this); - } - - @Test - void queryAll_WithCategoryKeyQuery_ShouldFetchCorrectCategories() { - // preparation - final SphereClient sphereClient = mock(SphereClient.class); - final List categories = getMockCategoryPage(); - final PagedQueryResult mockQueryResults = getMockQueryResults(categories); - when(sphereClient.execute(any())).thenReturn(completedFuture(mockQueryResults)); - - final List keysToQuery = IntStream - .range(1, 510) - .mapToObj(i -> "key" + i) - .collect(Collectors.toList()); - - final Function> keyPredicateFunction = - categoryQueryModel -> categoryQueryModel.key().isIn(keysToQuery); - - // test - queryAll(sphereClient, CategoryQuery.of().plusPredicates(keyPredicateFunction), identity()) - .toCompletableFuture() - .join(); - - // assertions - verify(sphereClient, times(2)).execute(sphereRequestArgumentCaptor.capture()); - assertThat(sphereRequestArgumentCaptor.getAllValues()) - .containsExactly( - getFirstPageQuery(keyPredicateFunction, QuerySort.of("id asc")), - getSecondPageQuery(keyPredicateFunction, categories, QuerySort.of("id asc"))); - } - - @Test - void queryAll_WithCustomSort_ShouldNotSortByIdAsc() { - // preparation - final SphereClient sphereClient = mock(SphereClient.class); - final List categories = getMockCategoryPage(); - final PagedQueryResult mockQueryResults = getMockQueryResults(categories); - when(sphereClient.execute(any())).thenReturn(completedFuture(mockQueryResults)); - - final List keysToQuery = IntStream - .range(1, 510) - .mapToObj(i -> "key" + i) - .collect(Collectors.toList()); - - final Function> keyPredicateFunction = - categoryQueryModel -> categoryQueryModel.key().isIn(keysToQuery); - - // test - queryAll(sphereClient, CategoryQuery - .of() - .withSort(QuerySort.of("id desc")) - .plusPredicates(keyPredicateFunction), identity()) - .toCompletableFuture() - .join(); - - // assertions - verify(sphereClient, times(2)).execute(sphereRequestArgumentCaptor.capture()); - assertThat(sphereRequestArgumentCaptor.getAllValues()) - .containsExactly( - getFirstPageQuery(keyPredicateFunction, QuerySort.of("id desc")), - getSecondPageQuery(keyPredicateFunction, categories, QuerySort.of("id desc"))); - } - - @Test - void queryAll_WithGraphQlRequest_ShouldFetchPagedResult() { - // preparation - final SphereClient sphereClient = mock(SphereClient.class); - final List resultPage = IntStream - .range(0, 500) + @Captor private ArgumentCaptor sphereRequestArgumentCaptor; + + @Captor + private ArgumentCaptor> + graphQlRequestArgumentCaptor; + + @BeforeEach + void init() { + MockitoAnnotations.initMocks(this); + } + + @Test + void queryAll_WithCategoryKeyQuery_ShouldFetchCorrectCategories() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final List categories = getMockCategoryPage(); + final PagedQueryResult mockQueryResults = getMockQueryResults(categories); + when(sphereClient.execute(any())).thenReturn(completedFuture(mockQueryResults)); + + final List keysToQuery = + IntStream.range(1, 510).mapToObj(i -> "key" + i).collect(Collectors.toList()); + + final Function> keyPredicateFunction = + categoryQueryModel -> categoryQueryModel.key().isIn(keysToQuery); + + // test + queryAll(sphereClient, CategoryQuery.of().plusPredicates(keyPredicateFunction), identity()) + .toCompletableFuture() + .join(); + + // assertions + verify(sphereClient, times(2)).execute(sphereRequestArgumentCaptor.capture()); + assertThat(sphereRequestArgumentCaptor.getAllValues()) + .containsExactly( + getFirstPageQuery(keyPredicateFunction, QuerySort.of("id asc")), + getSecondPageQuery(keyPredicateFunction, categories, QuerySort.of("id asc"))); + } + + @Test + void queryAll_WithCustomSort_ShouldNotSortByIdAsc() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final List categories = getMockCategoryPage(); + final PagedQueryResult mockQueryResults = getMockQueryResults(categories); + when(sphereClient.execute(any())).thenReturn(completedFuture(mockQueryResults)); + + final List keysToQuery = + IntStream.range(1, 510).mapToObj(i -> "key" + i).collect(Collectors.toList()); + + final Function> keyPredicateFunction = + categoryQueryModel -> categoryQueryModel.key().isIn(keysToQuery); + + // test + queryAll( + sphereClient, + CategoryQuery.of() + .withSort(QuerySort.of("id desc")) + .plusPredicates(keyPredicateFunction), + identity()) + .toCompletableFuture() + .join(); + + // assertions + verify(sphereClient, times(2)).execute(sphereRequestArgumentCaptor.capture()); + assertThat(sphereRequestArgumentCaptor.getAllValues()) + .containsExactly( + getFirstPageQuery(keyPredicateFunction, QuerySort.of("id desc")), + getSecondPageQuery(keyPredicateFunction, categories, QuerySort.of("id desc"))); + } + + @Test + void queryAll_WithGraphQlRequest_ShouldFetchPagedResult() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final List resultPage = + IntStream.range(0, 500) .mapToObj(i -> mock(ResourceKeyId.class)) .collect(Collectors.toList()); - final ResourceKeyId lastCategory = resultPage.get(resultPage.size() - 1); - when(lastCategory.getId()).thenReturn(UUID.randomUUID().toString()); - - final Set mockPage = resultPage.stream().collect(Collectors.toSet()); - - final ResourceKeyIdGraphQlResult pagedResourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); - when(pagedResourceKeyIdGraphQlResult.getResults()) - .thenReturn(mockPage) - .thenReturn(mockPage) - .thenReturn(mockPage) - .thenReturn(mockPage) - .thenReturn(mockPage.stream().limit(10).collect(Collectors.toSet())); - - when(sphereClient.execute(any())).thenReturn(completedFuture(pagedResourceKeyIdGraphQlResult)); - - final Set keysToQuery = IntStream - .range(1, 2010) - .mapToObj(i -> "key" + i) - .collect(Collectors.toSet()); - - ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = - new ResourceKeyIdGraphQlRequest(keysToQuery, GraphQlQueryResources.CATEGORIES); - - // test - queryAll(sphereClient, resourceKeyIdGraphQlRequest, results -> identity()) - .toCompletableFuture() - .join(); - - // assertions - verify(sphereClient, times(5)).execute(graphQlRequestArgumentCaptor.capture()); - assertThat(graphQlRequestArgumentCaptor.getAllValues()) - .containsExactly( - resourceKeyIdGraphQlRequest.withLimit(500), - resourceKeyIdGraphQlRequest.withLimit(500).withPredicate(format("id > \\\\\\\"%s\\\\\\\"", "id")), - resourceKeyIdGraphQlRequest.withLimit(500).withPredicate(format("id > \\\\\\\"%s\\\\\\\"", "id")), - resourceKeyIdGraphQlRequest.withLimit(500).withPredicate(format("id > \\\\\\\"%s\\\\\\\"", "id")), - resourceKeyIdGraphQlRequest.withLimit(500).withPredicate(format("id > \\\\\\\"%s\\\\\\\"", "id"))); - } - - @Nonnull - private List getMockCategoryPage() { - final List categories = IntStream - .range(0, 500) - .mapToObj(i -> mock(Category.class)) - .collect(Collectors.toList()); - - // stub an id to the last category in the page. - final Category lastCategory = categories.get(categories.size() - 1); - when(lastCategory.getId()).thenReturn(UUID.randomUUID().toString()); - return categories; - } - - @Nonnull - private PagedQueryResult getMockQueryResults(@Nonnull final List mockPage) { - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - when(pagedQueryResult.getResults()) - .thenReturn(mockPage) // get full page on first call - .thenReturn(mockPage.subList(0, 10)); // get only 10 categories on second call - return pagedQueryResult; - } - - @Nonnull - private CategoryQuery getFirstPageQuery( - @Nonnull final Function> keyPredicate, - @Nonnull final QuerySort sort) { - - return CategoryQuery - .of() - .plusPredicates(keyPredicate).withSort(sort) - .withLimit(500) - .withFetchTotal(false); - } - - @Nonnull - private CategoryQuery getSecondPageQuery( - @Nonnull final Function> keyPredicateFunction, - @Nonnull final List categoryPage, - @Nonnull final QuerySort sort) { - - final String lastCategoryIdInPage = categoryPage - .get(categoryPage.size() - 1) - .getId(); - - return CategoryQuery - .of() - .plusPredicates(keyPredicateFunction).withSort(sort) - .plusPredicates(QueryPredicate.of(format("id > \"%s\"", lastCategoryIdInPage))) - .withLimit(500) - .withFetchTotal(false); - } + final ResourceKeyId lastCategory = resultPage.get(resultPage.size() - 1); + when(lastCategory.getId()).thenReturn(UUID.randomUUID().toString()); + + final Set mockPage = resultPage.stream().collect(Collectors.toSet()); + + final ResourceKeyIdGraphQlResult pagedResourceKeyIdGraphQlResult = + mock(ResourceKeyIdGraphQlResult.class); + when(pagedResourceKeyIdGraphQlResult.getResults()) + .thenReturn(mockPage) + .thenReturn(mockPage) + .thenReturn(mockPage) + .thenReturn(mockPage) + .thenReturn(mockPage.stream().limit(10).collect(Collectors.toSet())); + + when(sphereClient.execute(any())).thenReturn(completedFuture(pagedResourceKeyIdGraphQlResult)); + + final Set keysToQuery = + IntStream.range(1, 2010).mapToObj(i -> "key" + i).collect(Collectors.toSet()); + + ResourceKeyIdGraphQlRequest resourceKeyIdGraphQlRequest = + new ResourceKeyIdGraphQlRequest(keysToQuery, GraphQlQueryResources.CATEGORIES); + + // test + queryAll(sphereClient, resourceKeyIdGraphQlRequest, results -> identity()) + .toCompletableFuture() + .join(); + + // assertions + verify(sphereClient, times(5)).execute(graphQlRequestArgumentCaptor.capture()); + assertThat(graphQlRequestArgumentCaptor.getAllValues()) + .containsExactly( + resourceKeyIdGraphQlRequest.withLimit(500), + resourceKeyIdGraphQlRequest + .withLimit(500) + .withPredicate(format("id > \\\\\\\"%s\\\\\\\"", "id")), + resourceKeyIdGraphQlRequest + .withLimit(500) + .withPredicate(format("id > \\\\\\\"%s\\\\\\\"", "id")), + resourceKeyIdGraphQlRequest + .withLimit(500) + .withPredicate(format("id > \\\\\\\"%s\\\\\\\"", "id")), + resourceKeyIdGraphQlRequest + .withLimit(500) + .withPredicate(format("id > \\\\\\\"%s\\\\\\\"", "id"))); + } + + @Nonnull + private List getMockCategoryPage() { + final List categories = + IntStream.range(0, 500).mapToObj(i -> mock(Category.class)).collect(Collectors.toList()); + + // stub an id to the last category in the page. + final Category lastCategory = categories.get(categories.size() - 1); + when(lastCategory.getId()).thenReturn(UUID.randomUUID().toString()); + return categories; + } + + @Nonnull + private PagedQueryResult getMockQueryResults(@Nonnull final List mockPage) { + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + when(pagedQueryResult.getResults()) + .thenReturn(mockPage) // get full page on first call + .thenReturn(mockPage.subList(0, 10)); // get only 10 categories on second call + return pagedQueryResult; + } + + @Nonnull + private CategoryQuery getFirstPageQuery( + @Nonnull final Function> keyPredicate, + @Nonnull final QuerySort sort) { + + return CategoryQuery.of() + .plusPredicates(keyPredicate) + .withSort(sort) + .withLimit(500) + .withFetchTotal(false); + } + + @Nonnull + private CategoryQuery getSecondPageQuery( + @Nonnull final Function> keyPredicateFunction, + @Nonnull final List categoryPage, + @Nonnull final QuerySort sort) { + + final String lastCategoryIdInPage = categoryPage.get(categoryPage.size() - 1).getId(); + + return CategoryQuery.of() + .plusPredicates(keyPredicateFunction) + .withSort(sort) + .plusPredicates(QueryPredicate.of(format("id > \"%s\"", lastCategoryIdInPage))) + .withLimit(500) + .withFetchTotal(false); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/CustomTypeReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CustomTypeReferenceResolutionUtilsTest.java index 0db8f65349..aa736e9647 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CustomTypeReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CustomTypeReferenceResolutionUtilsTest.java @@ -1,70 +1,69 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.categories.Category; import io.sphere.sdk.models.Reference; import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.util.UUID; - -import static com.commercetools.sync.commons.utils.CustomTypeReferenceResolutionUtils.mapToCustomFieldsDraft; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CustomTypeReferenceResolutionUtilsTest { - @Test - void mapToCustomFieldsDraft_WithNullCustomType_ShouldReturnNullCustomFields() { - final Category mockCategory = mock(Category.class); - - final CustomFieldsDraft customFieldsDraft = mapToCustomFieldsDraft(mockCategory); + @Test + void mapToCustomFieldsDraft_WithNullCustomType_ShouldReturnNullCustomFields() { + final Category mockCategory = mock(Category.class); - assertThat(customFieldsDraft).isNull(); - } + final CustomFieldsDraft customFieldsDraft = mapToCustomFieldsDraft(mockCategory); - @Test - void mapToCustomFieldsDraft_WithExpandedCategory_ShouldReturnCustomFieldsDraft() { - // preparation - final Category mockCategory = mock(Category.class); - final CustomFields mockCustomFields = mock(CustomFields.class); - final Type mockType = mock(Type.class); - final String typeKey = "typeKey"; - when(mockType.getKey()).thenReturn(typeKey); - final Reference mockCustomType = Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), mockType); - when(mockCustomFields.getType()).thenReturn(mockCustomType); - when(mockCategory.getCustom()).thenReturn(mockCustomFields); + assertThat(customFieldsDraft).isNull(); + } - // test - final CustomFieldsDraft customFieldsDraft = mapToCustomFieldsDraft(mockCategory); + @Test + void mapToCustomFieldsDraft_WithExpandedCategory_ShouldReturnCustomFieldsDraft() { + // preparation + final Category mockCategory = mock(Category.class); + final CustomFields mockCustomFields = mock(CustomFields.class); + final Type mockType = mock(Type.class); + final String typeKey = "typeKey"; + when(mockType.getKey()).thenReturn(typeKey); + final Reference mockCustomType = + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), mockType); + when(mockCustomFields.getType()).thenReturn(mockCustomType); + when(mockCategory.getCustom()).thenReturn(mockCustomFields); - // assertion - assertThat(customFieldsDraft).isNotNull(); - assertThat(customFieldsDraft.getType().getKey()).isEqualTo(typeKey); - assertThat(customFieldsDraft.getType().getId()).isNull(); - } + // test + final CustomFieldsDraft customFieldsDraft = mapToCustomFieldsDraft(mockCategory); - @Test - void mapToCustomFieldsDraft_WithNonExpandedCategory_ShouldReturnResourceIdentifierWithoutKey() { - // preparation - final Category mockCategory = mock(Category.class); - final CustomFields mockCustomFields = mock(CustomFields.class); - final String customTypeUuid = UUID.randomUUID().toString(); - final Reference mockCustomType = Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), - customTypeUuid); - when(mockCustomFields.getType()).thenReturn(mockCustomType); - when(mockCategory.getCustom()).thenReturn(mockCustomFields); + // assertion + assertThat(customFieldsDraft).isNotNull(); + assertThat(customFieldsDraft.getType().getKey()).isEqualTo(typeKey); + assertThat(customFieldsDraft.getType().getId()).isNull(); + } - // test - final CustomFieldsDraft customFieldsDraft = mapToCustomFieldsDraft(mockCategory); + @Test + void mapToCustomFieldsDraft_WithNonExpandedCategory_ShouldReturnResourceIdentifierWithoutKey() { + // preparation + final Category mockCategory = mock(Category.class); + final CustomFields mockCustomFields = mock(CustomFields.class); + final String customTypeUuid = UUID.randomUUID().toString(); + final Reference mockCustomType = + Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), customTypeUuid); + when(mockCustomFields.getType()).thenReturn(mockCustomType); + when(mockCategory.getCustom()).thenReturn(mockCustomFields); - // assertion - assertThat(customFieldsDraft).isNotNull(); - assertThat(customFieldsDraft.getType()).isNotNull(); - assertThat(customFieldsDraft.getType().getId()).isEqualTo(customTypeUuid); - assertThat(customFieldsDraft.getType().getKey()).isNull(); - } + // test + final CustomFieldsDraft customFieldsDraft = mapToCustomFieldsDraft(mockCategory); + // assertion + assertThat(customFieldsDraft).isNotNull(); + assertThat(customFieldsDraft.getType()).isNotNull(); + assertThat(customFieldsDraft.getType().getId()).isEqualTo(customTypeUuid); + assertThat(customFieldsDraft.getType().getKey()).isNull(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/CustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CustomUpdateActionUtilsTest.java index 72e715e880..e2501bb2b3 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CustomUpdateActionUtilsTest.java @@ -1,5 +1,17 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildCustomUpdateActions; +import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildNonNullCustomFieldsUpdateActions; +import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildSetCustomFieldsUpdateActions; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.types.CustomFieldsDraft.ofTypeKeyAndJson; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; import com.commercetools.sync.categories.helpers.CategoryCustomActionBuilder; @@ -30,746 +42,919 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; - -import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildCustomUpdateActions; -import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildNonNullCustomFieldsUpdateActions; -import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildSetCustomFieldsUpdateActions; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.types.CustomFieldsDraft.ofTypeKeyAndJson; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CustomUpdateActionUtilsTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private static final CategorySyncOptions CATEGORY_SYNC_OPTIONS = CategorySyncOptionsBuilder.of(CTP_CLIENT).build(); - Resource mainOldResource = mock(Resource.class); - Resource maiNewResource = mock(Resource.class); - - @Test - void buildCustomUpdateActions_WithNonNullCustomFieldsWithDifferentTypes_ShouldBuildUpdateActions() { - final Asset oldAsset = mock(Asset.class); - final CustomFields oldAssetCustomFields = mock(CustomFields.class); - final Reference oldAssetCustomFieldsDraftTypeReference = Type.referenceOfId("2"); - when(oldAssetCustomFields.getType()).thenReturn(oldAssetCustomFieldsDraftTypeReference); - when(oldAsset.getCustom()).thenReturn(oldAssetCustomFields); - - final AssetDraft newAssetDraft = mock(AssetDraft.class); - final CustomFieldsDraft newAssetCustomFieldsDraft = mock(CustomFieldsDraft.class); - - final ResourceIdentifier typeResourceIdentifier = Type.referenceOfId("1"); - when(newAssetCustomFieldsDraft.getType()).thenReturn(typeResourceIdentifier); - when(newAssetDraft.getCustom()).thenReturn(newAssetCustomFieldsDraft); - - - final List> updateActions = buildCustomUpdateActions(mainOldResource, maiNewResource, - oldAsset, newAssetDraft,new AssetCustomActionBuilder(), 10, Asset::getId, - asset -> Asset.resourceTypeId(), Asset::getKey, + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private static final CategorySyncOptions CATEGORY_SYNC_OPTIONS = + CategorySyncOptionsBuilder.of(CTP_CLIENT).build(); + Resource mainOldResource = mock(Resource.class); + Resource maiNewResource = mock(Resource.class); + + @Test + void + buildCustomUpdateActions_WithNonNullCustomFieldsWithDifferentTypes_ShouldBuildUpdateActions() { + final Asset oldAsset = mock(Asset.class); + final CustomFields oldAssetCustomFields = mock(CustomFields.class); + final Reference oldAssetCustomFieldsDraftTypeReference = Type.referenceOfId("2"); + when(oldAssetCustomFields.getType()).thenReturn(oldAssetCustomFieldsDraftTypeReference); + when(oldAsset.getCustom()).thenReturn(oldAssetCustomFields); + + final AssetDraft newAssetDraft = mock(AssetDraft.class); + final CustomFieldsDraft newAssetCustomFieldsDraft = mock(CustomFieldsDraft.class); + + final ResourceIdentifier typeResourceIdentifier = Type.referenceOfId("1"); + when(newAssetCustomFieldsDraft.getType()).thenReturn(typeResourceIdentifier); + when(newAssetDraft.getCustom()).thenReturn(newAssetCustomFieldsDraft); + + final List> updateActions = + buildCustomUpdateActions( + mainOldResource, + maiNewResource, + oldAsset, + newAssetDraft, + new AssetCustomActionBuilder(), + 10, + Asset::getId, + asset -> Asset.resourceTypeId(), + Asset::getKey, ProductSyncOptionsBuilder.of(CTP_CLIENT).build()); - // Should set custom type of old asset. - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetAssetCustomType.class); - } - - @Test - void buildCustomUpdateActions_WithNullOldCustomFields_ShouldBuildUpdateActions() { - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(null); - - final CustomFieldsDraft newAssetCustomFieldsDraft = mock(CustomFieldsDraft.class); - final ResourceIdentifier typeResourceIdentifier = Type.referenceOfId("1"); - when(newAssetCustomFieldsDraft.getType()).thenReturn(typeResourceIdentifier); - - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .custom(newAssetCustomFieldsDraft) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainOldResource, maiNewResource, oldAsset, newAssetDraft, - new AssetCustomActionBuilder(), 10, - Asset::getId, asset -> Asset.resourceTypeId(), Asset::getKey, - ProductSyncOptionsBuilder.of(CTP_CLIENT).build()); - - // Should add custom type to old asset. - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetAssetCustomType.class); - } - - @Test - void buildCustomUpdateActions_WithNullOldCustomFieldsAndBlankNewTypeId_ShouldCallErrorCallBack() { - final Asset oldAsset = mock(Asset.class); - final String oldAssetId = "oldAssetId"; - when(oldAsset.getId()).thenReturn(oldAssetId); - when(oldAsset.getCustom()).thenReturn(null); - - - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .custom(ofTypeKeyAndJson("key", new HashMap<>())) - .build(); - - // Mock custom options error callback - final ArrayList callBackResponses = new ArrayList<>(); - final QuadConsumer, Optional, - List>> errorCallback = - (exception, newResource, oldResource, updateActions) -> { - callBackResponses.add(exception.getMessage()); - callBackResponses.add(exception.getCause()); - }; - - // Mock sync options - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .errorCallback(errorCallback) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainOldResource, maiNewResource, oldAsset, newAssetDraft, - new AssetCustomActionBuilder(), 10, Asset::getId, asset -> Asset.resourceTypeId(), - Asset::getKey, productSyncOptions); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(0); - assertThat(callBackResponses.get(0)).isEqualTo(format("Failed to build custom fields update actions on the " - + "asset with id '%s'. Reason: New resource's custom type id is blank (empty/null).", oldAssetId)); - assertThat(callBackResponses.get(1)).isNull(); - } - - @Test - void buildCustomUpdateActions_WithNullNewCustomFields_ShouldBuildUpdateActions() { - final Asset oldAsset = mock(Asset.class); - final String oldAssetId = "oldAssetId"; - when(oldAsset.getId()).thenReturn(oldAssetId); - when(oldAsset.getCustom()).thenReturn(mock(CustomFields.class)); - - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .custom(null) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainOldResource, maiNewResource, oldAsset, newAssetDraft, - new AssetCustomActionBuilder(), 10, Asset::getId, asset -> Asset.resourceTypeId(), - Asset::getKey, ProductSyncOptionsBuilder.of(CTP_CLIENT).build()); - - // Should remove custom type from old asset. - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetAssetCustomType.class); - } - - @Test - void buildCustomUpdateActions_WithNullIds_ShouldCallSyncOptionsCallBack() { - final Reference assetCustomTypeReference = Type.referenceOfId(null); - - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(assetCustomTypeReference); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(assetCustomTypeReference); - - // Mock old Asset - final Asset oldAsset = mock(Asset.class); - final String oldAssetId = "oldAssetId"; - when(oldAsset.getId()).thenReturn(oldAssetId); - when(oldAsset.getCustom()).thenReturn(oldCustomFieldsMock); - - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .custom(newCustomFieldsMock) - .build(); - - - - - // Mock custom options error callback - final ArrayList callBackResponses = new ArrayList<>(); - final QuadConsumer, Optional, - List>> errorCallback = - (exception, newResource, oldResource, updateActions) -> { - callBackResponses.add(exception.getMessage()); - callBackResponses.add(exception.getCause()); - }; - - // Mock sync options - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .errorCallback(errorCallback) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainOldResource, maiNewResource, oldAsset, - newAssetDraft, new AssetCustomActionBuilder(), 10, Asset::getId, - asset -> Asset.resourceTypeId(), Asset::getKey, productSyncOptions); - - assertThat(callBackResponses).hasSize(2); - assertThat(callBackResponses.get(0)).isEqualTo("Failed to build custom fields update actions on the asset" - + " with id 'oldAssetId'. Reason: Custom type ids are not set for both the old and new asset."); - assertThat((Exception) callBackResponses.get(1)).isInstanceOf(BuildUpdateActionException.class); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithBothNullCustomFields_ShouldNotBuildUpdateActions() { - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(null); - - final AssetDraft newAssetDraft = mock(AssetDraft.class); - when(newAssetDraft.getCustom()).thenReturn(null); - - // Mock custom options error callback - final ArrayList callBackResponses = new ArrayList<>(); - final QuadConsumer, Optional, - List>> errorCallback = - (exception, newResource, oldResource, updateActions) -> { - callBackResponses.add(exception.getMessage()); - callBackResponses.add(exception.getCause()); - }; - - // Mock sync options - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .errorCallback(errorCallback) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainOldResource, maiNewResource, oldAsset, newAssetDraft, - new AssetCustomActionBuilder(), 10, Asset::getId, asset -> Asset.resourceTypeId(), - Asset::getKey, productSyncOptions); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).isEmpty(); - assertThat(callBackResponses).isEmpty(); - } - - @Test - void - buildNonNullCustomFieldsUpdateActions_WithSameCategoryTypesButDifferentFieldValues_ShouldBuildUpdateActions() - throws BuildUpdateActionException { - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - final Map oldCustomFieldsJsonMapMock = new HashMap<>(); - oldCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryAssetCustomTypeId")); - when(oldCustomFieldsMock.getFieldsJsonMap()).thenReturn(oldCustomFieldsJsonMapMock); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - final Map newCustomFieldsJsonMapMock = new HashMap<>(); - newCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryAssetCustomTypeId")); - when(newCustomFieldsMock.getFields()).thenReturn(newCustomFieldsJsonMapMock); - - - final List> updateActions = buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, mock(Asset.class), - new com.commercetools.sync.categories.helpers.AssetCustomActionBuilder(), 1, Asset::getId, - assetResource -> Asset.resourceTypeId(), Asset::getKey, + // Should set custom type of old asset. + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetAssetCustomType.class); + } + + @Test + void buildCustomUpdateActions_WithNullOldCustomFields_ShouldBuildUpdateActions() { + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(null); + + final CustomFieldsDraft newAssetCustomFieldsDraft = mock(CustomFieldsDraft.class); + final ResourceIdentifier typeResourceIdentifier = Type.referenceOfId("1"); + when(newAssetCustomFieldsDraft.getType()).thenReturn(typeResourceIdentifier); + + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) + .custom(newAssetCustomFieldsDraft) + .build(); + + final List> updateActions = + buildCustomUpdateActions( + mainOldResource, + maiNewResource, + oldAsset, + newAssetDraft, + new AssetCustomActionBuilder(), + 10, + Asset::getId, + asset -> Asset.resourceTypeId(), + Asset::getKey, + ProductSyncOptionsBuilder.of(CTP_CLIENT).build()); + + // Should add custom type to old asset. + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetAssetCustomType.class); + } + + @Test + void buildCustomUpdateActions_WithNullOldCustomFieldsAndBlankNewTypeId_ShouldCallErrorCallBack() { + final Asset oldAsset = mock(Asset.class); + final String oldAssetId = "oldAssetId"; + when(oldAsset.getId()).thenReturn(oldAssetId); + when(oldAsset.getCustom()).thenReturn(null); + + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) + .custom(ofTypeKeyAndJson("key", new HashMap<>())) + .build(); + + // Mock custom options error callback + final ArrayList callBackResponses = new ArrayList<>(); + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallback = + (exception, newResource, oldResource, updateActions) -> { + callBackResponses.add(exception.getMessage()); + callBackResponses.add(exception.getCause()); + }; + + // Mock sync options + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).errorCallback(errorCallback).build(); + + final List> updateActions = + buildCustomUpdateActions( + mainOldResource, + maiNewResource, + oldAsset, + newAssetDraft, + new AssetCustomActionBuilder(), + 10, + Asset::getId, + asset -> Asset.resourceTypeId(), + Asset::getKey, + productSyncOptions); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(0); + assertThat(callBackResponses.get(0)) + .isEqualTo( + format( + "Failed to build custom fields update actions on the " + + "asset with id '%s'. Reason: New resource's custom type id is blank (empty/null).", + oldAssetId)); + assertThat(callBackResponses.get(1)).isNull(); + } + + @Test + void buildCustomUpdateActions_WithNullNewCustomFields_ShouldBuildUpdateActions() { + final Asset oldAsset = mock(Asset.class); + final String oldAssetId = "oldAssetId"; + when(oldAsset.getId()).thenReturn(oldAssetId); + when(oldAsset.getCustom()).thenReturn(mock(CustomFields.class)); + + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")).custom(null).build(); + + final List> updateActions = + buildCustomUpdateActions( + mainOldResource, + maiNewResource, + oldAsset, + newAssetDraft, + new AssetCustomActionBuilder(), + 10, + Asset::getId, + asset -> Asset.resourceTypeId(), + Asset::getKey, + ProductSyncOptionsBuilder.of(CTP_CLIENT).build()); + + // Should remove custom type from old asset. + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetAssetCustomType.class); + } + + @Test + void buildCustomUpdateActions_WithNullIds_ShouldCallSyncOptionsCallBack() { + final Reference assetCustomTypeReference = Type.referenceOfId(null); + + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(assetCustomTypeReference); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(assetCustomTypeReference); + + // Mock old Asset + final Asset oldAsset = mock(Asset.class); + final String oldAssetId = "oldAssetId"; + when(oldAsset.getId()).thenReturn(oldAssetId); + when(oldAsset.getCustom()).thenReturn(oldCustomFieldsMock); + + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) + .custom(newCustomFieldsMock) + .build(); + + // Mock custom options error callback + final ArrayList callBackResponses = new ArrayList<>(); + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallback = + (exception, newResource, oldResource, updateActions) -> { + callBackResponses.add(exception.getMessage()); + callBackResponses.add(exception.getCause()); + }; + + // Mock sync options + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).errorCallback(errorCallback).build(); + + final List> updateActions = + buildCustomUpdateActions( + mainOldResource, + maiNewResource, + oldAsset, + newAssetDraft, + new AssetCustomActionBuilder(), + 10, + Asset::getId, + asset -> Asset.resourceTypeId(), + Asset::getKey, + productSyncOptions); + + assertThat(callBackResponses).hasSize(2); + assertThat(callBackResponses.get(0)) + .isEqualTo( + "Failed to build custom fields update actions on the asset" + + " with id 'oldAssetId'. Reason: Custom type ids are not set for both the old and new asset."); + assertThat((Exception) callBackResponses.get(1)).isInstanceOf(BuildUpdateActionException.class); + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildNonNullCustomFieldsUpdateActions_WithBothNullCustomFields_ShouldNotBuildUpdateActions() { + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(null); + + final AssetDraft newAssetDraft = mock(AssetDraft.class); + when(newAssetDraft.getCustom()).thenReturn(null); + + // Mock custom options error callback + final ArrayList callBackResponses = new ArrayList<>(); + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallback = + (exception, newResource, oldResource, updateActions) -> { + callBackResponses.add(exception.getMessage()); + callBackResponses.add(exception.getCause()); + }; + + // Mock sync options + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).errorCallback(errorCallback).build(); + + final List> updateActions = + buildCustomUpdateActions( + mainOldResource, + maiNewResource, + oldAsset, + newAssetDraft, + new AssetCustomActionBuilder(), + 10, + Asset::getId, + asset -> Asset.resourceTypeId(), + Asset::getKey, + productSyncOptions); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions).isEmpty(); + assertThat(callBackResponses).isEmpty(); + } + + @Test + void + buildNonNullCustomFieldsUpdateActions_WithSameCategoryTypesButDifferentFieldValues_ShouldBuildUpdateActions() + throws BuildUpdateActionException { + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + final Map oldCustomFieldsJsonMapMock = new HashMap<>(); + oldCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryAssetCustomTypeId")); + when(oldCustomFieldsMock.getFieldsJsonMap()).thenReturn(oldCustomFieldsJsonMapMock); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + final Map newCustomFieldsJsonMapMock = new HashMap<>(); + newCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryAssetCustomTypeId")); + when(newCustomFieldsMock.getFields()).thenReturn(newCustomFieldsJsonMapMock); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + mock(Asset.class), + new com.commercetools.sync.categories.helpers.AssetCustomActionBuilder(), + 1, + Asset::getId, + assetResource -> Asset.resourceTypeId(), + Asset::getKey, CATEGORY_SYNC_OPTIONS); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf( - io.sphere.sdk.categories.commands.updateactions.SetAssetCustomField.class); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithDifferentCategoryTypeIds_ShouldBuildUpdateActions() - throws BuildUpdateActionException { - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("assetCustomTypeId")); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(ResourceIdentifier.ofId("newAssetCustomTypeId")); - - final List> updateActions = buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, mock(Asset.class), - new com.commercetools.sync.categories.helpers.AssetCustomActionBuilder(), 1, Asset::getId, - assetResource -> Asset.resourceTypeId(), Asset::getKey, + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)) + .isInstanceOf(io.sphere.sdk.categories.commands.updateactions.SetAssetCustomField.class); + } + + @Test + void buildNonNullCustomFieldsUpdateActions_WithDifferentCategoryTypeIds_ShouldBuildUpdateActions() + throws BuildUpdateActionException { + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("assetCustomTypeId")); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(ResourceIdentifier.ofId("newAssetCustomTypeId")); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + mock(Asset.class), + new com.commercetools.sync.categories.helpers.AssetCustomActionBuilder(), + 1, + Asset::getId, + assetResource -> Asset.resourceTypeId(), + Asset::getKey, CATEGORY_SYNC_OPTIONS); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf( - io.sphere.sdk.categories.commands.updateactions.SetAssetCustomType.class); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithNullOldCategoryTypeId_ShouldBuildUpdateActions() - throws BuildUpdateActionException { - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId(null)); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("priceCustomTypeId")); - - final List> updateActions = buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, mock(Price.class), new PriceCustomActionBuilder(), 1, Price::getId, - priceResource -> Price.resourceTypeId(), Price::getId, ProductSyncOptionsBuilder.of(CTP_CLIENT).build()); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetProductPriceCustomType.class); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithNullNewCategoryTypeId_ShouldBuildUpdateActions() - throws BuildUpdateActionException { - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("1")); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId(null)); - - // Mock custom options error callback - final ArrayList errorMessages = new ArrayList<>(); - final QuadConsumer, Optional, - List>> errorCallback = - (exception, newResource, oldResource, updateActions) -> errorMessages - .add(exception.getMessage()); - - // Mock sync options - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .errorCallback(errorCallback) - .build(); - - final Price price = mock(Price.class); - when(price.getId()).thenReturn(UUID.randomUUID().toString()); - - final List> updateActions = buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, price, new PriceCustomActionBuilder(), 1, Price::getId, - priceResource -> Price.resourceTypeId(), Price::getId, productSyncOptions); - - assertThat(errorMessages).hasSize(1); - assertThat(errorMessages.get(0)).isEqualTo(format("Failed to build 'setCustomType' update action on the " - + "%s with id '%s'. Reason: New Custom Type id is blank (null/empty).", Price.resourceTypeId(), - price.getId())); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithSameIdsButNullNewCustomFields_ShouldBuildUpdateActions() - throws BuildUpdateActionException { - final Reference productPriceTypeReference = Type.referenceOfId("productPriceCustomTypeId"); - - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - final Map oldCustomFieldsJsonMapMock = new HashMap<>(); - oldCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - when(oldCustomFieldsMock.getType()).thenReturn(productPriceTypeReference); - when(oldCustomFieldsMock.getFieldsJsonMap()).thenReturn(oldCustomFieldsJsonMapMock); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(productPriceTypeReference); - when(newCustomFieldsMock.getFields()).thenReturn(null); - - final List> updateActions = buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, mock(Price.class), new PriceCustomActionBuilder(), 1, Price::getId, - priceResource -> Price.resourceTypeId(), Price::getId, ProductSyncOptionsBuilder.of(CTP_CLIENT).build()); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetProductPriceCustomType.class); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithNullIds_ShouldThrowBuildUpdateActionException() { - final Reference productPriceTypeReference = Type.referenceOfId(null); - - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(productPriceTypeReference); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(productPriceTypeReference); - - assertThatThrownBy(() -> - buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, mock(Price.class), new PriceCustomActionBuilder(), 1, Price::getId, - priceResource -> Price.resourceTypeId(), Price::getId, - ProductSyncOptionsBuilder.of(CTP_CLIENT).build())) - .isInstanceOf(BuildUpdateActionException.class) - .hasMessageMatching("Custom type ids are not set for both the old and new product-price."); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithDifferentCustomFieldValues_ShouldBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - final List> updateActions = buildSetCustomFieldsUpdateActions(oldCustomFields, - newCustomFields, mock(Price.class), new PriceCustomActionBuilder(), 1, Price::getId); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).isNotEmpty(); - assertThat(updateActions).hasSize(2); - final UpdateAction categoryUpdateAction = updateActions.get(0); - assertThat(categoryUpdateAction).isNotNull(); - assertThat(categoryUpdateAction).isInstanceOf(SetProductPriceCustomField.class); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNewCustomFields_ShouldBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - newCustomFields.put("url", JsonNodeFactory.instance.objectNode().put("domain", "domain.com")); - newCustomFields.put("size", JsonNodeFactory.instance.objectNode().put("cm", 34)); - - final List> updateActions = buildSetCustomFieldsUpdateActions(oldCustomFields, - newCustomFields, mock(Price.class), new PriceCustomActionBuilder(), 1, Price::getId); - - assertThat(updateActions).hasSize(4); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithRemovedCustomFields_ShouldBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - - final List> updateActions = buildSetCustomFieldsUpdateActions(oldCustomFields, - newCustomFields, mock(Price.class), new PriceCustomActionBuilder(), 1, Price::getId); - - - assertThat(updateActions) - .hasSize(1) - .hasOnlyOneElementSatisfying(action -> { - assertThat(action).isInstanceOf(SetProductPriceCustomField.class); - final SetProductPriceCustomField setProductPriceCustomFieldAction = (SetProductPriceCustomField) action; - assertThat(setProductPriceCustomFieldAction.getName()).isEqualTo("backgroundColor"); - assertThat(setProductPriceCustomFieldAction.getValue()).isEqualTo(null); + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)) + .isInstanceOf(io.sphere.sdk.categories.commands.updateactions.SetAssetCustomType.class); + } + + @Test + void buildNonNullCustomFieldsUpdateActions_WithNullOldCategoryTypeId_ShouldBuildUpdateActions() + throws BuildUpdateActionException { + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId(null)); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("priceCustomTypeId")); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + mock(Price.class), + new PriceCustomActionBuilder(), + 1, + Price::getId, + priceResource -> Price.resourceTypeId(), + Price::getId, + ProductSyncOptionsBuilder.of(CTP_CLIENT).build()); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetProductPriceCustomType.class); + } + + @Test + void buildNonNullCustomFieldsUpdateActions_WithNullNewCategoryTypeId_ShouldBuildUpdateActions() + throws BuildUpdateActionException { + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("1")); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId(null)); + + // Mock custom options error callback + final ArrayList errorMessages = new ArrayList<>(); + final QuadConsumer< + SyncException, Optional, Optional, List>> + errorCallback = + (exception, newResource, oldResource, updateActions) -> + errorMessages.add(exception.getMessage()); + + // Mock sync options + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).errorCallback(errorCallback).build(); + + final Price price = mock(Price.class); + when(price.getId()).thenReturn(UUID.randomUUID().toString()); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + price, + new PriceCustomActionBuilder(), + 1, + Price::getId, + priceResource -> Price.resourceTypeId(), + Price::getId, + productSyncOptions); + + assertThat(errorMessages).hasSize(1); + assertThat(errorMessages.get(0)) + .isEqualTo( + format( + "Failed to build 'setCustomType' update action on the " + + "%s with id '%s'. Reason: New Custom Type id is blank (null/empty).", + Price.resourceTypeId(), price.getId())); + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildNonNullCustomFieldsUpdateActions_WithSameIdsButNullNewCustomFields_ShouldBuildUpdateActions() + throws BuildUpdateActionException { + final Reference productPriceTypeReference = + Type.referenceOfId("productPriceCustomTypeId"); + + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + final Map oldCustomFieldsJsonMapMock = new HashMap<>(); + oldCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + when(oldCustomFieldsMock.getType()).thenReturn(productPriceTypeReference); + when(oldCustomFieldsMock.getFieldsJsonMap()).thenReturn(oldCustomFieldsJsonMapMock); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(productPriceTypeReference); + when(newCustomFieldsMock.getFields()).thenReturn(null); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + mock(Price.class), + new PriceCustomActionBuilder(), + 1, + Price::getId, + priceResource -> Price.resourceTypeId(), + Price::getId, + ProductSyncOptionsBuilder.of(CTP_CLIENT).build()); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetProductPriceCustomType.class); + } + + @Test + void buildNonNullCustomFieldsUpdateActions_WithNullIds_ShouldThrowBuildUpdateActionException() { + final Reference productPriceTypeReference = Type.referenceOfId(null); + + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(productPriceTypeReference); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(productPriceTypeReference); + + assertThatThrownBy( + () -> + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + mock(Price.class), + new PriceCustomActionBuilder(), + 1, + Price::getId, + priceResource -> Price.resourceTypeId(), + Price::getId, + ProductSyncOptionsBuilder.of(CTP_CLIENT).build())) + .isInstanceOf(BuildUpdateActionException.class) + .hasMessageMatching("Custom type ids are not set for both the old and new product-price."); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithDifferentCustomFieldValues_ShouldBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + oldCustomFields.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Price.class), + new PriceCustomActionBuilder(), + 1, + Price::getId); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions).isNotEmpty(); + assertThat(updateActions).hasSize(2); + final UpdateAction categoryUpdateAction = updateActions.get(0); + assertThat(categoryUpdateAction).isNotNull(); + assertThat(categoryUpdateAction).isInstanceOf(SetProductPriceCustomField.class); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithNewCustomFields_ShouldBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + newCustomFields.put("url", JsonNodeFactory.instance.objectNode().put("domain", "domain.com")); + newCustomFields.put("size", JsonNodeFactory.instance.objectNode().put("cm", 34)); + + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Price.class), + new PriceCustomActionBuilder(), + 1, + Price::getId); + + assertThat(updateActions).hasSize(4); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithRemovedCustomFields_ShouldBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFields.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Price.class), + new PriceCustomActionBuilder(), + 1, + Price::getId); + + assertThat(updateActions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + action -> { + assertThat(action).isInstanceOf(SetProductPriceCustomField.class); + final SetProductPriceCustomField setProductPriceCustomFieldAction = + (SetProductPriceCustomField) action; + assertThat(setProductPriceCustomFieldAction.getName()).isEqualTo("backgroundColor"); + assertThat(setProductPriceCustomFieldAction.getValue()).isEqualTo(null); }); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithSameCustomFieldValues_ShouldNotBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - final List> updateActions = buildSetCustomFieldsUpdateActions(oldCustomFields, - newCustomFields, mock(Asset.class), new CategoryCustomActionBuilder(), 1, Asset::getId); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithDifferentOrderOfCustomFieldValues_ShouldNotBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("backgroundColor", - JsonNodeFactory.instance.objectNode().put("de", "rot").put("es", "rojo")); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("backgroundColor", - JsonNodeFactory.instance.objectNode().put("es", "rojo").put("de", "rot")); - - final List> updateActions = buildSetCustomFieldsUpdateActions(oldCustomFields, - newCustomFields, mock(Asset.class), - new com.commercetools.sync.categories.helpers.AssetCustomActionBuilder(), 1, Asset::getId); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithEmptyCustomFieldValues_ShouldNotBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode()); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode()); - - final List> updateActions = buildSetCustomFieldsUpdateActions(oldCustomFields, - newCustomFields, mock(Asset.class), new AssetCustomActionBuilder(), 1, Asset::getId); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithEmptyCustomFields_ShouldNotBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - - final Map newCustomFields = new HashMap<>(); - - final List> updateActions = buildSetCustomFieldsUpdateActions(oldCustomFields, - newCustomFields, mock(Asset.class), new AssetCustomActionBuilder(), 1, Asset::getId); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNullNewValue_ShouldBuildSetAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", null); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(oldCustomFieldsMap, newCustomFieldsMap, mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId); - - // assertion - assertThat(updateActions) - .containsExactly(SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, oldAsset.getKey(), "setOfBooleans", - null, true)); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithEmptyArrayAsDiffValue_ShouldBuildSetAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(oldCustomFieldsMap, newCustomFieldsMap, mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId); - - // assertion - assertThat(updateActions) - .containsExactly(SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, oldAsset.getKey(), "setOfBooleans", - JsonNodeFactory.instance.arrayNode(), true)); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithEmptyArrayAndNullOldValue_ShouldBuildSetAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("setOfBooleans", null); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(oldCustomFieldsMap, newCustomFieldsMap, mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId); - - // assertion - assertThat(updateActions) - .containsExactly(SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, oldAsset.getKey(), "setOfBooleans", - JsonNodeFactory.instance.arrayNode(), true)); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithEmptyArrayAndNullNodeOldValue_ShouldBuildSetAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(oldCustomFieldsMap, newCustomFieldsMap, mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId); - - // assertion - assertThat(updateActions) - .containsExactly(SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, oldAsset.getKey(), "setOfBooleans", - JsonNodeFactory.instance.arrayNode(), true)); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithEmptyArrayAsNewValue_ShouldBuildSetAction() { - // preparation - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(new HashMap<>(), newCustomFieldsMap, mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId); - - // assertion - assertThat(updateActions) - .containsExactly(SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, oldAsset.getKey(), "setOfBooleans", - JsonNodeFactory.instance.arrayNode(), true)); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithEmptyArrayAsSameValue_ShouldBuildSetAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(new HashMap<>(), newCustomFieldsMap, mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId); - - // assertion - assertThat(updateActions) - .containsExactly(SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, oldAsset.getKey(), "setOfBooleans", - JsonNodeFactory.instance.arrayNode(), true)); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNullJsonNodeNewValue_ShouldBuildAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(oldCustomFieldsMap, newCustomFieldsMap, mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId); - - // assertion - assertThat(updateActions) - .containsExactly(SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, oldAsset.getKey(), "setOfBooleans", - null, true)); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNullNewValueOfNewField_ShouldNotBuildAction() { - // preparation - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", null); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(new HashMap<>(), newCustomFieldsMap, mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId); - - // assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNullJsonNodeNewValueOfNewField_ShouldNotBuildAction() { - // preparation - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(new HashMap<>(), newCustomFieldsMap, mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId); - - // assertion - assertThat(updateActions).isEmpty(); - } + } + + @Test + void buildSetCustomFieldsUpdateActions_WithSameCustomFieldValues_ShouldNotBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Asset.class), + new CategoryCustomActionBuilder(), + 1, + Asset::getId); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildSetCustomFieldsUpdateActions_WithDifferentOrderOfCustomFieldValues_ShouldNotBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put( + "backgroundColor", + JsonNodeFactory.instance.objectNode().put("de", "rot").put("es", "rojo")); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put( + "backgroundColor", + JsonNodeFactory.instance.objectNode().put("es", "rojo").put("de", "rot")); + + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Asset.class), + new com.commercetools.sync.categories.helpers.AssetCustomActionBuilder(), + 1, + Asset::getId); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions).isEmpty(); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithEmptyCustomFieldValues_ShouldNotBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode()); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode()); + + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions).isEmpty(); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithEmptyCustomFields_ShouldNotBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + + final Map newCustomFields = new HashMap<>(); + + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions).isEmpty(); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithNullNewValue_ShouldBuildSetAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put( + "setOfBooleans", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", null); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFieldsMap, + newCustomFieldsMap, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + // assertion + assertThat(updateActions) + .containsExactly( + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, oldAsset.getKey(), "setOfBooleans", null, true)); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithEmptyArrayAsDiffValue_ShouldBuildSetAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put( + "setOfBooleans", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFieldsMap, + newCustomFieldsMap, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + // assertion + assertThat(updateActions) + .containsExactly( + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, oldAsset.getKey(), "setOfBooleans", JsonNodeFactory.instance.arrayNode(), true)); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithEmptyArrayAndNullOldValue_ShouldBuildSetAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("setOfBooleans", null); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFieldsMap, + newCustomFieldsMap, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + // assertion + assertThat(updateActions) + .containsExactly( + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, oldAsset.getKey(), "setOfBooleans", JsonNodeFactory.instance.arrayNode(), true)); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithEmptyArrayAndNullNodeOldValue_ShouldBuildSetAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFieldsMap, + newCustomFieldsMap, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + // assertion + assertThat(updateActions) + .containsExactly( + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, oldAsset.getKey(), "setOfBooleans", JsonNodeFactory.instance.arrayNode(), true)); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithEmptyArrayAsNewValue_ShouldBuildSetAction() { + // preparation + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + new HashMap<>(), + newCustomFieldsMap, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + // assertion + assertThat(updateActions) + .containsExactly( + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, oldAsset.getKey(), "setOfBooleans", JsonNodeFactory.instance.arrayNode(), true)); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithEmptyArrayAsSameValue_ShouldBuildSetAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.arrayNode()); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + new HashMap<>(), + newCustomFieldsMap, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + // assertion + assertThat(updateActions) + .containsExactly( + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, oldAsset.getKey(), "setOfBooleans", JsonNodeFactory.instance.arrayNode(), true)); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithNullJsonNodeNewValue_ShouldBuildAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put( + "setOfBooleans", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFieldsMap, + newCustomFieldsMap, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + // assertion + assertThat(updateActions) + .containsExactly( + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, oldAsset.getKey(), "setOfBooleans", null, true)); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithNullNewValueOfNewField_ShouldNotBuildAction() { + // preparation + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", null); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + new HashMap<>(), + newCustomFieldsMap, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + // assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithNullJsonNodeNewValueOfNewField_ShouldNotBuildAction() { + // preparation + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + new HashMap<>(), + newCustomFieldsMap, + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId); + + // assertion + assertThat(updateActions).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/CustomerCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/CustomerCustomUpdateActionUtilsTest.java index 164b87466d..eec1b02a35 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/CustomerCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/CustomerCustomUpdateActionUtilsTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; +import static io.sphere.sdk.models.ResourceIdentifier.ofId; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; import com.commercetools.sync.customers.utils.CustomerCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -13,46 +19,50 @@ import java.util.UUID; import org.junit.jupiter.api.Test; -import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; -import static io.sphere.sdk.models.ResourceIdentifier.ofId; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - class CustomerCustomUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithCustomerResource_ShouldBuildCustomerUpdateAction() { - final String newCustomTypeId = UUID.randomUUID().toString(); + @Test + void buildTypedSetCustomTypeUpdateAction_WithCustomerResource_ShouldBuildCustomerUpdateAction() { + final String newCustomTypeId = UUID.randomUUID().toString(); - final UpdateAction updateAction = - GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction(newCustomTypeId, new HashMap<>(), - mock(Customer.class), CustomerCustomActionBuilder.of(), null, Customer::getId, - customerResource -> customerResource.toReference().getTypeId(), customerResource -> null, - CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()).orElse(null); + final UpdateAction updateAction = + GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), + mock(Customer.class), + CustomerCustomActionBuilder.of(), + null, + Customer::getId, + customerResource -> customerResource.toReference().getTypeId(), + customerResource -> null, + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); - } + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction) + .hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); + } - @Test - void buildRemoveCustomTypeAction_WithCustomerResource_ShouldBuildCustomerUpdateAction() { - final UpdateAction updateAction = - CustomerCustomActionBuilder.of().buildRemoveCustomTypeAction(null, null); + @Test + void buildRemoveCustomTypeAction_WithCustomerResource_ShouldBuildCustomerUpdateAction() { + final UpdateAction updateAction = + CustomerCustomActionBuilder.of().buildRemoveCustomTypeAction(null, null); - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); - } + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); + } - @Test - void buildSetCustomFieldAction_WithCustomerResource_ShouldBuildCustomerUpdateAction() { - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final String customFieldName = "name"; + @Test + void buildSetCustomFieldAction_WithCustomerResource_ShouldBuildCustomerUpdateAction() { + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + final String customFieldName = "name"; - final UpdateAction updateAction = CustomerCustomActionBuilder.of() + final UpdateAction updateAction = + CustomerCustomActionBuilder.of() .buildSetCustomFieldAction(null, null, customFieldName, customFieldValue); - assertThat(updateAction).isInstanceOf(SetCustomField.class); - assertThat((SetCustomField) updateAction).hasValues("setCustomField", customFieldName, customFieldValue); - } + assertThat(updateAction).isInstanceOf(SetCustomField.class); + assertThat((SetCustomField) updateAction) + .hasValues("setCustomField", customFieldName, customFieldValue); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/EnumValuesUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/EnumValuesUpdateActionUtilsTest.java index cb41419b87..8cfc10ab3a 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/EnumValuesUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/EnumValuesUpdateActionUtilsTest.java @@ -1,19 +1,5 @@ package com.commercetools.sync.commons.utils; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.models.EnumValue; -import io.sphere.sdk.producttypes.ProductType; -import io.sphere.sdk.producttypes.commands.updateactions.AddEnumValue; -import io.sphere.sdk.producttypes.commands.updateactions.ChangeEnumValueOrder; -import io.sphere.sdk.producttypes.commands.updateactions.ChangePlainEnumValueLabel; -import io.sphere.sdk.producttypes.commands.updateactions.RemoveEnumValues; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - import static com.commercetools.sync.commons.utils.EnumValuesUpdateActionUtils.buildActions; import static com.commercetools.sync.commons.utils.EnumValuesUpdateActionUtils.buildAddEnumValuesUpdateActions; import static com.commercetools.sync.commons.utils.EnumValuesUpdateActionUtils.buildChangeEnumValuesOrderUpdateAction; @@ -25,13 +11,28 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.models.EnumValue; +import io.sphere.sdk.producttypes.ProductType; +import io.sphere.sdk.producttypes.commands.updateactions.AddEnumValue; +import io.sphere.sdk.producttypes.commands.updateactions.ChangeEnumValueOrder; +import io.sphere.sdk.producttypes.commands.updateactions.ChangePlainEnumValueLabel; +import io.sphere.sdk.producttypes.commands.updateactions.RemoveEnumValues; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + class EnumValuesUpdateActionUtilsTest { - private static final String attributeDefinitionName = "attribute_definition_name_1"; + private static final String attributeDefinitionName = "attribute_definition_name_1"; - @Test - void buildActions_WithoutCallbacks_ShouldNotBuildActions() { - final List> updateActions = buildActions(attributeDefinitionName, + @Test + void buildActions_WithoutCallbacks_ShouldNotBuildActions() { + final List> updateActions = + buildActions( + attributeDefinitionName, ENUM_VALUES_ABC, ENUM_VALUES_ABCD, null, @@ -40,28 +41,23 @@ void buildActions_WithoutCallbacks_ShouldNotBuildActions() { null, null); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildActions_WithoutRemoveCallback_ShouldNotBuildRemoveAction() { - final List> updateActions = buildActions(attributeDefinitionName, - ENUM_VALUES_ABC, - null, - null, - null, - null, - null, - null); + @Test + void buildActions_WithoutRemoveCallback_ShouldNotBuildRemoveAction() { + final List> updateActions = + buildActions(attributeDefinitionName, ENUM_VALUES_ABC, null, null, null, null, null, null); - assertThat(updateActions).doesNotContain( - RemoveEnumValues.of(attributeDefinitionName, asList("a", "b", "c")) - ); - } + assertThat(updateActions) + .doesNotContain(RemoveEnumValues.of(attributeDefinitionName, asList("a", "b", "c"))); + } - @Test - void buildActions_WithNullNewEnumValues_ShouldJustBuildRemoveActions() { - final List> updateActions = buildActions(attributeDefinitionName, + @Test + void buildActions_WithNullNewEnumValues_ShouldJustBuildRemoveActions() { + final List> updateActions = + buildActions( + attributeDefinitionName, ENUM_VALUES_ABC, null, RemoveEnumValues::of, @@ -70,18 +66,18 @@ void buildActions_WithNullNewEnumValues_ShouldJustBuildRemoveActions() { null, null); - assertThat(updateActions).containsAnyOf( - RemoveEnumValues.of(attributeDefinitionName, asList("a", "b", "c")) - ); + assertThat(updateActions) + .containsAnyOf(RemoveEnumValues.of(attributeDefinitionName, asList("a", "b", "c"))); - assertThat(updateActions).doesNotContain( - AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_A) - ); - } + assertThat(updateActions) + .doesNotContain(AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_A)); + } - @Test - void buildActions_WithEmptyNewEnumValues_ShouldJustBuildRemoveActions() { - final List> updateActions = buildActions(attributeDefinitionName, + @Test + void buildActions_WithEmptyNewEnumValues_ShouldJustBuildRemoveActions() { + final List> updateActions = + buildActions( + attributeDefinitionName, ENUM_VALUES_ABC, Collections.emptyList(), RemoveEnumValues::of, @@ -90,18 +86,18 @@ void buildActions_WithEmptyNewEnumValues_ShouldJustBuildRemoveActions() { null, null); - assertThat(updateActions).containsAnyOf( - RemoveEnumValues.of(attributeDefinitionName, asList("a", "b", "c")) - ); + assertThat(updateActions) + .containsAnyOf(RemoveEnumValues.of(attributeDefinitionName, asList("a", "b", "c"))); - assertThat(updateActions).doesNotContain( - AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_A) - ); - } + assertThat(updateActions) + .doesNotContain(AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_A)); + } - @Test - void buildActions_WithRemoveCallback_ShouldBuildRemoveAction() { - final List> updateActions = buildActions(attributeDefinitionName, + @Test + void buildActions_WithRemoveCallback_ShouldBuildRemoveAction() { + final List> updateActions = + buildActions( + attributeDefinitionName, ENUM_VALUES_ABC, null, RemoveEnumValues::of, @@ -110,31 +106,26 @@ void buildActions_WithRemoveCallback_ShouldBuildRemoveAction() { null, null); - assertThat(updateActions).containsAnyOf( - RemoveEnumValues.of(attributeDefinitionName, asList("a", "b", "c")) - ); - } + assertThat(updateActions) + .containsAnyOf(RemoveEnumValues.of(attributeDefinitionName, asList("a", "b", "c"))); + } - @Test - void buildActions_WithoutMatchingEnumCallback_ShouldNotBuildMatchingActions() { - final List> updateActions = buildActions(attributeDefinitionName, - ENUM_VALUES_AB, - null, - null, - null, - null, - null, - null); + @Test + void buildActions_WithoutMatchingEnumCallback_ShouldNotBuildMatchingActions() { + final List> updateActions = + buildActions(attributeDefinitionName, ENUM_VALUES_AB, null, null, null, null, null, null); - assertThat(updateActions).doesNotContain( + assertThat(updateActions) + .doesNotContain( ChangePlainEnumValueLabel.of(attributeDefinitionName, ENUM_VALUE_A), - ChangePlainEnumValueLabel.of(attributeDefinitionName, ENUM_VALUE_B) - ); - } + ChangePlainEnumValueLabel.of(attributeDefinitionName, ENUM_VALUE_B)); + } - @Test - void buildActions_WithMatchingEnumCallback_ShouldBuildMatchingActions() { - final List> updateActions = buildActions(attributeDefinitionName, + @Test + void buildActions_WithMatchingEnumCallback_ShouldBuildMatchingActions() { + final List> updateActions = + buildActions( + attributeDefinitionName, ENUM_VALUES_AB, ENUM_VALUES_AB_WITH_DIFFERENT_LABEL, null, @@ -143,30 +134,26 @@ void buildActions_WithMatchingEnumCallback_ShouldBuildMatchingActions() { null, null); - assertThat(updateActions).containsAnyOf( - ChangePlainEnumValueLabel.of(attributeDefinitionName, ENUM_VALUE_A_DIFFERENT_LABEL) - ); - } - - @Test - void buildActions_WithoutAddEnumCallback_ShouldNotBuildAddEnumActions() { - final List> updateActions = buildActions(attributeDefinitionName, - ENUM_VALUES_AB, - ENUM_VALUES_ABC, - null, - null, - null, - null, - null); - - assertThat(updateActions).doesNotContain( - AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_C) - ); - } - - @Test - void buildActions_WithAddEnumCallback_ShouldBuildAddEnumActions() { - final List> updateActions = buildActions(attributeDefinitionName, + assertThat(updateActions) + .containsAnyOf( + ChangePlainEnumValueLabel.of(attributeDefinitionName, ENUM_VALUE_A_DIFFERENT_LABEL)); + } + + @Test + void buildActions_WithoutAddEnumCallback_ShouldNotBuildAddEnumActions() { + final List> updateActions = + buildActions( + attributeDefinitionName, ENUM_VALUES_AB, ENUM_VALUES_ABC, null, null, null, null, null); + + assertThat(updateActions) + .doesNotContain(AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_C)); + } + + @Test + void buildActions_WithAddEnumCallback_ShouldBuildAddEnumActions() { + final List> updateActions = + buildActions( + attributeDefinitionName, ENUM_VALUES_AB, ENUM_VALUES_ABC, null, @@ -175,14 +162,14 @@ void buildActions_WithAddEnumCallback_ShouldBuildAddEnumActions() { null, null); - assertThat(updateActions).containsAnyOf( - AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_C) - ); - } + assertThat(updateActions).containsAnyOf(AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_C)); + } - @Test - void buildActions_WithoutChangeOrderEnumCallback_ShouldNotBuildChangeOrderEnumActions() { - final List> updateActions = buildActions(attributeDefinitionName, + @Test + void buildActions_WithoutChangeOrderEnumCallback_ShouldNotBuildChangeOrderEnumActions() { + final List> updateActions = + buildActions( + attributeDefinitionName, ENUM_VALUES_ABC, ENUM_VALUES_CAB, null, @@ -191,18 +178,17 @@ void buildActions_WithoutChangeOrderEnumCallback_ShouldNotBuildChangeOrderEnumAc null, null); - assertThat(updateActions).doesNotContain( - ChangeEnumValueOrder.of(attributeDefinitionName, asList( - ENUM_VALUE_C, - ENUM_VALUE_A, - ENUM_VALUE_B - )) - ); - } - - @Test - void buildActions_WithChangeOrderEnumCallback_ShouldBuildChangeOrderEnumActions() { - final List> updateActions = buildActions(attributeDefinitionName, + assertThat(updateActions) + .doesNotContain( + ChangeEnumValueOrder.of( + attributeDefinitionName, asList(ENUM_VALUE_C, ENUM_VALUE_A, ENUM_VALUE_B))); + } + + @Test + void buildActions_WithChangeOrderEnumCallback_ShouldBuildChangeOrderEnumActions() { + final List> updateActions = + buildActions( + attributeDefinitionName, ENUM_VALUES_ABC, ENUM_VALUES_CAB, null, @@ -211,113 +197,100 @@ void buildActions_WithChangeOrderEnumCallback_ShouldBuildChangeOrderEnumActions( ChangeEnumValueOrder::of, null); - assertThat(updateActions).containsAnyOf( - ChangeEnumValueOrder.of(attributeDefinitionName, asList( - ENUM_VALUE_C, - ENUM_VALUE_A, - ENUM_VALUE_B - )) - ); - } - - @Test - void buildRemoveEnumValuesUpdateAction_WithNullNewEnumValues_ShouldBuildRemoveActions() { - final Optional> updateAction = buildRemoveEnumValuesUpdateAction( - attributeDefinitionName, - ENUM_VALUES_ABC, - null, - RemoveEnumValues::of); - - assertThat(updateAction).isNotEmpty(); - assertThat(updateAction).isEqualTo( + assertThat(updateActions) + .containsAnyOf( + ChangeEnumValueOrder.of( + attributeDefinitionName, asList(ENUM_VALUE_C, ENUM_VALUE_A, ENUM_VALUE_B))); + } + + @Test + void buildRemoveEnumValuesUpdateAction_WithNullNewEnumValues_ShouldBuildRemoveActions() { + final Optional> updateAction = + buildRemoveEnumValuesUpdateAction( + attributeDefinitionName, ENUM_VALUES_ABC, null, RemoveEnumValues::of); + + assertThat(updateAction).isNotEmpty(); + assertThat(updateAction) + .isEqualTo( Optional.of(RemoveEnumValues.of(attributeDefinitionName, asList("a", "b", "c")))); - } + } - @Test - void buildMatchingEnumValuesUpdateActions_WithDifferentEnumValues_ShouldBuildChangeLabelActions() { - final List> updateActions = buildMatchingEnumValuesUpdateActions( + @Test + void + buildMatchingEnumValuesUpdateActions_WithDifferentEnumValues_ShouldBuildChangeLabelActions() { + final List> updateActions = + buildMatchingEnumValuesUpdateActions( attributeDefinitionName, ENUM_VALUES_AB, ENUM_VALUES_AB_WITH_DIFFERENT_LABEL, getMatchingValueFunction()); - assertThat(updateActions).containsAnyOf( - ChangePlainEnumValueLabel.of(attributeDefinitionName, ENUM_VALUE_A_DIFFERENT_LABEL) - ); - } - - @Test - void buildMatchingEnumValuesUpdateActions_WithSameNewEnumValues_ShouldNotBuildChangeLabelActions() { - final List> updateActions = buildMatchingEnumValuesUpdateActions( - attributeDefinitionName, - ENUM_VALUES_AB, - ENUM_VALUES_AB, - getMatchingValueFunction()); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddEnumValuesUpdateActions_WithNewEnumValues_ShouldBuildAddActions() { - final List> updateActions = buildAddEnumValuesUpdateActions(attributeDefinitionName, - ENUM_VALUES_AB, - ENUM_VALUES_ABC, - AddEnumValue::of); - - assertThat(updateActions).containsAnyOf( - AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_C) - ); - } - - @Test - void buildAddEnumValuesUpdateActions_WithSameEnumValues_ShouldNotBuildAddActions() { - final List> updateActions = buildAddEnumValuesUpdateActions(attributeDefinitionName, - ENUM_VALUES_AB, - ENUM_VALUES_AB, - AddEnumValue::of); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildChangeEnumValuesOrderUpdateAction_WithNewEnumValues_ShouldBuildAddActions() { - final Optional> updateAction = - buildChangeEnumValuesOrderUpdateAction(attributeDefinitionName, - ENUM_VALUES_ABC, - ENUM_VALUES_CAB, - ChangeEnumValueOrder::of); - - assertThat(updateAction).isNotEmpty(); - assertThat(updateAction).isEqualTo( - Optional.of(ChangeEnumValueOrder.of(attributeDefinitionName, asList( - ENUM_VALUE_C, - ENUM_VALUE_A, - ENUM_VALUE_B)))); - } - - @Test - void buildChangeEnumValuesOrderUpdateAction_WithSameEnumValues_ShouldNotBuildAddActions() { - final List> updateActions = buildAddEnumValuesUpdateActions(attributeDefinitionName, - ENUM_VALUES_AB, - ENUM_VALUES_AB, - AddEnumValue::of); - - assertThat(updateActions).isEmpty(); - } - - @Nonnull - private TriFunction>> getMatchingValueFunction() { - return (definitionName, oldEnumValue, newEnumValue) -> { - - if (oldEnumValue == null || newEnumValue == null) { - return Collections.emptyList(); - } - - return filterEmptyOptionals( - buildChangeLabelAction(attributeDefinitionName, - oldEnumValue, - newEnumValue - )); - }; - } + assertThat(updateActions) + .containsAnyOf( + ChangePlainEnumValueLabel.of(attributeDefinitionName, ENUM_VALUE_A_DIFFERENT_LABEL)); + } + + @Test + void + buildMatchingEnumValuesUpdateActions_WithSameNewEnumValues_ShouldNotBuildChangeLabelActions() { + final List> updateActions = + buildMatchingEnumValuesUpdateActions( + attributeDefinitionName, ENUM_VALUES_AB, ENUM_VALUES_AB, getMatchingValueFunction()); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddEnumValuesUpdateActions_WithNewEnumValues_ShouldBuildAddActions() { + final List> updateActions = + buildAddEnumValuesUpdateActions( + attributeDefinitionName, ENUM_VALUES_AB, ENUM_VALUES_ABC, AddEnumValue::of); + + assertThat(updateActions).containsAnyOf(AddEnumValue.of(attributeDefinitionName, ENUM_VALUE_C)); + } + + @Test + void buildAddEnumValuesUpdateActions_WithSameEnumValues_ShouldNotBuildAddActions() { + final List> updateActions = + buildAddEnumValuesUpdateActions( + attributeDefinitionName, ENUM_VALUES_AB, ENUM_VALUES_AB, AddEnumValue::of); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildChangeEnumValuesOrderUpdateAction_WithNewEnumValues_ShouldBuildAddActions() { + final Optional> updateAction = + buildChangeEnumValuesOrderUpdateAction( + attributeDefinitionName, ENUM_VALUES_ABC, ENUM_VALUES_CAB, ChangeEnumValueOrder::of); + + assertThat(updateAction).isNotEmpty(); + assertThat(updateAction) + .isEqualTo( + Optional.of( + ChangeEnumValueOrder.of( + attributeDefinitionName, asList(ENUM_VALUE_C, ENUM_VALUE_A, ENUM_VALUE_B)))); + } + + @Test + void buildChangeEnumValuesOrderUpdateAction_WithSameEnumValues_ShouldNotBuildAddActions() { + final List> updateActions = + buildAddEnumValuesUpdateActions( + attributeDefinitionName, ENUM_VALUES_AB, ENUM_VALUES_AB, AddEnumValue::of); + + assertThat(updateActions).isEmpty(); + } + + @Nonnull + private TriFunction>> + getMatchingValueFunction() { + return (definitionName, oldEnumValue, newEnumValue) -> { + if (oldEnumValue == null || newEnumValue == null) { + return Collections.emptyList(); + } + + return filterEmptyOptionals( + buildChangeLabelAction(attributeDefinitionName, oldEnumValue, newEnumValue)); + }; + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/FilterUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/FilterUtilsTest.java index cf15086418..101a4dc258 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/FilterUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/FilterUtilsTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.FilterUtils.executeSupplierIfPassesFilter; +import static com.commercetools.sync.products.ActionGroup.CATEGORIES; +import static com.commercetools.sync.products.ActionGroup.IMAGES; +import static com.commercetools.sync.products.ActionGroup.PRICES; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import com.commercetools.sync.products.SyncFilter; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Image; @@ -7,99 +14,102 @@ import io.sphere.sdk.products.Product; import io.sphere.sdk.products.commands.updateactions.AddExternalImage; import io.sphere.sdk.products.commands.updateactions.RemoveImage; -import org.junit.jupiter.api.Test; - import java.util.Arrays; import java.util.List; - -import static com.commercetools.sync.commons.utils.FilterUtils.executeSupplierIfPassesFilter; -import static com.commercetools.sync.products.ActionGroup.CATEGORIES; -import static com.commercetools.sync.products.ActionGroup.IMAGES; -import static com.commercetools.sync.products.ActionGroup.PRICES; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class FilterUtilsTest { - private final List> passingUpdateActions = Arrays.asList( - AddExternalImage.of(Image.of("url2", ImageDimensions.of(10, 10)), 0), - AddExternalImage.of(Image.of("url2", ImageDimensions.of(10, 10)), 0) - ); - - private static final List> defaultActions = - singletonList(RemoveImage.of("anyUrl", 0)); - - @Test - void executeSupplierIfPassesFilter_WithGroupInBlackList_ShouldFilterOutOnlyThisGroup() { - final SyncFilter syncFilter = SyncFilter.ofBlackList(IMAGES); - - final List> updateActionsAfterImagesFilter = - executeSupplierIfPassesFilter(syncFilter, IMAGES, () -> passingUpdateActions , () -> defaultActions); - assertThat(updateActionsAfterImagesFilter).hasSize(1); - assertThat(updateActionsAfterImagesFilter).isSameAs(defaultActions); - - final List> updateActionsAfterPricesFilter = - executeSupplierIfPassesFilter(syncFilter, PRICES, () -> passingUpdateActions, () -> defaultActions); - assertThat(updateActionsAfterPricesFilter).hasSize(2); - assertThat(updateActionsAfterPricesFilter).isSameAs(passingUpdateActions); - - final List> updateActionsAfterCategoriesFilter = - executeSupplierIfPassesFilter(syncFilter, CATEGORIES, () -> passingUpdateActions, () -> defaultActions); - assertThat(updateActionsAfterCategoriesFilter).hasSize(2); - assertThat(updateActionsAfterCategoriesFilter).isSameAs(passingUpdateActions); - } - - @Test - void executeSupplierIfPassesFilter_WithGroupNotInBlackList_ShouldFilterInThisGroup() { - final SyncFilter syncFilter = SyncFilter.ofBlackList(PRICES); - final List> updateActionsAfterImagesFilter = - executeSupplierIfPassesFilter(syncFilter, IMAGES, () -> passingUpdateActions , () -> defaultActions); - assertThat(updateActionsAfterImagesFilter).hasSize(2); - assertThat(updateActionsAfterImagesFilter).isSameAs(passingUpdateActions); - - final List> updateActionsAfterPricesFilter = - executeSupplierIfPassesFilter(syncFilter, PRICES, () -> passingUpdateActions, () -> defaultActions); - assertThat(updateActionsAfterPricesFilter).hasSize(1); - assertThat(updateActionsAfterPricesFilter).isSameAs(defaultActions); - } - - @Test - void executeSupplierIfPassesFilter_WithGroupInWhiteList_ShouldFilterInOnlyThisGroup() { - final SyncFilter syncFilter = SyncFilter.ofWhiteList(PRICES); - - final List> updateActionsAfterPricesFilter = - executeSupplierIfPassesFilter(syncFilter, PRICES, () -> passingUpdateActions, () -> defaultActions); - assertThat(updateActionsAfterPricesFilter).hasSize(2); - assertThat(updateActionsAfterPricesFilter).isSameAs(passingUpdateActions); - - final List> updateActionsAfterImagesFilter = - executeSupplierIfPassesFilter(syncFilter, IMAGES, () -> passingUpdateActions , () -> defaultActions); - assertThat(updateActionsAfterImagesFilter).hasSize(1); - assertThat(updateActionsAfterImagesFilter).isSameAs(defaultActions); - - final List> updateActionsAfterCategoriesFilter = - executeSupplierIfPassesFilter(syncFilter, CATEGORIES, () -> passingUpdateActions, () -> defaultActions); - assertThat(updateActionsAfterCategoriesFilter).hasSize(1); - assertThat(updateActionsAfterCategoriesFilter).isSameAs(defaultActions); - } - - @Test - void executeSupplierIfPassesFilter_WithDefault_ShouldFilterInEveryThing() { - final SyncFilter syncFilter = SyncFilter.of(); - - final List> updateActionsAfterPricesFilter = - executeSupplierIfPassesFilter(syncFilter, PRICES, () -> passingUpdateActions, () -> defaultActions); - assertThat(updateActionsAfterPricesFilter).hasSize(2); - assertThat(updateActionsAfterPricesFilter).isSameAs(passingUpdateActions); - - final List> updateActionsAfterImagesFilter = - executeSupplierIfPassesFilter(syncFilter, IMAGES, () -> passingUpdateActions , () -> defaultActions); - assertThat(updateActionsAfterImagesFilter).hasSize(2); - assertThat(updateActionsAfterImagesFilter).isSameAs(passingUpdateActions); - - final List> updateActionsAfterCategoriesFilter = - executeSupplierIfPassesFilter(syncFilter, CATEGORIES, () -> passingUpdateActions, () -> defaultActions); - assertThat(updateActionsAfterCategoriesFilter).hasSize(2); - assertThat(updateActionsAfterCategoriesFilter).isSameAs(passingUpdateActions); - } + private final List> passingUpdateActions = + Arrays.asList( + AddExternalImage.of(Image.of("url2", ImageDimensions.of(10, 10)), 0), + AddExternalImage.of(Image.of("url2", ImageDimensions.of(10, 10)), 0)); + + private static final List> defaultActions = + singletonList(RemoveImage.of("anyUrl", 0)); + + @Test + void executeSupplierIfPassesFilter_WithGroupInBlackList_ShouldFilterOutOnlyThisGroup() { + final SyncFilter syncFilter = SyncFilter.ofBlackList(IMAGES); + + final List> updateActionsAfterImagesFilter = + executeSupplierIfPassesFilter( + syncFilter, IMAGES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterImagesFilter).hasSize(1); + assertThat(updateActionsAfterImagesFilter).isSameAs(defaultActions); + + final List> updateActionsAfterPricesFilter = + executeSupplierIfPassesFilter( + syncFilter, PRICES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterPricesFilter).hasSize(2); + assertThat(updateActionsAfterPricesFilter).isSameAs(passingUpdateActions); + + final List> updateActionsAfterCategoriesFilter = + executeSupplierIfPassesFilter( + syncFilter, CATEGORIES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterCategoriesFilter).hasSize(2); + assertThat(updateActionsAfterCategoriesFilter).isSameAs(passingUpdateActions); + } + + @Test + void executeSupplierIfPassesFilter_WithGroupNotInBlackList_ShouldFilterInThisGroup() { + final SyncFilter syncFilter = SyncFilter.ofBlackList(PRICES); + final List> updateActionsAfterImagesFilter = + executeSupplierIfPassesFilter( + syncFilter, IMAGES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterImagesFilter).hasSize(2); + assertThat(updateActionsAfterImagesFilter).isSameAs(passingUpdateActions); + + final List> updateActionsAfterPricesFilter = + executeSupplierIfPassesFilter( + syncFilter, PRICES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterPricesFilter).hasSize(1); + assertThat(updateActionsAfterPricesFilter).isSameAs(defaultActions); + } + + @Test + void executeSupplierIfPassesFilter_WithGroupInWhiteList_ShouldFilterInOnlyThisGroup() { + final SyncFilter syncFilter = SyncFilter.ofWhiteList(PRICES); + + final List> updateActionsAfterPricesFilter = + executeSupplierIfPassesFilter( + syncFilter, PRICES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterPricesFilter).hasSize(2); + assertThat(updateActionsAfterPricesFilter).isSameAs(passingUpdateActions); + + final List> updateActionsAfterImagesFilter = + executeSupplierIfPassesFilter( + syncFilter, IMAGES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterImagesFilter).hasSize(1); + assertThat(updateActionsAfterImagesFilter).isSameAs(defaultActions); + + final List> updateActionsAfterCategoriesFilter = + executeSupplierIfPassesFilter( + syncFilter, CATEGORIES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterCategoriesFilter).hasSize(1); + assertThat(updateActionsAfterCategoriesFilter).isSameAs(defaultActions); + } + + @Test + void executeSupplierIfPassesFilter_WithDefault_ShouldFilterInEveryThing() { + final SyncFilter syncFilter = SyncFilter.of(); + + final List> updateActionsAfterPricesFilter = + executeSupplierIfPassesFilter( + syncFilter, PRICES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterPricesFilter).hasSize(2); + assertThat(updateActionsAfterPricesFilter).isSameAs(passingUpdateActions); + + final List> updateActionsAfterImagesFilter = + executeSupplierIfPassesFilter( + syncFilter, IMAGES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterImagesFilter).hasSize(2); + assertThat(updateActionsAfterImagesFilter).isSameAs(passingUpdateActions); + + final List> updateActionsAfterCategoriesFilter = + executeSupplierIfPassesFilter( + syncFilter, CATEGORIES, () -> passingUpdateActions, () -> defaultActions); + assertThat(updateActionsAfterCategoriesFilter).hasSize(2); + assertThat(updateActionsAfterCategoriesFilter).isSameAs(passingUpdateActions); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/GenericUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/GenericUpdateActionUtilsTest.java index a68cbebe4c..a93003da4b 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/GenericUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/GenericUpdateActionUtilsTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.categories.CategorySyncOptionsBuilder.of; +import static com.commercetools.sync.commons.utils.GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.helpers.AssetCustomActionBuilder; import com.commercetools.sync.commons.exceptions.SyncException; @@ -8,42 +13,48 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.Asset; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.categories.CategorySyncOptionsBuilder.of; -import static com.commercetools.sync.commons.utils.GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.Test; class GenericUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithNullNewIdCategoryAsset_ShouldNotBuildCategoryUpdateAction() { - final ArrayList errorMessages = new ArrayList<>(); - final QuadConsumer, Optional, - List>> errorCallback = - (exception, oldResource, newResource, updateActions) -> errorMessages - .add(exception.getMessage()); - - // Mock sync options - final CategorySyncOptions categorySyncOptions = - of(mock(SphereClient.class)).errorCallback(errorCallback) - .build(); - - final Optional> updateAction = - buildTypedSetCustomTypeUpdateAction(null, new HashMap<>(), mock(Asset.class), - new AssetCustomActionBuilder(), 1, Asset::getId, - assetResource -> Asset.resourceTypeId(), Asset::getKey, categorySyncOptions); - - assertThat(errorMessages).hasSize(1); - assertThat(errorMessages.get(0)).isEqualTo("Failed to build 'setCustomType' update action on the asset with" - + " id 'null'. Reason: New Custom Type id is blank (null/empty)."); - assertThat(updateAction).isEmpty(); - } - + @Test + void + buildTypedSetCustomTypeUpdateAction_WithNullNewIdCategoryAsset_ShouldNotBuildCategoryUpdateAction() { + final ArrayList errorMessages = new ArrayList<>(); + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallback = + (exception, oldResource, newResource, updateActions) -> + errorMessages.add(exception.getMessage()); + + // Mock sync options + final CategorySyncOptions categorySyncOptions = + of(mock(SphereClient.class)).errorCallback(errorCallback).build(); + + final Optional> updateAction = + buildTypedSetCustomTypeUpdateAction( + null, + new HashMap<>(), + mock(Asset.class), + new AssetCustomActionBuilder(), + 1, + Asset::getId, + assetResource -> Asset.resourceTypeId(), + Asset::getKey, + categorySyncOptions); + + assertThat(errorMessages).hasSize(1); + assertThat(errorMessages.get(0)) + .isEqualTo( + "Failed to build 'setCustomType' update action on the asset with" + + " id 'null'. Reason: New Custom Type id is blank (null/empty)."); + assertThat(updateAction).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/GraphQlQueryAllTest.java b/src/test/java/com/commercetools/sync/commons/utils/GraphQlQueryAllTest.java index 55fc0a026e..a62e2d6da6 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/GraphQlQueryAllTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/GraphQlQueryAllTest.java @@ -1,84 +1,85 @@ package com.commercetools.sync.commons.utils; +import static io.sphere.sdk.queries.QueryExecutionUtils.DEFAULT_PAGE_SIZE; +import static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.ResourceKeyId; import com.commercetools.sync.commons.models.ResourceKeyIdGraphQlResult; import io.sphere.sdk.client.SphereClient; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; - -import static io.sphere.sdk.queries.QueryExecutionUtils.DEFAULT_PAGE_SIZE; -import static java.util.Collections.emptySet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; class GraphQlQueryAllTest { - private static final ResourceKeyIdGraphQlResult pagedGraphQlQueryResult = mock(ResourceKeyIdGraphQlResult.class); - private static final SphereClient sphereClient = mock(SphereClient.class); - - @BeforeAll - static void setup() { - when(sphereClient.execute(any())).thenReturn(CompletableFuture.completedFuture(pagedGraphQlQueryResult)); - } - - @Test - void run_WithConsumer_ShouldApplyConsumer() { - //preparation - ResourceKeyId resource1 = new ResourceKeyId("key1", "id1"); - ResourceKeyId resource2 = new ResourceKeyId("key2", "id2"); - ResourceKeyId resource3 = new ResourceKeyId("key3", "id3"); - ResourceKeyId resource4 = new ResourceKeyId("key4", "id4"); - - Set results = new HashSet<>(); - results.add(resource1); - results.add(resource2); - results.add(resource3); - results.add(resource4); - when(pagedGraphQlQueryResult.getResults()).thenReturn(results); - - final GraphQlQueryAll query = - GraphQlQueryAll.of(sphereClient, mock(ResourceKeyIdGraphQlRequest.class), DEFAULT_PAGE_SIZE); - final List resourceIds = new ArrayList<>(); - - final Consumer> resourceIdCollector = page -> - page.forEach(resource -> resourceIds.add(resource.getId())); - - //test - query.run(resourceIdCollector).toCompletableFuture().join(); - - //assertions - assertThat(resourceIds).hasSize(4); - assertThat(resourceIds).containsExactlyInAnyOrder("id2", "id1", "id3", "id4"); - } - - @Test - void run_WithEmptyResults_ShouldSkipConsumer() { - //preparation - when(pagedGraphQlQueryResult.getResults()).thenReturn(emptySet()); - - final GraphQlQueryAll query = - GraphQlQueryAll.of(sphereClient, mock(ResourceKeyIdGraphQlRequest.class), DEFAULT_PAGE_SIZE); - final List resourceIds = new ArrayList<>(); - - final Consumer> resourceIdCollector = page -> - page.forEach(resource -> resourceIds.add(resource.getId())); - - //test - query.run(resourceIdCollector).toCompletableFuture().join(); - - //assertions - assertThat(resourceIds).isEmpty(); - } - + private static final ResourceKeyIdGraphQlResult pagedGraphQlQueryResult = + mock(ResourceKeyIdGraphQlResult.class); + private static final SphereClient sphereClient = mock(SphereClient.class); + + @BeforeAll + static void setup() { + when(sphereClient.execute(any())) + .thenReturn(CompletableFuture.completedFuture(pagedGraphQlQueryResult)); + } + + @Test + void run_WithConsumer_ShouldApplyConsumer() { + // preparation + ResourceKeyId resource1 = new ResourceKeyId("key1", "id1"); + ResourceKeyId resource2 = new ResourceKeyId("key2", "id2"); + ResourceKeyId resource3 = new ResourceKeyId("key3", "id3"); + ResourceKeyId resource4 = new ResourceKeyId("key4", "id4"); + + Set results = new HashSet<>(); + results.add(resource1); + results.add(resource2); + results.add(resource3); + results.add(resource4); + when(pagedGraphQlQueryResult.getResults()).thenReturn(results); + + final GraphQlQueryAll query = + GraphQlQueryAll.of( + sphereClient, mock(ResourceKeyIdGraphQlRequest.class), DEFAULT_PAGE_SIZE); + final List resourceIds = new ArrayList<>(); + + final Consumer> resourceIdCollector = + page -> page.forEach(resource -> resourceIds.add(resource.getId())); + + // test + query.run(resourceIdCollector).toCompletableFuture().join(); + + // assertions + assertThat(resourceIds).hasSize(4); + assertThat(resourceIds).containsExactlyInAnyOrder("id2", "id1", "id3", "id4"); + } + + @Test + void run_WithEmptyResults_ShouldSkipConsumer() { + // preparation + when(pagedGraphQlQueryResult.getResults()).thenReturn(emptySet()); + + final GraphQlQueryAll query = + GraphQlQueryAll.of( + sphereClient, mock(ResourceKeyIdGraphQlRequest.class), DEFAULT_PAGE_SIZE); + final List resourceIds = new ArrayList<>(); + + final Consumer> resourceIdCollector = + page -> page.forEach(resource -> resourceIds.add(resource.getId())); + + // test + query.run(resourceIdCollector).toCompletableFuture().join(); + + // assertions + assertThat(resourceIds).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/InventoryEntryCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/InventoryEntryCustomUpdateActionUtilsTest.java index 4d975d5247..2c1a3c2ac3 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/InventoryEntryCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/InventoryEntryCustomUpdateActionUtilsTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; +import static io.sphere.sdk.models.ResourceIdentifier.ofId; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; import com.commercetools.sync.inventories.helpers.InventoryCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -9,52 +15,55 @@ import io.sphere.sdk.inventory.InventoryEntry; import io.sphere.sdk.inventory.commands.updateactions.SetCustomField; import io.sphere.sdk.inventory.commands.updateactions.SetCustomType; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.UUID; - -import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; -import static io.sphere.sdk.models.ResourceIdentifier.ofId; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.Test; class InventoryEntryCustomUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithInventoryResource_ShouldBuildInventoryUpdateAction() { - final String newCustomTypeId = UUID.randomUUID().toString(); - - final UpdateAction updateAction = - GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction(newCustomTypeId, - new HashMap<>(), mock(InventoryEntry.class), new InventoryCustomActionBuilder(), null, - InventoryEntry::getId, inventoryResource -> inventoryResource.toReference().getTypeId(), - inventoryResource -> null, InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()) - .orElse(null); - - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); - } - - @Test - void buildRemoveCustomTypeAction_WithInventoryResource_ShouldBuildChannelUpdateAction() { - final UpdateAction updateAction = - new InventoryCustomActionBuilder().buildRemoveCustomTypeAction(null, null); - - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); - } - - @Test - void buildSetCustomFieldAction_WithInventoryResource_ShouldBuildInventoryUpdateAction() { - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final String customFieldName = "name"; - - final UpdateAction updateAction = - new InventoryCustomActionBuilder().buildSetCustomFieldAction(null, null, customFieldName, customFieldValue); - - assertThat(updateAction).isInstanceOf(SetCustomField.class); - assertThat((SetCustomField) updateAction).hasValues("setCustomField", customFieldName, customFieldValue); - } + @Test + void + buildTypedSetCustomTypeUpdateAction_WithInventoryResource_ShouldBuildInventoryUpdateAction() { + final String newCustomTypeId = UUID.randomUUID().toString(); + + final UpdateAction updateAction = + GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), + mock(InventoryEntry.class), + new InventoryCustomActionBuilder(), + null, + InventoryEntry::getId, + inventoryResource -> inventoryResource.toReference().getTypeId(), + inventoryResource -> null, + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); + + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction) + .hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); + } + + @Test + void buildRemoveCustomTypeAction_WithInventoryResource_ShouldBuildChannelUpdateAction() { + final UpdateAction updateAction = + new InventoryCustomActionBuilder().buildRemoveCustomTypeAction(null, null); + + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); + } + + @Test + void buildSetCustomFieldAction_WithInventoryResource_ShouldBuildInventoryUpdateAction() { + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + final String customFieldName = "name"; + + final UpdateAction updateAction = + new InventoryCustomActionBuilder() + .buildSetCustomFieldAction(null, null, customFieldName, customFieldValue); + + assertThat(updateAction).isInstanceOf(SetCustomField.class); + assertThat((SetCustomField) updateAction) + .hasValues("setCustomField", customFieldName, customFieldValue); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/LocalizedEnumValueFixtures.java b/src/test/java/com/commercetools/sync/commons/utils/LocalizedEnumValueFixtures.java index 8615a5d8f3..3c67e1210c 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/LocalizedEnumValueFixtures.java +++ b/src/test/java/com/commercetools/sync/commons/utils/LocalizedEnumValueFixtures.java @@ -1,47 +1,49 @@ package com.commercetools.sync.commons.utils; -import io.sphere.sdk.models.LocalizedEnumValue; - -import java.util.List; - import static io.sphere.sdk.models.LocalizedString.ofEnglish; import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableList; +import io.sphere.sdk.models.LocalizedEnumValue; +import java.util.List; + public final class LocalizedEnumValueFixtures { - public static final LocalizedEnumValue ENUM_VALUE_A = LocalizedEnumValue.of("a", ofEnglish("label_a")); - public static final LocalizedEnumValue ENUM_VALUE_A_DIFFERENT_LABEL = - LocalizedEnumValue.of("a", ofEnglish("label_a_diff")); - public static final LocalizedEnumValue ENUM_VALUE_B = LocalizedEnumValue.of("b", ofEnglish("label_b")); - public static final LocalizedEnumValue ENUM_VALUE_C = LocalizedEnumValue.of("c", ofEnglish("label_c")); - public static final LocalizedEnumValue ENUM_VALUE_D = LocalizedEnumValue.of("d", ofEnglish("label_d")); + public static final LocalizedEnumValue ENUM_VALUE_A = + LocalizedEnumValue.of("a", ofEnglish("label_a")); + public static final LocalizedEnumValue ENUM_VALUE_A_DIFFERENT_LABEL = + LocalizedEnumValue.of("a", ofEnglish("label_a_diff")); + public static final LocalizedEnumValue ENUM_VALUE_B = + LocalizedEnumValue.of("b", ofEnglish("label_b")); + public static final LocalizedEnumValue ENUM_VALUE_C = + LocalizedEnumValue.of("c", ofEnglish("label_c")); + public static final LocalizedEnumValue ENUM_VALUE_D = + LocalizedEnumValue.of("d", ofEnglish("label_d")); - public static final List ENUM_VALUES_ABC = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_C)); - public static final List ENUM_VALUES_BAC = - unmodifiableList(asList(ENUM_VALUE_B, ENUM_VALUE_A, ENUM_VALUE_C)); - public static final List ENUM_VALUES_AB_WITH_DIFFERENT_LABEL = - unmodifiableList(asList(ENUM_VALUE_A_DIFFERENT_LABEL, ENUM_VALUE_B)); - public static final List ENUM_VALUES_AB = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B)); - public static final List ENUM_VALUES_ABB = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_B)); - public static final List ENUM_VALUES_ABD = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_D)); - public static final List ENUM_VALUES_ABCD = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_C, ENUM_VALUE_D)); - public static final List ENUM_VALUES_CAB = - unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_A, ENUM_VALUE_B)); - public static final List ENUM_VALUES_CB = - unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_B)); - public static final List ENUM_VALUES_ACBD = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D)); - public static final List ENUM_VALUES_ADBC = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_D, ENUM_VALUE_B, ENUM_VALUE_C)); - public static final List ENUM_VALUES_CBD = - unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D)); + public static final List ENUM_VALUES_ABC = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_C)); + public static final List ENUM_VALUES_BAC = + unmodifiableList(asList(ENUM_VALUE_B, ENUM_VALUE_A, ENUM_VALUE_C)); + public static final List ENUM_VALUES_AB_WITH_DIFFERENT_LABEL = + unmodifiableList(asList(ENUM_VALUE_A_DIFFERENT_LABEL, ENUM_VALUE_B)); + public static final List ENUM_VALUES_AB = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B)); + public static final List ENUM_VALUES_ABB = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_B)); + public static final List ENUM_VALUES_ABD = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_D)); + public static final List ENUM_VALUES_ABCD = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_C, ENUM_VALUE_D)); + public static final List ENUM_VALUES_CAB = + unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_A, ENUM_VALUE_B)); + public static final List ENUM_VALUES_CB = + unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_B)); + public static final List ENUM_VALUES_ACBD = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D)); + public static final List ENUM_VALUES_ADBC = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_D, ENUM_VALUE_B, ENUM_VALUE_C)); + public static final List ENUM_VALUES_CBD = + unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D)); - private LocalizedEnumValueFixtures() { - } + private LocalizedEnumValueFixtures() {} } diff --git a/src/test/java/com/commercetools/sync/commons/utils/OptionalUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/OptionalUtilsTest.java index 35cdc4bc07..eb51b88e09 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/OptionalUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/OptionalUtilsTest.java @@ -1,10 +1,5 @@ package com.commercetools.sync.commons.utils; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.Optional; - import static com.commercetools.sync.commons.utils.OptionalUtils.filterEmptyOptionals; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -13,89 +8,93 @@ import static java.util.Optional.of; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; + class OptionalUtilsTest { - @Test - void filterEmptyOptionals_withEmptyCollection_ShouldReturnEmptyList() { - // preparation - final List> optionalStrings = emptyList(); - - // test - final List filteredOptionals = filterEmptyOptionals(optionalStrings); - - // assertion - assertEquals(emptyList(), filteredOptionals); - } - - @Test - void filterEmptyOptionals_withNoEmptyOptionals_ShouldReturnOptionalContentsAsList() { - // preparation - final List> optionalStrings = asList(Optional.of("foo"), Optional.of("bar")); - - // test - final List filteredOptionals = filterEmptyOptionals(optionalStrings); - - // assertion - assertEquals(asList("foo", "bar"), filteredOptionals); - } - - @Test - void filterEmptyOptionals_withAllEmptyOptionals_ShouldReturnEmptyList() { - // preparation - final List> optionalStrings = asList(empty(), empty()); - - // test - final List filteredOptionals = filterEmptyOptionals(optionalStrings); - - // assertion - assertEquals(emptyList(), filteredOptionals); - } - - @Test - void filterEmptyOptionals_withSomeEmptyOptionals_ShouldFilterEmptyOptionals() { - // preparation - final List> optionalStrings = asList(Optional.of("foo"), empty()); - - // test - final List filteredOptionals = filterEmptyOptionals(optionalStrings); - - // assertion - assertEquals(singletonList("foo"), filteredOptionals); - } - - @Test - void filterEmptyOptionals_withNoVarArgs_ShouldReturnEmptyList() { - // test - final List filteredOptionals = filterEmptyOptionals(); - - // assertion - assertEquals(emptyList(), filteredOptionals); - } - - @Test - void filterEmptyOptionals_withVarArgsAllEmptyOptionals_ShouldReturnEmptyList() { - // test - final List filteredOptionals = filterEmptyOptionals(empty(), empty()); - - // assertion - assertEquals(emptyList(), filteredOptionals); - } - - @Test - void filterEmptyOptionals_withVarArgsAllNonEmptyOptionals_ShouldReturnAllElementsInList() { - // test - final List filteredOptionals = filterEmptyOptionals(of("foo"), of("bar")); - - // assertion - assertEquals(asList("foo", "bar"), filteredOptionals); - } - - @Test - void filterEmptyOptionals_withVarArgsSomeEmptyOptionals_ShouldFilterEmptyOptionals() { - // test - final List filteredOptionals = filterEmptyOptionals(of("foo"), empty()); - - // assertion - assertEquals(singletonList("foo"), filteredOptionals); - } -} \ No newline at end of file + @Test + void filterEmptyOptionals_withEmptyCollection_ShouldReturnEmptyList() { + // preparation + final List> optionalStrings = emptyList(); + + // test + final List filteredOptionals = filterEmptyOptionals(optionalStrings); + + // assertion + assertEquals(emptyList(), filteredOptionals); + } + + @Test + void filterEmptyOptionals_withNoEmptyOptionals_ShouldReturnOptionalContentsAsList() { + // preparation + final List> optionalStrings = asList(Optional.of("foo"), Optional.of("bar")); + + // test + final List filteredOptionals = filterEmptyOptionals(optionalStrings); + + // assertion + assertEquals(asList("foo", "bar"), filteredOptionals); + } + + @Test + void filterEmptyOptionals_withAllEmptyOptionals_ShouldReturnEmptyList() { + // preparation + final List> optionalStrings = asList(empty(), empty()); + + // test + final List filteredOptionals = filterEmptyOptionals(optionalStrings); + + // assertion + assertEquals(emptyList(), filteredOptionals); + } + + @Test + void filterEmptyOptionals_withSomeEmptyOptionals_ShouldFilterEmptyOptionals() { + // preparation + final List> optionalStrings = asList(Optional.of("foo"), empty()); + + // test + final List filteredOptionals = filterEmptyOptionals(optionalStrings); + + // assertion + assertEquals(singletonList("foo"), filteredOptionals); + } + + @Test + void filterEmptyOptionals_withNoVarArgs_ShouldReturnEmptyList() { + // test + final List filteredOptionals = filterEmptyOptionals(); + + // assertion + assertEquals(emptyList(), filteredOptionals); + } + + @Test + void filterEmptyOptionals_withVarArgsAllEmptyOptionals_ShouldReturnEmptyList() { + // test + final List filteredOptionals = filterEmptyOptionals(empty(), empty()); + + // assertion + assertEquals(emptyList(), filteredOptionals); + } + + @Test + void filterEmptyOptionals_withVarArgsAllNonEmptyOptionals_ShouldReturnAllElementsInList() { + // test + final List filteredOptionals = filterEmptyOptionals(of("foo"), of("bar")); + + // assertion + assertEquals(asList("foo", "bar"), filteredOptionals); + } + + @Test + void filterEmptyOptionals_withVarArgsSomeEmptyOptionals_ShouldFilterEmptyOptionals() { + // test + final List filteredOptionals = filterEmptyOptionals(of("foo"), empty()); + + // assertion + assertEquals(singletonList("foo"), filteredOptionals); + } +} diff --git a/src/test/java/com/commercetools/sync/commons/utils/PlainEnumValueFixtures.java b/src/test/java/com/commercetools/sync/commons/utils/PlainEnumValueFixtures.java index a9c8c07c68..52be2ab1ec 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/PlainEnumValueFixtures.java +++ b/src/test/java/com/commercetools/sync/commons/utils/PlainEnumValueFixtures.java @@ -1,45 +1,43 @@ package com.commercetools.sync.commons.utils; -import io.sphere.sdk.models.EnumValue; - -import java.util.List; - import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableList; +import io.sphere.sdk.models.EnumValue; +import java.util.List; + public final class PlainEnumValueFixtures { - public static final EnumValue ENUM_VALUE_A = EnumValue.of("a", "label_a"); - public static final EnumValue ENUM_VALUE_A_DIFFERENT_LABEL = EnumValue.of("a", "label_a_diff"); - public static final EnumValue ENUM_VALUE_B = EnumValue.of("b", "label_b"); - public static final EnumValue ENUM_VALUE_C = EnumValue.of("c", "label_c"); - public static final EnumValue ENUM_VALUE_D = EnumValue.of("d", "label_d"); + public static final EnumValue ENUM_VALUE_A = EnumValue.of("a", "label_a"); + public static final EnumValue ENUM_VALUE_A_DIFFERENT_LABEL = EnumValue.of("a", "label_a_diff"); + public static final EnumValue ENUM_VALUE_B = EnumValue.of("b", "label_b"); + public static final EnumValue ENUM_VALUE_C = EnumValue.of("c", "label_c"); + public static final EnumValue ENUM_VALUE_D = EnumValue.of("d", "label_d"); - public static final List ENUM_VALUES_ABC = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_C)); - public static final List ENUM_VALUES_AB = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B)); - public static final List ENUM_VALUES_BAC = - unmodifiableList(asList(ENUM_VALUE_B, ENUM_VALUE_A, ENUM_VALUE_C)); - public static final List ENUM_VALUES_AB_WITH_DIFFERENT_LABEL = - unmodifiableList(asList(ENUM_VALUE_A_DIFFERENT_LABEL, ENUM_VALUE_B)); - public static final List ENUM_VALUES_ABB = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_B)); - public static final List ENUM_VALUES_ABD = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_D)); - public static final List ENUM_VALUES_ABCD = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_C, ENUM_VALUE_D)); - public static final List ENUM_VALUES_CAB = - unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_A, ENUM_VALUE_B)); - public static final List ENUM_VALUES_CB = - unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_B)); - public static final List ENUM_VALUES_ACBD = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D)); - public static final List ENUM_VALUES_ADBC = - unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_D, ENUM_VALUE_B, ENUM_VALUE_C)); - public static final List ENUM_VALUES_CBD = - unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D)); + public static final List ENUM_VALUES_ABC = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_C)); + public static final List ENUM_VALUES_AB = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B)); + public static final List ENUM_VALUES_BAC = + unmodifiableList(asList(ENUM_VALUE_B, ENUM_VALUE_A, ENUM_VALUE_C)); + public static final List ENUM_VALUES_AB_WITH_DIFFERENT_LABEL = + unmodifiableList(asList(ENUM_VALUE_A_DIFFERENT_LABEL, ENUM_VALUE_B)); + public static final List ENUM_VALUES_ABB = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_B)); + public static final List ENUM_VALUES_ABD = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_D)); + public static final List ENUM_VALUES_ABCD = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_B, ENUM_VALUE_C, ENUM_VALUE_D)); + public static final List ENUM_VALUES_CAB = + unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_A, ENUM_VALUE_B)); + public static final List ENUM_VALUES_CB = + unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_B)); + public static final List ENUM_VALUES_ACBD = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D)); + public static final List ENUM_VALUES_ADBC = + unmodifiableList(asList(ENUM_VALUE_A, ENUM_VALUE_D, ENUM_VALUE_B, ENUM_VALUE_C)); + public static final List ENUM_VALUES_CBD = + unmodifiableList(asList(ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D)); - private PlainEnumValueFixtures() { - } + private PlainEnumValueFixtures() {} } diff --git a/src/test/java/com/commercetools/sync/commons/utils/ProductAssetCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/ProductAssetCustomUpdateActionUtilsTest.java index 80de854ecc..c1d8a16b2c 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/ProductAssetCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/ProductAssetCustomUpdateActionUtilsTest.java @@ -1,5 +1,13 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; +import static com.commercetools.sync.commons.utils.GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction; +import static io.sphere.sdk.models.ResourceIdentifier.ofId; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.AssetCustomActionBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -10,62 +18,69 @@ import io.sphere.sdk.products.Product; import io.sphere.sdk.products.commands.updateactions.SetAssetCustomField; import io.sphere.sdk.products.commands.updateactions.SetAssetCustomType; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.UUID; - -import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; -import static com.commercetools.sync.commons.utils.GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction; -import static io.sphere.sdk.models.ResourceIdentifier.ofId; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductAssetCustomUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithProductAsset_ShouldBuildProductUpdateAction() { - final Asset asset = mock(Asset.class); - when(asset.getKey()).thenReturn("assetKey"); - final String newCustomTypeId = UUID.randomUUID().toString(); - final int variantId = 1; + @Test + void buildTypedSetCustomTypeUpdateAction_WithProductAsset_ShouldBuildProductUpdateAction() { + final Asset asset = mock(Asset.class); + when(asset.getKey()).thenReturn("assetKey"); + final String newCustomTypeId = UUID.randomUUID().toString(); + final int variantId = 1; - final UpdateAction updateAction = - buildTypedSetCustomTypeUpdateAction(newCustomTypeId, new HashMap<>(), asset, - new AssetCustomActionBuilder(), variantId, - Asset::getId, assetResource -> Asset.resourceTypeId(), Asset::getKey, - ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build()).orElse(null); + final UpdateAction updateAction = + buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), + asset, + new AssetCustomActionBuilder(), + variantId, + Asset::getId, + assetResource -> Asset.resourceTypeId(), + Asset::getKey, + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); - assertThat(updateAction).isInstanceOf(SetAssetCustomType.class); - assertThat((SetAssetCustomType) updateAction) - .hasValues("setAssetCustomType", asset.getId(), null, variantId, true, emptyMap(), ofId(newCustomTypeId)); - } + assertThat(updateAction).isInstanceOf(SetAssetCustomType.class); + assertThat((SetAssetCustomType) updateAction) + .hasValues( + "setAssetCustomType", + asset.getId(), + null, + variantId, + true, + emptyMap(), + ofId(newCustomTypeId)); + } - @Test - void buildRemoveCustomTypeAction_WithProductAsset_ShouldBuildChannelUpdateAction() { - final int variantId = 1; - final UpdateAction updateAction = - new AssetCustomActionBuilder().buildRemoveCustomTypeAction(variantId, "assetKey"); + @Test + void buildRemoveCustomTypeAction_WithProductAsset_ShouldBuildChannelUpdateAction() { + final int variantId = 1; + final UpdateAction updateAction = + new AssetCustomActionBuilder().buildRemoveCustomTypeAction(variantId, "assetKey"); - assertThat(updateAction).isInstanceOf(SetAssetCustomType.class); - assertThat((SetAssetCustomType) updateAction) - .hasValues("setAssetCustomType", null, null, variantId, true, null, ofId(null)); - } + assertThat(updateAction).isInstanceOf(SetAssetCustomType.class); + assertThat((SetAssetCustomType) updateAction) + .hasValues("setAssetCustomType", null, null, variantId, true, null, ofId(null)); + } - @Test - void buildSetCustomFieldAction_WithProductAsset_ShouldBuildProductUpdateAction() { - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final String customFieldName = "name"; - final String assetKey = "assetKey"; - final int variantId = 1; + @Test + void buildSetCustomFieldAction_WithProductAsset_ShouldBuildProductUpdateAction() { + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + final String customFieldName = "name"; + final String assetKey = "assetKey"; + final int variantId = 1; - final UpdateAction updateAction = new AssetCustomActionBuilder() + final UpdateAction updateAction = + new AssetCustomActionBuilder() .buildSetCustomFieldAction(variantId, assetKey, customFieldName, customFieldValue); - assertThat(updateAction).isInstanceOf(SetAssetCustomField.class); - assertThat((SetAssetCustomField) updateAction) - .hasValues("setAssetCustomField", null, null, variantId, true, customFieldName, customFieldValue); - } + assertThat(updateAction).isInstanceOf(SetAssetCustomField.class); + assertThat((SetAssetCustomField) updateAction) + .hasValues( + "setAssetCustomField", null, null, variantId, true, customFieldName, customFieldValue); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/ProductPriceCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/ProductPriceCustomUpdateActionUtilsTest.java index 56b1419559..53d39316e6 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/ProductPriceCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/ProductPriceCustomUpdateActionUtilsTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; +import static com.commercetools.sync.commons.utils.GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction; +import static io.sphere.sdk.models.ResourceIdentifier.ofId; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.PriceCustomActionBuilder; @@ -11,59 +18,60 @@ import io.sphere.sdk.products.Product; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomField; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomType; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.UUID; - -import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; -import static com.commercetools.sync.commons.utils.GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction; -import static io.sphere.sdk.models.ResourceIdentifier.ofId; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductPriceCustomUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithProductPrice_ShouldBuildProductUpdateAction() { - final Price price = mock(Price.class); - when(price.getId()).thenReturn("priceId"); - final String newCustomTypeId = UUID.randomUUID().toString(); + @Test + void buildTypedSetCustomTypeUpdateAction_WithProductPrice_ShouldBuildProductUpdateAction() { + final Price price = mock(Price.class); + when(price.getId()).thenReturn("priceId"); + final String newCustomTypeId = UUID.randomUUID().toString(); - final UpdateAction updateAction = - buildTypedSetCustomTypeUpdateAction(newCustomTypeId, new HashMap<>(), price, - new PriceCustomActionBuilder(), 1, Price::getId, priceResource -> Price.resourceTypeId(), - Price::getId, ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build()).orElse(null); + final UpdateAction updateAction = + buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), + price, + new PriceCustomActionBuilder(), + 1, + Price::getId, + priceResource -> Price.resourceTypeId(), + Price::getId, + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); - assertThat(updateAction).isInstanceOf(SetProductPriceCustomType.class); - assertThat((SetProductPriceCustomType) updateAction) - .hasValues("setProductPriceCustomType", price.getId(), true, emptyMap(), ofId(newCustomTypeId)); - } + assertThat(updateAction).isInstanceOf(SetProductPriceCustomType.class); + assertThat((SetProductPriceCustomType) updateAction) + .hasValues( + "setProductPriceCustomType", price.getId(), true, emptyMap(), ofId(newCustomTypeId)); + } - @Test - void buildRemoveCustomTypeAction_WithProductPrice_ShouldBuildChannelUpdateAction() { - final String priceId = "1"; - final UpdateAction updateAction = new PriceCustomActionBuilder().buildRemoveCustomTypeAction(1, - priceId); + @Test + void buildRemoveCustomTypeAction_WithProductPrice_ShouldBuildChannelUpdateAction() { + final String priceId = "1"; + final UpdateAction updateAction = + new PriceCustomActionBuilder().buildRemoveCustomTypeAction(1, priceId); - assertThat(updateAction).isInstanceOf(SetProductPriceCustomType.class); - assertThat((SetProductPriceCustomType) updateAction) - .hasValues("setProductPriceCustomType", priceId, true, null, ofId(null)); - } + assertThat(updateAction).isInstanceOf(SetProductPriceCustomType.class); + assertThat((SetProductPriceCustomType) updateAction) + .hasValues("setProductPriceCustomType", priceId, true, null, ofId(null)); + } - @Test - void buildSetCustomFieldAction_WithProductPrice_ShouldBuildProductUpdateAction() { - final String priceId = "1"; - final String customFieldName = "name"; - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + @Test + void buildSetCustomFieldAction_WithProductPrice_ShouldBuildProductUpdateAction() { + final String priceId = "1"; + final String customFieldName = "name"; + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final UpdateAction updateAction = new PriceCustomActionBuilder() + final UpdateAction updateAction = + new PriceCustomActionBuilder() .buildSetCustomFieldAction(1, priceId, customFieldName, customFieldValue); - assertThat(updateAction).isInstanceOf(SetProductPriceCustomField.class); - assertThat((SetProductPriceCustomField) updateAction) - .hasValues("setProductPriceCustomField", true, priceId, customFieldName, customFieldValue); - } + assertThat(updateAction).isInstanceOf(SetProductPriceCustomField.class); + assertThat((SetProductPriceCustomField) updateAction) + .hasValues("setProductPriceCustomField", true, priceId, customFieldName, customFieldValue); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/QueryAllTest.java b/src/test/java/com/commercetools/sync/commons/utils/QueryAllTest.java index 676a6234dd..e4a0642226 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/QueryAllTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/QueryAllTest.java @@ -1,14 +1,17 @@ package com.commercetools.sync.commons.utils; +import static io.sphere.sdk.queries.QueryExecutionUtils.DEFAULT_PAGE_SIZE; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.queries.CategoryQuery; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.client.SphereRequest; import io.sphere.sdk.queries.PagedQueryResult; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -16,65 +19,62 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Function; - -import static io.sphere.sdk.queries.QueryExecutionUtils.DEFAULT_PAGE_SIZE; -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; class QueryAllTest { - private static final String CATEGORY_KEY = "catKey"; - private static final String CATEGORY_ID = UUID.randomUUID().toString(); - private static final SphereClient sphereClient = mock(SphereClient.class); - - /** - * Prepares mock data for unit testing the CTP QueryAll utilities; specifically stubs a {@link SphereClient} - * to always return a {@link PagedQueryResult} containing 4 identical mock categories on every call of - * {@link SphereClient#execute(SphereRequest)}. - */ - @BeforeAll - static void setup() { - final Category mockCategory = mock(Category.class); - when(mockCategory.getKey()).thenReturn(CATEGORY_KEY); - when(mockCategory.getId()).thenReturn(CATEGORY_ID); - - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - when(pagedQueryResult.getResults()).thenReturn(asList(mockCategory, mockCategory, mockCategory, mockCategory)); - - when(sphereClient.execute(any())).thenReturn(CompletableFuture.completedFuture(pagedQueryResult)); - } - - @Test - void run_WithCallback_ShouldApplyCallback() { - final QueryAll> query = - QueryAll.of(sphereClient, CategoryQuery.of(), DEFAULT_PAGE_SIZE); - - final Function, Optional> getFirstCategoryInPage = - categoryPage -> categoryPage.isEmpty() ? Optional.empty() : Optional.of(categoryPage.get(0)); - - final List> firstCategories = query.run(getFirstCategoryInPage) - .toCompletableFuture() - .join(); - - assertThat(firstCategories).hasSize(1); - assertThat(firstCategories.get(0)).isPresent(); - assertThat(firstCategories.get(0).get().getKey()).isEqualTo(CATEGORY_KEY); - } - - @Test - void run_WithConsumer_ShouldApplyConsumer() { - final QueryAll query = - QueryAll.of(sphereClient, CategoryQuery.of(), DEFAULT_PAGE_SIZE); - final List categoryIds = new ArrayList<>(); - - final Consumer> categoryIdCollector = page -> - page.forEach(category -> categoryIds.add(category.getId())); - - query.run(categoryIdCollector).toCompletableFuture().join(); - - assertThat(categoryIds).hasSize(4); - categoryIds.forEach(id -> assertThat(id).isEqualTo(CATEGORY_ID)); - } + private static final String CATEGORY_KEY = "catKey"; + private static final String CATEGORY_ID = UUID.randomUUID().toString(); + private static final SphereClient sphereClient = mock(SphereClient.class); + + /** + * Prepares mock data for unit testing the CTP QueryAll utilities; specifically stubs a {@link + * SphereClient} to always return a {@link PagedQueryResult} containing 4 identical mock + * categories on every call of {@link SphereClient#execute(SphereRequest)}. + */ + @BeforeAll + static void setup() { + final Category mockCategory = mock(Category.class); + when(mockCategory.getKey()).thenReturn(CATEGORY_KEY); + when(mockCategory.getId()).thenReturn(CATEGORY_ID); + + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + when(pagedQueryResult.getResults()) + .thenReturn(asList(mockCategory, mockCategory, mockCategory, mockCategory)); + + when(sphereClient.execute(any())) + .thenReturn(CompletableFuture.completedFuture(pagedQueryResult)); + } + + @Test + void run_WithCallback_ShouldApplyCallback() { + final QueryAll> query = + QueryAll.of(sphereClient, CategoryQuery.of(), DEFAULT_PAGE_SIZE); + + final Function, Optional> getFirstCategoryInPage = + categoryPage -> + categoryPage.isEmpty() ? Optional.empty() : Optional.of(categoryPage.get(0)); + + final List> firstCategories = + query.run(getFirstCategoryInPage).toCompletableFuture().join(); + + assertThat(firstCategories).hasSize(1); + assertThat(firstCategories.get(0)).isPresent(); + assertThat(firstCategories.get(0).get().getKey()).isEqualTo(CATEGORY_KEY); + } + + @Test + void run_WithConsumer_ShouldApplyConsumer() { + final QueryAll query = + QueryAll.of(sphereClient, CategoryQuery.of(), DEFAULT_PAGE_SIZE); + final List categoryIds = new ArrayList<>(); + + final Consumer> categoryIdCollector = + page -> page.forEach(category -> categoryIds.add(category.getId())); + + query.run(categoryIdCollector).toCompletableFuture().join(); + + assertThat(categoryIds).hasSize(4); + categoryIds.forEach(id -> assertThat(id).isEqualTo(CATEGORY_ID)); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/ResourceCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/ResourceCustomUpdateActionUtilsTest.java index b8849968fb..718d9abcd0 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/ResourceCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/ResourceCustomUpdateActionUtilsTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildNonNullCustomFieldsUpdateActions; +import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildPrimaryResourceCustomUpdateActions; +import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildSetCustomFieldsUpdateActions; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.categories.CategorySyncMockUtils; import com.commercetools.sync.categories.CategorySyncOptions; import com.commercetools.sync.categories.CategorySyncOptionsBuilder; @@ -24,586 +33,695 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.UUID; - -import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildNonNullCustomFieldsUpdateActions; -import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildPrimaryResourceCustomUpdateActions; -import static com.commercetools.sync.commons.utils.CustomUpdateActionUtils.buildSetCustomFieldsUpdateActions; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ResourceCustomUpdateActionUtilsTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private ArrayList errorMessages; - private ArrayList exceptions; - private CategorySyncOptions categorySyncOptions; - - @BeforeEach - void setupTest() { - errorMessages = new ArrayList<>(); - exceptions = new ArrayList<>(); - final QuadConsumer, Optional, - List>> errorCallback = - (exception, newResource, oldResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }; - - // Mock sync options - categorySyncOptions = CategorySyncOptionsBuilder.of(CTP_CLIENT) - .errorCallback(errorCallback) - .build(); - } - - @Test void buildResourceCustomUpdateActions_WithNonNullCustomFieldsWithDifferentTypes_ShouldBuildUpdateActions() { - final Category oldCategory = mock(Category.class); - final CustomFields oldCategoryCustomFields = mock(CustomFields.class); - final Reference oldCategoryCustomFieldsDraftTypeReference = Type.referenceOfId("2"); - when(oldCategoryCustomFields.getType()).thenReturn(oldCategoryCustomFieldsDraftTypeReference); - when(oldCategory.getCustom()).thenReturn(oldCategoryCustomFields); - - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - final CustomFieldsDraft newCategoryCustomFieldsDraft = mock(CustomFieldsDraft.class); - - final ResourceIdentifier typeResourceIdentifier = Type.referenceOfId("1"); - when(newCategoryCustomFieldsDraft.getType()).thenReturn(typeResourceIdentifier); - when(newCategoryDraft.getCustom()).thenReturn(newCategoryCustomFieldsDraft); - - final List> updateActions = - buildPrimaryResourceCustomUpdateActions(oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), - categorySyncOptions); - - // Should set custom type of old category. - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); - } - - @Test - void buildResourceCustomUpdateActions_WithNullOldCustomFields_ShouldBuildUpdateActions() { - final Category oldCategory = mock(Category.class); - when(oldCategory.getCustom()).thenReturn(null); - - final CategoryDraft newCategoryDraft = - CategoryDraftBuilder.of(LocalizedString.ofEnglish("name"), LocalizedString.ofEnglish("testSlug")) - .key("key") - .parent(ResourceIdentifier.ofId("parentId")) - .custom(CustomFieldsDraft.ofTypeIdAndJson("customTypeId", new HashMap<>())) - .build(); - - final List> updateActions = - buildPrimaryResourceCustomUpdateActions(oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), - categorySyncOptions); - - // Should add custom type to old category. - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); - } - - @Test - void buildResourceCustomUpdateActions_WithNullOldCustomFieldsAndBlankNewTypeId_ShouldCallErrorCallBack() { - final Category oldCategory = mock(Category.class); - when(oldCategory.getCustom()).thenReturn(null); - final String oldCategoryId = "oldCategoryId"; - when(oldCategory.getId()).thenReturn(oldCategoryId); - when(oldCategory.toReference()).thenReturn(Category.referenceOfId(oldCategoryId)); - - final CategoryDraft newCategoryDraft = CategorySyncMockUtils.getMockCategoryDraft(Locale.ENGLISH, "name", - "slug", "key"); - final CustomFieldsDraft mockCustomFieldsDraft = CustomFieldsDraft.ofTypeKeyAndJson("key", new HashMap<>()); - when(newCategoryDraft.getCustom()).thenReturn(mockCustomFieldsDraft); - - - final List> updateActions = - buildPrimaryResourceCustomUpdateActions(oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), - categorySyncOptions); - - // Should add custom type to old category. - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(0); - assertThat(errorMessages.get(0)).isEqualTo(format("Failed to build custom fields update actions on the " - + "category with id '%s'. Reason: New resource's custom type id is blank (empty/null).", oldCategoryId)); - assertThat(exceptions.get(0)).isNull(); - } - - @Test - void buildResourceCustomUpdateActions_WithNullNewCustomFields_ShouldBuildUpdateActions() { - final Category oldCategory = mock(Category.class); - final CustomFields oldCategoryCustomFields = mock(CustomFields.class); - when(oldCategory.getCustom()).thenReturn(oldCategoryCustomFields); - - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getCustom()).thenReturn(null); - - final List> updateActions = - buildPrimaryResourceCustomUpdateActions(oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), - categorySyncOptions); - - // Should remove custom type from old category. - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); - } - - @Test - void buildResourceCustomUpdateActions_WithNullIds_ShouldCallSyncOptionsCallBack() { - final Reference categoryTypeReference = Type.referenceOfId(null); - - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(categoryTypeReference); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(categoryTypeReference); - - // Mock old Category - final Category oldCategory = mock(Category.class); - when(oldCategory.getId()).thenReturn("oldCategoryId"); - when(oldCategory.toReference()).thenReturn(Category.referenceOfId("oldCategoryId")); - when(oldCategory.getCustom()).thenReturn(oldCustomFieldsMock); - - // Mock new Category - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getCustom()).thenReturn(newCustomFieldsMock); - - - final List> updateActions = - buildPrimaryResourceCustomUpdateActions(oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), - categorySyncOptions); - - assertThat(errorMessages).hasSize(1); - assertThat(exceptions).hasSize(1); - assertThat(errorMessages.get(0)).isEqualTo("Failed to build custom fields update actions on the category" - + " with id 'oldCategoryId'. Reason: Custom type ids are not set for both the old and new category."); - assertThat(exceptions.get(0)).isInstanceOf(BuildUpdateActionException.class); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithBothNullCustomFields_ShouldNotBuildUpdateActions() { - final Category oldCategory = mock(Category.class); - when(oldCategory.getCustom()).thenReturn(null); - - final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); - when(newCategoryDraft.getCustom()).thenReturn(null); - - final List> updateActions = - buildPrimaryResourceCustomUpdateActions(oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), - categorySyncOptions); - - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithSameCategoryTypesButDifferentFieldValues_ShouldBuildUpdateActions() - throws BuildUpdateActionException { - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - final Map oldCustomFieldsJsonMapMock = new HashMap<>(); - oldCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryCustomTypeId")); - when(oldCustomFieldsMock.getFieldsJsonMap()).thenReturn(oldCustomFieldsJsonMapMock); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - final Map newCustomFieldsJsonMapMock = new HashMap<>(); - newCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryCustomTypeId")); - when(newCustomFieldsMock.getFields()).thenReturn(newCustomFieldsJsonMapMock); - - - final List> updateActions = - buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, mock(Category.class), new CategoryCustomActionBuilder(), null, Category::getId, - category -> category.toReference().getTypeId(), category -> null, - categorySyncOptions); - - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetCustomField.class); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithDifferentCategoryTypeIds_ShouldBuildUpdateActions() - throws BuildUpdateActionException { - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryCustomTypeId")); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(ResourceIdentifier.ofId("newCategoryCustomTypeId")); - - final List> updateActions = buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, mock(Category.class), new CategoryCustomActionBuilder(), null, Category::getId, - category -> category.toReference().getTypeId(), category -> null, categorySyncOptions); - - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithNullOldCategoryTypeId_ShouldBuildUpdateActions() - throws BuildUpdateActionException { - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId(null)); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryCustomTypeId")); - - final List> updateActions = buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, mock(Category.class), new CategoryCustomActionBuilder(), null, Category::getId, - category -> category.toReference().getTypeId(), category -> null, categorySyncOptions); - - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithNullNewCategoryTypeId_TriggersErrorCallbackAndNoAction() - throws BuildUpdateActionException { - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("1")); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - final Reference value = Type.referenceOfId(null); - - - when(newCustomFieldsMock.getType()).thenReturn(value); - - final String categoryId = UUID.randomUUID().toString(); - final Category category = mock(Category.class); - when(category.getId()).thenReturn(categoryId); - when(category.toReference()).thenReturn(Category.referenceOfId(categoryId)); - - final List> updateActions = buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, category, new CategoryCustomActionBuilder(), null, Category::getId, - categoryRes -> category.toReference().getTypeId(), categoryRes -> null, categorySyncOptions); - - assertThat(errorMessages).hasSize(1); - assertThat(errorMessages.get(0)).isEqualTo(format("Failed to build 'setCustomType' update action on the " - + "%s with id '%s'. Reason: New Custom Type id is blank (null/empty).", category.toReference().getTypeId(), - categoryId)); - assertThat(exceptions).hasSize(1); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithSameIdsButNullNewCustomFields_ShouldBuildUpdateActions() - throws BuildUpdateActionException { - final Reference categoryTypeReference = Type.referenceOfId("categoryCustomTypeId"); - - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - final Map oldCustomFieldsJsonMapMock = new HashMap<>(); - oldCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - when(oldCustomFieldsMock.getType()).thenReturn(categoryTypeReference); - when(oldCustomFieldsMock.getFieldsJsonMap()).thenReturn(oldCustomFieldsJsonMapMock); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(categoryTypeReference); - when(newCustomFieldsMock.getFields()).thenReturn(null); - - final List> updateActions = - buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, mock(Category.class), new CategoryCustomActionBuilder(), null, Category::getId, - category -> category.toReference().getTypeId(), category -> null, categorySyncOptions); - - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(updateActions).isNotNull(); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); - } - - @Test - void buildNonNullCustomFieldsUpdateActions_WithNullIds_ShouldThrowBuildUpdateActionException() { - final Reference categoryTypeReference = Type.referenceOfId(null); - - // Mock old CustomFields - final CustomFields oldCustomFieldsMock = mock(CustomFields.class); - when(oldCustomFieldsMock.getType()).thenReturn(categoryTypeReference); - - // Mock new CustomFieldsDraft - final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); - when(newCustomFieldsMock.getType()).thenReturn(categoryTypeReference); - - final Category oldCategory = mock(Category.class); - when(oldCategory.getId()).thenReturn("oldCategoryId"); - when(oldCategory.toReference()).thenReturn(Category.referenceOfId( null)); - - assertThatThrownBy(() -> - buildNonNullCustomFieldsUpdateActions(oldCustomFieldsMock, - newCustomFieldsMock, oldCategory, new CategoryCustomActionBuilder(), null, Category::getId, - category -> category.toReference().getTypeId(), category -> null, categorySyncOptions)) - .isInstanceOf(BuildUpdateActionException.class) - .hasMessageMatching("Custom type ids are not set for both the old and new category."); - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithDifferentCustomFieldValues_ShouldBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - final List> setCustomFieldsUpdateActions = - buildSetCustomFieldsUpdateActions(oldCustomFields, newCustomFields, mock(Category.class), - new CategoryCustomActionBuilder(),null, category -> null); - - assertThat(setCustomFieldsUpdateActions).isNotNull(); - assertThat(setCustomFieldsUpdateActions).isNotEmpty(); - assertThat(setCustomFieldsUpdateActions).hasSize(2); - final UpdateAction categoryUpdateAction = setCustomFieldsUpdateActions.get(0); - assertThat(categoryUpdateAction).isNotNull(); - assertThat(categoryUpdateAction).isInstanceOf(SetCustomField.class); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNoNewCustomFieldsInOldCustomFields_ShouldBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - newCustomFields.put("url", JsonNodeFactory.instance.objectNode().put("domain", "domain.com")); - newCustomFields.put("size", JsonNodeFactory.instance.objectNode().put("cm", 34)); - - final List> setCustomFieldsUpdateActions = - buildSetCustomFieldsUpdateActions(oldCustomFields, newCustomFields, mock(Category.class), - new CategoryCustomActionBuilder(),null, category -> null); - - assertThat(setCustomFieldsUpdateActions).isNotNull(); - assertThat(setCustomFieldsUpdateActions).isNotEmpty(); - assertThat(setCustomFieldsUpdateActions).hasSize(4); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithOldCustomFieldNotInNewFields_ShouldBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - - final List> setCustomFieldsUpdateActions = - buildSetCustomFieldsUpdateActions(oldCustomFields, newCustomFields, mock(Category.class), - new CategoryCustomActionBuilder(),null, category -> null); - - assertThat(setCustomFieldsUpdateActions).isNotNull(); - assertThat(setCustomFieldsUpdateActions).isNotEmpty(); - assertThat(setCustomFieldsUpdateActions).hasSize(1); - assertThat(setCustomFieldsUpdateActions.get(0)).isInstanceOf(SetCustomField.class); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithSameCustomFieldValues_ShouldNotBuildUpdateActions() { - final BooleanNode oldBooleanFieldValue = JsonNodeFactory.instance.booleanNode(true); - final ObjectNode oldLocalizedFieldValue = JsonNodeFactory.instance.objectNode().put("de", "rot"); - - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("invisibleInShop", oldBooleanFieldValue); - oldCustomFields.put("backgroundColor", oldLocalizedFieldValue); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("invisibleInShop", oldBooleanFieldValue); - newCustomFields.put("backgroundColor", oldLocalizedFieldValue); - - final List> setCustomFieldsUpdateActions = - buildSetCustomFieldsUpdateActions(oldCustomFields, newCustomFields, mock(Category.class), - new CategoryCustomActionBuilder(),null, category -> null); - - assertThat(setCustomFieldsUpdateActions).isNotNull(); - assertThat(setCustomFieldsUpdateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithDifferentOrderOfCustomFieldValues_ShouldNotBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("backgroundColor", - JsonNodeFactory.instance.objectNode().put("de", "rot").put("es", "rojo")); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("backgroundColor", - JsonNodeFactory.instance.objectNode().put("es", "rojo").put("de", "rot")); - - final List> setCustomFieldsUpdateActions = - buildSetCustomFieldsUpdateActions(oldCustomFields, newCustomFields, mock(Category.class), - new CategoryCustomActionBuilder(),null, category -> null); - - assertThat(setCustomFieldsUpdateActions).isNotNull(); - assertThat(setCustomFieldsUpdateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithEmptyCustomFieldValues_ShouldNotBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode()); - - final Map newCustomFields = new HashMap<>(); - newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode()); - - final List> setCustomFieldsUpdateActions = - buildSetCustomFieldsUpdateActions(oldCustomFields, newCustomFields, mock(Category.class), - new CategoryCustomActionBuilder(),null, category -> null); - - assertThat(setCustomFieldsUpdateActions).isNotNull(); - assertThat(setCustomFieldsUpdateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithEmptyCustomFields_ShouldNotBuildUpdateActions() { - final Map oldCustomFields = new HashMap<>(); - final Map newCustomFields = new HashMap<>(); - - final List> setCustomFieldsUpdateActions = - buildSetCustomFieldsUpdateActions(oldCustomFields, newCustomFields, mock(Category.class), - new CategoryCustomActionBuilder(),null, category -> null); - - assertThat(setCustomFieldsUpdateActions).isNotNull(); - assertThat(setCustomFieldsUpdateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNullNewValue_ShouldBuildSetAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", null); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(oldCustomFieldsMap, newCustomFieldsMap, mock(Asset.class), - new CategoryCustomActionBuilder(), null, category -> null); - - // assertion - assertThat(updateActions) - .containsExactly(SetCustomField.ofJson("setOfBooleans", null)); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNullJsonNodeNewValue_ShouldBuildAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(oldCustomFieldsMap, newCustomFieldsMap, mock(Asset.class), - new CategoryCustomActionBuilder(), null, category -> null); - - // assertion - assertThat(updateActions) - .containsExactly(SetCustomField.ofJson("setOfBooleans", null)); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNullNewValueOfNewField_ShouldNotBuildAction() { - // preparation - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", null); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(new HashMap<>(), newCustomFieldsMap, mock(Asset.class), - new CategoryCustomActionBuilder(), null, category -> null); - - // assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void buildSetCustomFieldsUpdateActions_WithNullJsonNodeNewValueOfNewField_ShouldNotBuildAction() { - // preparation - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); - - // test - final List> updateActions = - buildSetCustomFieldsUpdateActions(new HashMap<>(), newCustomFieldsMap, mock(Asset.class), - new CategoryCustomActionBuilder(), null, category -> null); - - // assertion - assertThat(updateActions).isEmpty(); - } + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private ArrayList errorMessages; + private ArrayList exceptions; + private CategorySyncOptions categorySyncOptions; + + @BeforeEach + void setupTest() { + errorMessages = new ArrayList<>(); + exceptions = new ArrayList<>(); + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + errorCallback = + (exception, newResource, oldResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }; + + // Mock sync options + categorySyncOptions = + CategorySyncOptionsBuilder.of(CTP_CLIENT).errorCallback(errorCallback).build(); + } + + @Test + void + buildResourceCustomUpdateActions_WithNonNullCustomFieldsWithDifferentTypes_ShouldBuildUpdateActions() { + final Category oldCategory = mock(Category.class); + final CustomFields oldCategoryCustomFields = mock(CustomFields.class); + final Reference oldCategoryCustomFieldsDraftTypeReference = Type.referenceOfId("2"); + when(oldCategoryCustomFields.getType()).thenReturn(oldCategoryCustomFieldsDraftTypeReference); + when(oldCategory.getCustom()).thenReturn(oldCategoryCustomFields); + + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + final CustomFieldsDraft newCategoryCustomFieldsDraft = mock(CustomFieldsDraft.class); + + final ResourceIdentifier typeResourceIdentifier = Type.referenceOfId("1"); + when(newCategoryCustomFieldsDraft.getType()).thenReturn(typeResourceIdentifier); + when(newCategoryDraft.getCustom()).thenReturn(newCategoryCustomFieldsDraft); + + final List> updateActions = + buildPrimaryResourceCustomUpdateActions( + oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), categorySyncOptions); + + // Should set custom type of old category. + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); + } + + @Test + void buildResourceCustomUpdateActions_WithNullOldCustomFields_ShouldBuildUpdateActions() { + final Category oldCategory = mock(Category.class); + when(oldCategory.getCustom()).thenReturn(null); + + final CategoryDraft newCategoryDraft = + CategoryDraftBuilder.of( + LocalizedString.ofEnglish("name"), LocalizedString.ofEnglish("testSlug")) + .key("key") + .parent(ResourceIdentifier.ofId("parentId")) + .custom(CustomFieldsDraft.ofTypeIdAndJson("customTypeId", new HashMap<>())) + .build(); + + final List> updateActions = + buildPrimaryResourceCustomUpdateActions( + oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), categorySyncOptions); + + // Should add custom type to old category. + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); + } + + @Test + void + buildResourceCustomUpdateActions_WithNullOldCustomFieldsAndBlankNewTypeId_ShouldCallErrorCallBack() { + final Category oldCategory = mock(Category.class); + when(oldCategory.getCustom()).thenReturn(null); + final String oldCategoryId = "oldCategoryId"; + when(oldCategory.getId()).thenReturn(oldCategoryId); + when(oldCategory.toReference()).thenReturn(Category.referenceOfId(oldCategoryId)); + + final CategoryDraft newCategoryDraft = + CategorySyncMockUtils.getMockCategoryDraft(Locale.ENGLISH, "name", "slug", "key"); + final CustomFieldsDraft mockCustomFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("key", new HashMap<>()); + when(newCategoryDraft.getCustom()).thenReturn(mockCustomFieldsDraft); + + final List> updateActions = + buildPrimaryResourceCustomUpdateActions( + oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), categorySyncOptions); + + // Should add custom type to old category. + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(0); + assertThat(errorMessages.get(0)) + .isEqualTo( + format( + "Failed to build custom fields update actions on the " + + "category with id '%s'. Reason: New resource's custom type id is blank (empty/null).", + oldCategoryId)); + assertThat(exceptions.get(0)).isNull(); + } + + @Test + void buildResourceCustomUpdateActions_WithNullNewCustomFields_ShouldBuildUpdateActions() { + final Category oldCategory = mock(Category.class); + final CustomFields oldCategoryCustomFields = mock(CustomFields.class); + when(oldCategory.getCustom()).thenReturn(oldCategoryCustomFields); + + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getCustom()).thenReturn(null); + + final List> updateActions = + buildPrimaryResourceCustomUpdateActions( + oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), categorySyncOptions); + + // Should remove custom type from old category. + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); + } + + @Test + void buildResourceCustomUpdateActions_WithNullIds_ShouldCallSyncOptionsCallBack() { + final Reference categoryTypeReference = Type.referenceOfId(null); + + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(categoryTypeReference); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(categoryTypeReference); + + // Mock old Category + final Category oldCategory = mock(Category.class); + when(oldCategory.getId()).thenReturn("oldCategoryId"); + when(oldCategory.toReference()).thenReturn(Category.referenceOfId("oldCategoryId")); + when(oldCategory.getCustom()).thenReturn(oldCustomFieldsMock); + + // Mock new Category + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getCustom()).thenReturn(newCustomFieldsMock); + + final List> updateActions = + buildPrimaryResourceCustomUpdateActions( + oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), categorySyncOptions); + + assertThat(errorMessages).hasSize(1); + assertThat(exceptions).hasSize(1); + assertThat(errorMessages.get(0)) + .isEqualTo( + "Failed to build custom fields update actions on the category" + + " with id 'oldCategoryId'. Reason: Custom type ids are not set for both the old and new category."); + assertThat(exceptions.get(0)).isInstanceOf(BuildUpdateActionException.class); + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildNonNullCustomFieldsUpdateActions_WithBothNullCustomFields_ShouldNotBuildUpdateActions() { + final Category oldCategory = mock(Category.class); + when(oldCategory.getCustom()).thenReturn(null); + + final CategoryDraft newCategoryDraft = mock(CategoryDraft.class); + when(newCategoryDraft.getCustom()).thenReturn(null); + + final List> updateActions = + buildPrimaryResourceCustomUpdateActions( + oldCategory, newCategoryDraft, new CategoryCustomActionBuilder(), categorySyncOptions); + + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActions).isNotNull(); + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildNonNullCustomFieldsUpdateActions_WithSameCategoryTypesButDifferentFieldValues_ShouldBuildUpdateActions() + throws BuildUpdateActionException { + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + final Map oldCustomFieldsJsonMapMock = new HashMap<>(); + oldCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryCustomTypeId")); + when(oldCustomFieldsMock.getFieldsJsonMap()).thenReturn(oldCustomFieldsJsonMapMock); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + final Map newCustomFieldsJsonMapMock = new HashMap<>(); + newCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryCustomTypeId")); + when(newCustomFieldsMock.getFields()).thenReturn(newCustomFieldsJsonMapMock); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + Category::getId, + category -> category.toReference().getTypeId(), + category -> null, + categorySyncOptions); + + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetCustomField.class); + } + + @Test + void buildNonNullCustomFieldsUpdateActions_WithDifferentCategoryTypeIds_ShouldBuildUpdateActions() + throws BuildUpdateActionException { + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryCustomTypeId")); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()) + .thenReturn(ResourceIdentifier.ofId("newCategoryCustomTypeId")); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + Category::getId, + category -> category.toReference().getTypeId(), + category -> null, + categorySyncOptions); + + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); + } + + @Test + void buildNonNullCustomFieldsUpdateActions_WithNullOldCategoryTypeId_ShouldBuildUpdateActions() + throws BuildUpdateActionException { + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId(null)); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("categoryCustomTypeId")); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + Category::getId, + category -> category.toReference().getTypeId(), + category -> null, + categorySyncOptions); + + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); + } + + @Test + void + buildNonNullCustomFieldsUpdateActions_WithNullNewCategoryTypeId_TriggersErrorCallbackAndNoAction() + throws BuildUpdateActionException { + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(Type.referenceOfId("1")); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + final Reference value = Type.referenceOfId(null); + + when(newCustomFieldsMock.getType()).thenReturn(value); + + final String categoryId = UUID.randomUUID().toString(); + final Category category = mock(Category.class); + when(category.getId()).thenReturn(categoryId); + when(category.toReference()).thenReturn(Category.referenceOfId(categoryId)); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + category, + new CategoryCustomActionBuilder(), + null, + Category::getId, + categoryRes -> category.toReference().getTypeId(), + categoryRes -> null, + categorySyncOptions); + + assertThat(errorMessages).hasSize(1); + assertThat(errorMessages.get(0)) + .isEqualTo( + format( + "Failed to build 'setCustomType' update action on the " + + "%s with id '%s'. Reason: New Custom Type id is blank (null/empty).", + category.toReference().getTypeId(), categoryId)); + assertThat(exceptions).hasSize(1); + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildNonNullCustomFieldsUpdateActions_WithSameIdsButNullNewCustomFields_ShouldBuildUpdateActions() + throws BuildUpdateActionException { + final Reference categoryTypeReference = Type.referenceOfId("categoryCustomTypeId"); + + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + final Map oldCustomFieldsJsonMapMock = new HashMap<>(); + oldCustomFieldsJsonMapMock.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + when(oldCustomFieldsMock.getType()).thenReturn(categoryTypeReference); + when(oldCustomFieldsMock.getFieldsJsonMap()).thenReturn(oldCustomFieldsJsonMapMock); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(categoryTypeReference); + when(newCustomFieldsMock.getFields()).thenReturn(null); + + final List> updateActions = + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + Category::getId, + category -> category.toReference().getTypeId(), + category -> null, + categorySyncOptions); + + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(updateActions).isNotNull(); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isInstanceOf(SetCustomType.class); + } + + @Test + void buildNonNullCustomFieldsUpdateActions_WithNullIds_ShouldThrowBuildUpdateActionException() { + final Reference categoryTypeReference = Type.referenceOfId(null); + + // Mock old CustomFields + final CustomFields oldCustomFieldsMock = mock(CustomFields.class); + when(oldCustomFieldsMock.getType()).thenReturn(categoryTypeReference); + + // Mock new CustomFieldsDraft + final CustomFieldsDraft newCustomFieldsMock = mock(CustomFieldsDraft.class); + when(newCustomFieldsMock.getType()).thenReturn(categoryTypeReference); + + final Category oldCategory = mock(Category.class); + when(oldCategory.getId()).thenReturn("oldCategoryId"); + when(oldCategory.toReference()).thenReturn(Category.referenceOfId(null)); + + assertThatThrownBy( + () -> + buildNonNullCustomFieldsUpdateActions( + oldCustomFieldsMock, + newCustomFieldsMock, + oldCategory, + new CategoryCustomActionBuilder(), + null, + Category::getId, + category -> category.toReference().getTypeId(), + category -> null, + categorySyncOptions)) + .isInstanceOf(BuildUpdateActionException.class) + .hasMessageMatching("Custom type ids are not set for both the old and new category."); + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithDifferentCustomFieldValues_ShouldBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + oldCustomFields.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + + final List> setCustomFieldsUpdateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + assertThat(setCustomFieldsUpdateActions).isNotNull(); + assertThat(setCustomFieldsUpdateActions).isNotEmpty(); + assertThat(setCustomFieldsUpdateActions).hasSize(2); + final UpdateAction categoryUpdateAction = setCustomFieldsUpdateActions.get(0); + assertThat(categoryUpdateAction).isNotNull(); + assertThat(categoryUpdateAction).isInstanceOf(SetCustomField.class); + } + + @Test + void + buildSetCustomFieldsUpdateActions_WithNoNewCustomFieldsInOldCustomFields_ShouldBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + newCustomFields.put("url", JsonNodeFactory.instance.objectNode().put("domain", "domain.com")); + newCustomFields.put("size", JsonNodeFactory.instance.objectNode().put("cm", 34)); + + final List> setCustomFieldsUpdateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + assertThat(setCustomFieldsUpdateActions).isNotNull(); + assertThat(setCustomFieldsUpdateActions).isNotEmpty(); + assertThat(setCustomFieldsUpdateActions).hasSize(4); + } + + @Test + void + buildSetCustomFieldsUpdateActions_WithOldCustomFieldNotInNewFields_ShouldBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFields.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot").put("en", "red")); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + + final List> setCustomFieldsUpdateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + assertThat(setCustomFieldsUpdateActions).isNotNull(); + assertThat(setCustomFieldsUpdateActions).isNotEmpty(); + assertThat(setCustomFieldsUpdateActions).hasSize(1); + assertThat(setCustomFieldsUpdateActions.get(0)).isInstanceOf(SetCustomField.class); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithSameCustomFieldValues_ShouldNotBuildUpdateActions() { + final BooleanNode oldBooleanFieldValue = JsonNodeFactory.instance.booleanNode(true); + final ObjectNode oldLocalizedFieldValue = + JsonNodeFactory.instance.objectNode().put("de", "rot"); + + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put("invisibleInShop", oldBooleanFieldValue); + oldCustomFields.put("backgroundColor", oldLocalizedFieldValue); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("invisibleInShop", oldBooleanFieldValue); + newCustomFields.put("backgroundColor", oldLocalizedFieldValue); + + final List> setCustomFieldsUpdateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + assertThat(setCustomFieldsUpdateActions).isNotNull(); + assertThat(setCustomFieldsUpdateActions).isEmpty(); + } + + @Test + void + buildSetCustomFieldsUpdateActions_WithDifferentOrderOfCustomFieldValues_ShouldNotBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put( + "backgroundColor", + JsonNodeFactory.instance.objectNode().put("de", "rot").put("es", "rojo")); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put( + "backgroundColor", + JsonNodeFactory.instance.objectNode().put("es", "rojo").put("de", "rot")); + + final List> setCustomFieldsUpdateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + assertThat(setCustomFieldsUpdateActions).isNotNull(); + assertThat(setCustomFieldsUpdateActions).isEmpty(); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithEmptyCustomFieldValues_ShouldNotBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + oldCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode()); + + final Map newCustomFields = new HashMap<>(); + newCustomFields.put("backgroundColor", JsonNodeFactory.instance.objectNode()); + + final List> setCustomFieldsUpdateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + assertThat(setCustomFieldsUpdateActions).isNotNull(); + assertThat(setCustomFieldsUpdateActions).isEmpty(); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithEmptyCustomFields_ShouldNotBuildUpdateActions() { + final Map oldCustomFields = new HashMap<>(); + final Map newCustomFields = new HashMap<>(); + + final List> setCustomFieldsUpdateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFields, + newCustomFields, + mock(Category.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + assertThat(setCustomFieldsUpdateActions).isNotNull(); + assertThat(setCustomFieldsUpdateActions).isEmpty(); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithNullNewValue_ShouldBuildSetAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put( + "setOfBooleans", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", null); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFieldsMap, + newCustomFieldsMap, + mock(Asset.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + // assertion + assertThat(updateActions).containsExactly(SetCustomField.ofJson("setOfBooleans", null)); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithNullJsonNodeNewValue_ShouldBuildAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put( + "setOfBooleans", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + oldCustomFieldsMap, + newCustomFieldsMap, + mock(Asset.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + // assertion + assertThat(updateActions).containsExactly(SetCustomField.ofJson("setOfBooleans", null)); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithNullNewValueOfNewField_ShouldNotBuildAction() { + // preparation + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", null); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + new HashMap<>(), + newCustomFieldsMap, + mock(Asset.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + // assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void buildSetCustomFieldsUpdateActions_WithNullJsonNodeNewValueOfNewField_ShouldNotBuildAction() { + // preparation + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(new HashMap<>()); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); + + // test + final List> updateActions = + buildSetCustomFieldsUpdateActions( + new HashMap<>(), + newCustomFieldsMap, + mock(Asset.class), + new CategoryCustomActionBuilder(), + null, + category -> null); + + // assertion + assertThat(updateActions).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/ResourceIdentifierUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/ResourceIdentifierUtilsTest.java index 58280934a5..cbf08245d3 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/ResourceIdentifierUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/ResourceIdentifierUtilsTest.java @@ -1,5 +1,13 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.isReferenceOfType; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.toResourceIdentifierIfNotNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; @@ -8,153 +16,148 @@ import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.Product; -import org.junit.jupiter.api.Test; - import java.util.UUID; - -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.isReferenceOfType; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.toResourceIdentifierIfNotNull; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ResourceIdentifierUtilsTest { - @Test - void toResourceIdentifierIfNotNull_WithNullResource_ShouldReturnNull() { - assertThat(toResourceIdentifierIfNotNull(null)).isNull(); - } - - @Test - void toResourceIdentifierIfNotNull_WithNonNullResource_ShouldReturnCorrectResourceIdentifier() { - final Category category = mock(Category.class); - when(category.getId()).thenReturn(UUID.randomUUID().toString()); - when(category.toResourceIdentifier()).thenCallRealMethod(); - when(category.toReference()).thenCallRealMethod(); - - final ResourceIdentifier categoryResourceIdentifier = toResourceIdentifierIfNotNull(category); - - assertThat(categoryResourceIdentifier).isNotNull(); - assertThat(categoryResourceIdentifier.getId()).isEqualTo(category.getId()); - assertThat(categoryResourceIdentifier.getTypeId()).isEqualTo(Category.resourceTypeId()); - } - - @Test - void toResourceIdentifierIfNotNull_WithNonNullReference_ShouldReturnCorrectResourceIdentifier() { - final Reference categoryReference = Category.referenceOfId("foo"); - - final ResourceIdentifier categoryResourceIdentifier = toResourceIdentifierIfNotNull( - categoryReference); - - assertThat(categoryResourceIdentifier).isNotNull(); - assertThat(categoryResourceIdentifier.getId()).isEqualTo("foo"); - assertThat(categoryResourceIdentifier.getTypeId()).isEqualTo(Category.resourceTypeId()); - } - - @Test - void isReferenceOfType_WithEmptyObjectNodeValueVsEmptyStringAsReferenceTypeId_ShouldReturnFalse() { - // preparation - final ObjectNode emptyObjectNode = JsonNodeFactory.instance.objectNode(); - - // test - final boolean isReference = isReferenceOfType(emptyObjectNode, ""); - - // assertion - assertThat(isReference).isFalse(); - } - - @Test - void isReferenceOfType_WithEmptyObjectNodeValueVsProductReferenceTypeId_ShouldReturnFalse() { - // preparation - final ObjectNode emptyObjectNode = JsonNodeFactory.instance.objectNode(); - - // test - final boolean isReference = isReferenceOfType(emptyObjectNode, Product.referenceTypeId()); - - // assertion - assertThat(isReference).isFalse(); - } - - @Test - void isReferenceOfType_WithTextNodeVsProductReferenceTypeId_ShouldReturnFalse() { - // preparation - final TextNode textNode = JsonNodeFactory.instance.textNode("foo"); - - // test - final boolean isReference = isReferenceOfType(textNode, Product.referenceTypeId()); - - // assertion - assertThat(isReference).isFalse(); - } - - @Test - void isReferenceOfType_WithInvalidReferenceVsProductReferenceTypeId_ShouldReturnFalse() { - // preparation - final ObjectNode invalidReferenceObject = JsonNodeFactory.instance.objectNode(); - invalidReferenceObject.put("anyString", "anyValue"); - - // test - final boolean isReference = isReferenceOfType(invalidReferenceObject, Product.referenceTypeId()); - - // assertion - assertThat(isReference).isFalse(); - } - - @Test - void isReferenceOfType_WithCategoryReferenceVsProductReferenceTypeId_ShouldReturnFalse() { - // preparation - final ObjectNode categoryReference = JsonNodeFactory.instance.objectNode(); - categoryReference.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); - categoryReference.put(REFERENCE_ID_FIELD, UUID.randomUUID().toString()); - - // test - final boolean isReference = isReferenceOfType(categoryReference, Product.referenceTypeId()); - - // assertion - assertThat(isReference).isFalse(); - } - - @Test - void isReferenceOfType_WithCategoryReferenceVsCategoryReferenceTypeId_ShouldReturnTrue() { - // preparation - final ObjectNode categoryReference = JsonNodeFactory.instance.objectNode(); - categoryReference.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); - categoryReference.put(REFERENCE_ID_FIELD, UUID.randomUUID().toString()); - - // test - final boolean isReference = isReferenceOfType(categoryReference, Category.referenceTypeId()); - - // assertion - assertThat(isReference).isTrue(); - } - - @Test - void isReferenceOfType_WithCustomObjectReferenceVsProductReferenceTypeId_ShouldReturnFalse() { - // preparation - final ObjectNode customObjectReference = JsonNodeFactory.instance.objectNode(); - customObjectReference.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); - customObjectReference.put(REFERENCE_ID_FIELD, UUID.randomUUID().toString()); - - // test - final boolean isReference = isReferenceOfType(customObjectReference, Product.referenceTypeId()); - - // assertion - assertThat(isReference).isFalse(); - } - - @Test - void isReferenceOfType_WithCustomObjectReferenceVsCustomObjectReferenceTypeId_ShouldReturnTrue() { - // preparation - final ObjectNode customObjectReference = JsonNodeFactory.instance.objectNode(); - customObjectReference.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); - customObjectReference.put(REFERENCE_ID_FIELD, UUID.randomUUID().toString()); - - // test - final boolean isReference = isReferenceOfType(customObjectReference, CustomObject.referenceTypeId()); - - // assertion - assertThat(isReference).isTrue(); - } + @Test + void toResourceIdentifierIfNotNull_WithNullResource_ShouldReturnNull() { + assertThat(toResourceIdentifierIfNotNull(null)).isNull(); + } + + @Test + void toResourceIdentifierIfNotNull_WithNonNullResource_ShouldReturnCorrectResourceIdentifier() { + final Category category = mock(Category.class); + when(category.getId()).thenReturn(UUID.randomUUID().toString()); + when(category.toResourceIdentifier()).thenCallRealMethod(); + when(category.toReference()).thenCallRealMethod(); + + final ResourceIdentifier categoryResourceIdentifier = + toResourceIdentifierIfNotNull(category); + + assertThat(categoryResourceIdentifier).isNotNull(); + assertThat(categoryResourceIdentifier.getId()).isEqualTo(category.getId()); + assertThat(categoryResourceIdentifier.getTypeId()).isEqualTo(Category.resourceTypeId()); + } + + @Test + void toResourceIdentifierIfNotNull_WithNonNullReference_ShouldReturnCorrectResourceIdentifier() { + final Reference categoryReference = Category.referenceOfId("foo"); + + final ResourceIdentifier categoryResourceIdentifier = + toResourceIdentifierIfNotNull(categoryReference); + + assertThat(categoryResourceIdentifier).isNotNull(); + assertThat(categoryResourceIdentifier.getId()).isEqualTo("foo"); + assertThat(categoryResourceIdentifier.getTypeId()).isEqualTo(Category.resourceTypeId()); + } + + @Test + void + isReferenceOfType_WithEmptyObjectNodeValueVsEmptyStringAsReferenceTypeId_ShouldReturnFalse() { + // preparation + final ObjectNode emptyObjectNode = JsonNodeFactory.instance.objectNode(); + + // test + final boolean isReference = isReferenceOfType(emptyObjectNode, ""); + + // assertion + assertThat(isReference).isFalse(); + } + + @Test + void isReferenceOfType_WithEmptyObjectNodeValueVsProductReferenceTypeId_ShouldReturnFalse() { + // preparation + final ObjectNode emptyObjectNode = JsonNodeFactory.instance.objectNode(); + + // test + final boolean isReference = isReferenceOfType(emptyObjectNode, Product.referenceTypeId()); + + // assertion + assertThat(isReference).isFalse(); + } + + @Test + void isReferenceOfType_WithTextNodeVsProductReferenceTypeId_ShouldReturnFalse() { + // preparation + final TextNode textNode = JsonNodeFactory.instance.textNode("foo"); + + // test + final boolean isReference = isReferenceOfType(textNode, Product.referenceTypeId()); + + // assertion + assertThat(isReference).isFalse(); + } + + @Test + void isReferenceOfType_WithInvalidReferenceVsProductReferenceTypeId_ShouldReturnFalse() { + // preparation + final ObjectNode invalidReferenceObject = JsonNodeFactory.instance.objectNode(); + invalidReferenceObject.put("anyString", "anyValue"); + + // test + final boolean isReference = + isReferenceOfType(invalidReferenceObject, Product.referenceTypeId()); + + // assertion + assertThat(isReference).isFalse(); + } + + @Test + void isReferenceOfType_WithCategoryReferenceVsProductReferenceTypeId_ShouldReturnFalse() { + // preparation + final ObjectNode categoryReference = JsonNodeFactory.instance.objectNode(); + categoryReference.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); + categoryReference.put(REFERENCE_ID_FIELD, UUID.randomUUID().toString()); + + // test + final boolean isReference = isReferenceOfType(categoryReference, Product.referenceTypeId()); + + // assertion + assertThat(isReference).isFalse(); + } + + @Test + void isReferenceOfType_WithCategoryReferenceVsCategoryReferenceTypeId_ShouldReturnTrue() { + // preparation + final ObjectNode categoryReference = JsonNodeFactory.instance.objectNode(); + categoryReference.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); + categoryReference.put(REFERENCE_ID_FIELD, UUID.randomUUID().toString()); + + // test + final boolean isReference = isReferenceOfType(categoryReference, Category.referenceTypeId()); + + // assertion + assertThat(isReference).isTrue(); + } + + @Test + void isReferenceOfType_WithCustomObjectReferenceVsProductReferenceTypeId_ShouldReturnFalse() { + // preparation + final ObjectNode customObjectReference = JsonNodeFactory.instance.objectNode(); + customObjectReference.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); + customObjectReference.put(REFERENCE_ID_FIELD, UUID.randomUUID().toString()); + + // test + final boolean isReference = isReferenceOfType(customObjectReference, Product.referenceTypeId()); + + // assertion + assertThat(isReference).isFalse(); + } + + @Test + void isReferenceOfType_WithCustomObjectReferenceVsCustomObjectReferenceTypeId_ShouldReturnTrue() { + // preparation + final ObjectNode customObjectReference = JsonNodeFactory.instance.objectNode(); + customObjectReference.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); + customObjectReference.put(REFERENCE_ID_FIELD, UUID.randomUUID().toString()); + + // test + final boolean isReference = + isReferenceOfType(customObjectReference, CustomObject.referenceTypeId()); + + // assertion + assertThat(isReference).isTrue(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/ShoppingListCustomUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/ShoppingListCustomUpdateActionUtilsTest.java index 2b6f3f605e..07a917bba2 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/ShoppingListCustomUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/ShoppingListCustomUpdateActionUtilsTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; +import static io.sphere.sdk.models.ResourceIdentifier.ofId; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.shoppinglists.ShoppingListSyncOptionsBuilder; import com.commercetools.sync.shoppinglists.utils.LineItemCustomActionBuilder; import com.commercetools.sync.shoppinglists.utils.ShoppingListCustomActionBuilder; @@ -17,150 +24,174 @@ import io.sphere.sdk.shoppinglists.commands.updateactions.SetLineItemCustomType; import io.sphere.sdk.shoppinglists.commands.updateactions.SetTextLineItemCustomField; import io.sphere.sdk.shoppinglists.commands.updateactions.SetTextLineItemCustomType; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.UUID; - -import static com.commercetools.sync.commons.asserts.actions.AssertionsForUpdateActions.assertThat; -import static io.sphere.sdk.models.ResourceIdentifier.ofId; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ShoppingListCustomUpdateActionUtilsTest { - @Test - void buildTypedSetCustomTypeUpdateAction_WithShoppingListResource_ShouldBuildShoppingListUpdateAction() { - final String newCustomTypeId = UUID.randomUUID().toString(); - - final UpdateAction updateAction = - GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction(newCustomTypeId, new HashMap<>(), - mock(ShoppingList.class), ShoppingListCustomActionBuilder.of(), null, ShoppingList::getId, - shoppingListResource -> shoppingListResource.toReference().getTypeId(), shoppingListResource -> null, - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()).orElse(null); - - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); - } - - @Test - void buildRemoveCustomTypeAction_WithShoppingListResource_ShouldBuildShoppingListUpdateAction() { - final UpdateAction updateAction = - ShoppingListCustomActionBuilder.of().buildRemoveCustomTypeAction(null, null); - - assertThat(updateAction).isInstanceOf(SetCustomType.class); - assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); - } - - @Test - void buildSetCustomFieldAction_WithShoppingListResource_ShouldBuildShoppingListUpdateAction() { - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final String customFieldName = "name"; - - final UpdateAction updateAction = ShoppingListCustomActionBuilder.of() - .buildSetCustomFieldAction(null, - null, customFieldName, - customFieldValue); - - assertThat(updateAction).isInstanceOf(SetCustomField.class); - assertThat((SetCustomField) updateAction).hasValues("setCustomField", customFieldName, customFieldValue); - } - - @Test - void buildTypedSetLineItemCustomTypeUpdateAction_WithLineItemResource_ShouldBuildShoppingListUpdateAction() { - final LineItem lineItem = mock(LineItem.class); - when(lineItem.getId()).thenReturn("line_item_id"); - - final String newCustomTypeId = UUID.randomUUID().toString(); - - final UpdateAction updateAction = - GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction(newCustomTypeId, new HashMap<>(), + @Test + void + buildTypedSetCustomTypeUpdateAction_WithShoppingListResource_ShouldBuildShoppingListUpdateAction() { + final String newCustomTypeId = UUID.randomUUID().toString(); + + final UpdateAction updateAction = + GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), + mock(ShoppingList.class), + ShoppingListCustomActionBuilder.of(), + null, + ShoppingList::getId, + shoppingListResource -> shoppingListResource.toReference().getTypeId(), + shoppingListResource -> null, + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); + + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction) + .hasValues("setCustomType", emptyMap(), ofId(newCustomTypeId)); + } + + @Test + void buildRemoveCustomTypeAction_WithShoppingListResource_ShouldBuildShoppingListUpdateAction() { + final UpdateAction updateAction = + ShoppingListCustomActionBuilder.of().buildRemoveCustomTypeAction(null, null); + + assertThat(updateAction).isInstanceOf(SetCustomType.class); + assertThat((SetCustomType) updateAction).hasValues("setCustomType", null, ofId(null)); + } + + @Test + void buildSetCustomFieldAction_WithShoppingListResource_ShouldBuildShoppingListUpdateAction() { + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + final String customFieldName = "name"; + + final UpdateAction updateAction = + ShoppingListCustomActionBuilder.of() + .buildSetCustomFieldAction(null, null, customFieldName, customFieldValue); + + assertThat(updateAction).isInstanceOf(SetCustomField.class); + assertThat((SetCustomField) updateAction) + .hasValues("setCustomField", customFieldName, customFieldValue); + } + + @Test + void + buildTypedSetLineItemCustomTypeUpdateAction_WithLineItemResource_ShouldBuildShoppingListUpdateAction() { + final LineItem lineItem = mock(LineItem.class); + when(lineItem.getId()).thenReturn("line_item_id"); + + final String newCustomTypeId = UUID.randomUUID().toString(); + + final UpdateAction updateAction = + GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), lineItem::getCustom, - new LineItemCustomActionBuilder(), -1, t -> lineItem.getId(), - lineItemResource -> LineItem.resourceTypeId(), t -> lineItem.getId(), - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isInstanceOf(SetLineItemCustomType.class); - assertThat((SetLineItemCustomType) updateAction) - .hasValues("setLineItemCustomType", emptyMap(), ofId(newCustomTypeId)); - assertThat(((SetLineItemCustomType) updateAction).getLineItemId()).isEqualTo("line_item_id"); - } - - @Test - void buildRemoveLineItemCustomTypeAction_WithLineItemResource_ShouldBuildShoppingListUpdateAction() { - final UpdateAction updateAction = - new LineItemCustomActionBuilder().buildRemoveCustomTypeAction(-1, "line_item_id"); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isInstanceOf(SetLineItemCustomType.class); - assertThat((SetLineItemCustomType) updateAction) - .hasValues("setLineItemCustomType", null, ofId(null)); - assertThat(((SetLineItemCustomType) updateAction).getLineItemId()).isEqualTo("line_item_id"); - } - - @Test - void buildSetLineItemCustomFieldAction_WithLineItemResource_ShouldBuildShoppingListUpdateAction() { - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final String customFieldName = "name"; - - final UpdateAction updateAction = new LineItemCustomActionBuilder() + new LineItemCustomActionBuilder(), + -1, + t -> lineItem.getId(), + lineItemResource -> LineItem.resourceTypeId(), + t -> lineItem.getId(), + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isInstanceOf(SetLineItemCustomType.class); + assertThat((SetLineItemCustomType) updateAction) + .hasValues("setLineItemCustomType", emptyMap(), ofId(newCustomTypeId)); + assertThat(((SetLineItemCustomType) updateAction).getLineItemId()).isEqualTo("line_item_id"); + } + + @Test + void + buildRemoveLineItemCustomTypeAction_WithLineItemResource_ShouldBuildShoppingListUpdateAction() { + final UpdateAction updateAction = + new LineItemCustomActionBuilder().buildRemoveCustomTypeAction(-1, "line_item_id"); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isInstanceOf(SetLineItemCustomType.class); + assertThat((SetLineItemCustomType) updateAction) + .hasValues("setLineItemCustomType", null, ofId(null)); + assertThat(((SetLineItemCustomType) updateAction).getLineItemId()).isEqualTo("line_item_id"); + } + + @Test + void + buildSetLineItemCustomFieldAction_WithLineItemResource_ShouldBuildShoppingListUpdateAction() { + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + final String customFieldName = "name"; + + final UpdateAction updateAction = + new LineItemCustomActionBuilder() .buildSetCustomFieldAction(-1, "line_item_id", customFieldName, customFieldValue); - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isInstanceOf(SetLineItemCustomField.class); - assertThat((SetLineItemCustomField) updateAction) - .hasValues("setLineItemCustomField", customFieldName, customFieldValue); - assertThat(((SetLineItemCustomField) updateAction).getLineItemId()).isEqualTo("line_item_id"); - } - - @Test - void buildTypedSetTextLineItemCustomTypeUpdateAction_WithTextLineItemRes_ShouldBuildShoppingListUpdateAction() { - final TextLineItem textLineItem = mock(TextLineItem.class); - when(textLineItem.getId()).thenReturn("text_line_item_id"); - - final String newCustomTypeId = UUID.randomUUID().toString(); - - final UpdateAction updateAction = - GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction(newCustomTypeId, new HashMap<>(), + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isInstanceOf(SetLineItemCustomField.class); + assertThat((SetLineItemCustomField) updateAction) + .hasValues("setLineItemCustomField", customFieldName, customFieldValue); + assertThat(((SetLineItemCustomField) updateAction).getLineItemId()).isEqualTo("line_item_id"); + } + + @Test + void + buildTypedSetTextLineItemCustomTypeUpdateAction_WithTextLineItemRes_ShouldBuildShoppingListUpdateAction() { + final TextLineItem textLineItem = mock(TextLineItem.class); + when(textLineItem.getId()).thenReturn("text_line_item_id"); + + final String newCustomTypeId = UUID.randomUUID().toString(); + + final UpdateAction updateAction = + GenericUpdateActionUtils.buildTypedSetCustomTypeUpdateAction( + newCustomTypeId, + new HashMap<>(), textLineItem::getCustom, - new TextLineItemCustomActionBuilder(), -1, t -> textLineItem.getId(), - textLineItemResource -> TextLineItem.resourceTypeId(), t -> textLineItem.getId(), - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isInstanceOf(SetTextLineItemCustomType.class); - assertThat((SetTextLineItemCustomType) updateAction) - .hasValues("setTextLineItemCustomType", emptyMap(), ofId(newCustomTypeId)); - assertThat(((SetTextLineItemCustomType) updateAction).getTextLineItemId()).isEqualTo("text_line_item_id"); - } - - @Test - void buildRemoveTextLineItemCustomTypeAction_WithTextLineItemResource_ShouldBuildShoppingListUpdateAction() { - final UpdateAction updateAction = new TextLineItemCustomActionBuilder() - .buildRemoveCustomTypeAction(-1, "text_line_item_id"); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isInstanceOf(SetTextLineItemCustomType.class); - assertThat((SetTextLineItemCustomType) updateAction) - .hasValues("setTextLineItemCustomType", null, ofId(null)); - assertThat(((SetTextLineItemCustomType) updateAction).getTextLineItemId()).isEqualTo("text_line_item_id"); - } - - @Test - void buildSetTextLineItemCustomFieldAction_WithTextLineItemResource_ShouldBuildShoppingListUpdateAction() { - final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); - final String customFieldName = "name"; - - final UpdateAction updateAction = new TextLineItemCustomActionBuilder() + new TextLineItemCustomActionBuilder(), + -1, + t -> textLineItem.getId(), + textLineItemResource -> TextLineItem.resourceTypeId(), + t -> textLineItem.getId(), + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()) + .orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isInstanceOf(SetTextLineItemCustomType.class); + assertThat((SetTextLineItemCustomType) updateAction) + .hasValues("setTextLineItemCustomType", emptyMap(), ofId(newCustomTypeId)); + assertThat(((SetTextLineItemCustomType) updateAction).getTextLineItemId()) + .isEqualTo("text_line_item_id"); + } + + @Test + void + buildRemoveTextLineItemCustomTypeAction_WithTextLineItemResource_ShouldBuildShoppingListUpdateAction() { + final UpdateAction updateAction = + new TextLineItemCustomActionBuilder().buildRemoveCustomTypeAction(-1, "text_line_item_id"); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isInstanceOf(SetTextLineItemCustomType.class); + assertThat((SetTextLineItemCustomType) updateAction) + .hasValues("setTextLineItemCustomType", null, ofId(null)); + assertThat(((SetTextLineItemCustomType) updateAction).getTextLineItemId()) + .isEqualTo("text_line_item_id"); + } + + @Test + void + buildSetTextLineItemCustomFieldAction_WithTextLineItemResource_ShouldBuildShoppingListUpdateAction() { + final JsonNode customFieldValue = JsonNodeFactory.instance.textNode("foo"); + final String customFieldName = "name"; + + final UpdateAction updateAction = + new TextLineItemCustomActionBuilder() .buildSetCustomFieldAction(-1, "text_line_item_id", customFieldName, customFieldValue); - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isInstanceOf(SetTextLineItemCustomField.class); - assertThat((SetTextLineItemCustomField) updateAction) - .hasValues("setTextLineItemCustomField", customFieldName, customFieldValue); - assertThat(((SetTextLineItemCustomField) updateAction).getTextLineItemId()).isEqualTo("text_line_item_id"); - } + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isInstanceOf(SetTextLineItemCustomField.class); + assertThat((SetTextLineItemCustomField) updateAction) + .hasValues("setTextLineItemCustomField", customFieldName, customFieldValue); + assertThat(((SetTextLineItemCustomField) updateAction).getTextLineItemId()) + .isEqualTo("text_line_item_id"); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/StreamUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/StreamUtilsTest.java index 38e6e9c1c1..3726d7d003 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/StreamUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/StreamUtilsTest.java @@ -1,60 +1,61 @@ package com.commercetools.sync.commons.utils; - -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.stream.Stream; - import static com.commercetools.sync.commons.utils.StreamUtils.filterNullAndMap; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + class StreamUtilsTest { - @Test - void filterNullAndMap_emptyStream_ShouldReturnAnEmptyStream() { - final Stream mappedStream = filterNullAndMap(Stream.empty(), String::length); - final List mappedList = mappedStream.collect(toList()); - assertThat(mappedList).isEmpty(); - } - - @Test - void filterNullAndMap_streamWithOnlyNullElements_ShouldReturnAnEmptyStream() { - final Stream mappedStream = filterNullAndMap(Stream.of(null, null, null), String::length); - final List mappedList = mappedStream.collect(toList()); - assertThat(mappedList).isEmpty(); - } - - @Test - void filterNullAndMap_streamWithOneNullElement_ShouldReturnAnEmptyStream() { - final String nullString = null; - final Stream mappedStream = filterNullAndMap(Stream.of(nullString), String::length); - final List mappedList = mappedStream.collect(toList()); - assertThat(mappedList).isEmpty(); - } - - @Test - void filterNullAndMap_singleElementStream_ShouldReturnMappedStream() { - final Stream mappedStream = filterNullAndMap(Stream.of("foo"), String::length); - final List mappedList = mappedStream.collect(toList()); - assertThat(mappedList).isNotEmpty(); - assertThat(mappedList).containsExactly(3); - } - - @Test - void filterNullAndMap_multipleElementsStream_ShouldReturnMappedStream() { - final Stream mappedStream = filterNullAndMap(Stream.of("james", "hetfield"), String::length); - final List mappedList = mappedStream.collect(toList()); - assertThat(mappedList).isNotEmpty(); - assertThat(mappedList).containsExactly(5, 8); - } - - @Test - void filterNullAndMap_nonEmptyStreamWithNullElements_ShouldReturnMappedStream() { - final Stream mappedStream = filterNullAndMap(Stream.of("james", "hetfield", null), String::length); - final List mappedList = mappedStream.collect(toList()); - assertThat(mappedList).isNotEmpty(); - assertThat(mappedList).containsExactly(5, 8); - } + @Test + void filterNullAndMap_emptyStream_ShouldReturnAnEmptyStream() { + final Stream mappedStream = filterNullAndMap(Stream.empty(), String::length); + final List mappedList = mappedStream.collect(toList()); + assertThat(mappedList).isEmpty(); + } + + @Test + void filterNullAndMap_streamWithOnlyNullElements_ShouldReturnAnEmptyStream() { + final Stream mappedStream = + filterNullAndMap(Stream.of(null, null, null), String::length); + final List mappedList = mappedStream.collect(toList()); + assertThat(mappedList).isEmpty(); + } + + @Test + void filterNullAndMap_streamWithOneNullElement_ShouldReturnAnEmptyStream() { + final String nullString = null; + final Stream mappedStream = filterNullAndMap(Stream.of(nullString), String::length); + final List mappedList = mappedStream.collect(toList()); + assertThat(mappedList).isEmpty(); + } + + @Test + void filterNullAndMap_singleElementStream_ShouldReturnMappedStream() { + final Stream mappedStream = filterNullAndMap(Stream.of("foo"), String::length); + final List mappedList = mappedStream.collect(toList()); + assertThat(mappedList).isNotEmpty(); + assertThat(mappedList).containsExactly(3); + } + + @Test + void filterNullAndMap_multipleElementsStream_ShouldReturnMappedStream() { + final Stream mappedStream = + filterNullAndMap(Stream.of("james", "hetfield"), String::length); + final List mappedList = mappedStream.collect(toList()); + assertThat(mappedList).isNotEmpty(); + assertThat(mappedList).containsExactly(5, 8); + } + + @Test + void filterNullAndMap_nonEmptyStreamWithNullElements_ShouldReturnMappedStream() { + final Stream mappedStream = + filterNullAndMap(Stream.of("james", "hetfield", null), String::length); + final List mappedList = mappedStream.collect(toList()); + assertThat(mappedList).isNotEmpty(); + assertThat(mappedList).containsExactly(5, 8); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/SyncUtilsTest.java b/src/test/java/com/commercetools/sync/commons/utils/SyncUtilsTest.java index ac56131ba0..1aa2832997 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/SyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/SyncUtilsTest.java @@ -1,13 +1,18 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategoryDraft; +import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; +import static com.commercetools.sync.commons.utils.SyncUtils.getReferenceWithKeyReplaced; +import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import io.sphere.sdk.categories.Category; import io.sphere.sdk.categories.CategoryDraft; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -16,174 +21,176 @@ import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategoryDraft; -import static com.commercetools.sync.commons.utils.SyncUtils.batchElements; -import static com.commercetools.sync.commons.utils.SyncUtils.getReferenceWithKeyReplaced; -import static com.commercetools.sync.commons.utils.SyncUtils.getResourceIdentifierWithKey; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class SyncUtilsTest { - @Test - void batchElements_WithValidSize_ShouldReturnCorrectBatches() { - final int numberOfCategoryDrafts = 160; - final int batchSize = 10; - final ArrayList categoryDrafts = new ArrayList<>(); - - for (int i = 0; i < numberOfCategoryDrafts; i++) { - categoryDrafts.add(getMockCategoryDraft(Locale.ENGLISH, "name", "key" + i, "parentKey", - "customTypeId", new HashMap<>())); - } - final List> batches = batchElements(categoryDrafts, 10); - assertThat(batches.size()).isEqualTo(numberOfCategoryDrafts / batchSize); - } + @Test + void batchElements_WithValidSize_ShouldReturnCorrectBatches() { + final int numberOfCategoryDrafts = 160; + final int batchSize = 10; + final ArrayList categoryDrafts = new ArrayList<>(); - @Test - void batchElements_WithUniformSeparation_ShouldReturnCorrectBatches() { - batchStringElementsAndAssertAfterBatching(100, 10); - batchStringElementsAndAssertAfterBatching(3, 1); + for (int i = 0; i < numberOfCategoryDrafts; i++) { + categoryDrafts.add( + getMockCategoryDraft( + Locale.ENGLISH, "name", "key" + i, "parentKey", "customTypeId", new HashMap<>())); } - - @Test - void batchElements_WithNonUniformSeparation_ShouldReturnCorrectBatches() { - batchStringElementsAndAssertAfterBatching(100, 9); - batchStringElementsAndAssertAfterBatching(3, 2); - } - - private void batchStringElementsAndAssertAfterBatching(final int numberOfElements, final int batchSize) { - final List elements = getPrefixedStrings(numberOfElements, "element"); - - final List> batches = batchElements(elements, batchSize); - - final int expectedNumberOfBatches = getExpectedNumberOfBatches(numberOfElements, batchSize); - assertThat(batches.size()).isEqualTo(expectedNumberOfBatches); - - // Assert correct size of elements after batching - final Integer numberOfElementsAfterBatching = batches.stream() - .map(List::size) - .reduce(0, - (element1, element2) -> element1 + element2); - - assertThat(numberOfElementsAfterBatching).isEqualTo(numberOfElements); - - - // Assert correct splitting of batches - final int remainder = numberOfElements % batchSize; - if (remainder == 0) { - final int numberOfDistinctBatchSizes = batches.stream().collect(Collectors.groupingBy(List::size)).size(); - assertThat(numberOfDistinctBatchSizes).isEqualTo(1); - } else { - final List lastBatch = batches.get(batches.size() - 1); - assertThat(lastBatch).hasSize(remainder); - } - - // Assert that all elements have been batched in correct order - final List flatBatches = batches.stream() - .flatMap(Collection::stream).collect(Collectors.toList()); - IntStream.range(0, flatBatches.size()) - .forEach(index -> assertThat(flatBatches.get(index)).isEqualTo(format("element#%s", index + 1))); - } - - @Nonnull - private List getPrefixedStrings(final int numberOfElements, @Nonnull final String prefix) { - return IntStream.range(1, numberOfElements + 1) - .mapToObj(i -> format("%s#%s", prefix, i)) - .collect(Collectors.toList()); - } - - private int getExpectedNumberOfBatches(int numberOfElements, int batchSize) { - return (int) (Math.ceil((double)numberOfElements / batchSize)); - } - - @Test - void batchElements_WithEmptyListAndAnySize_ShouldReturnNoBatches() { - final List> batches = batchElements(new ArrayList<>(), 100); - assertThat(batches.size()).isEqualTo(0); - } - - @Test - void batchElements_WithNegativeSize_ShouldReturnNoBatches() { - final int numberOfCategoryDrafts = 160; - final ArrayList categoryDrafts = new ArrayList<>(); - - for (int i = 0; i < numberOfCategoryDrafts; i++) { - categoryDrafts.add(getMockCategoryDraft(Locale.ENGLISH, "name", "key" + i, "parentKey", - "customTypeId", new HashMap<>())); - } - final List> batches = batchElements(categoryDrafts, -100); - assertThat(batches.size()).isEqualTo(0); - } - - @Test - void getReferenceWithKeyReplaced_WithNullReference_ShouldReturnNullReference() { - final Reference keyReplacedReference = getReferenceWithKeyReplaced(null, - () -> Reference.of(Category.referenceTypeId(), "id")); - assertThat(keyReplacedReference).isNull(); - } - - @Test - void getReferenceWithKeyReplaced_WithExpandedCategoryReference_ShouldReturnCategoryReferenceWithKey() { - final String categoryKey = "categoryKey"; - final Category mockCategory = mock(Category.class); - when(mockCategory.getKey()).thenReturn(categoryKey); - - final Reference categoryReference = Reference.ofResourceTypeIdAndObj(Category.referenceTypeId(), - mockCategory); - - final Reference keyReplacedReference = getReferenceWithKeyReplaced(categoryReference, - () -> Category.referenceOfId(categoryReference.getObj().getKey())); - - assertThat(keyReplacedReference).isNotNull(); - assertThat(keyReplacedReference.getId()).isEqualTo(categoryKey); - } - - @Test - void getResourceIdentifierWithKey_WithExpandedReference_ShouldReturnCategoryResourceIdentifierWithKey() { - final String categoryKey = "categoryKey"; - final Category mockCategory = mock(Category.class); - when(mockCategory.getKey()).thenReturn(categoryKey); - - final Reference categoryReference = Reference.ofResourceTypeIdAndObj(Category.referenceTypeId(), - mockCategory); - - final ResourceIdentifier resourceIdentifier = getResourceIdentifierWithKey(categoryReference); - - assertThat(resourceIdentifier).isNotNull(); - assertThat(resourceIdentifier.getKey()).isEqualTo(categoryKey); - } - - @Test - void getReferenceWithKey_WithNonExpandedCategoryReference_ShouldReturnReferenceWithoutKey() { - final String categoryUuid = UUID.randomUUID().toString(); - final Reference categoryReference = Reference.ofResourceTypeIdAndId(Category.referenceTypeId(), - categoryUuid); - - final Reference keyReplacedReference = getReferenceWithKeyReplaced(categoryReference, - () -> Category.referenceOfId(categoryReference.getObj().getKey())); - - assertThat(keyReplacedReference).isNotNull(); - assertThat(keyReplacedReference.getId()).isEqualTo(categoryUuid); - } - - @Test - void getResourceIdentifierWithKey_WithNonExpandedReference_ShouldReturnResIdentifierWithoutKey() { - final String categoryUuid = UUID.randomUUID().toString(); - final Reference categoryReference = Reference.ofResourceTypeIdAndId(Category.referenceTypeId(), - categoryUuid); - - final ResourceIdentifier resourceIdentifier = getResourceIdentifierWithKey(categoryReference); - - assertThat(resourceIdentifier).isNotNull(); - assertThat(resourceIdentifier.getId()).isEqualTo(categoryUuid); + final List> batches = batchElements(categoryDrafts, 10); + assertThat(batches.size()).isEqualTo(numberOfCategoryDrafts / batchSize); + } + + @Test + void batchElements_WithUniformSeparation_ShouldReturnCorrectBatches() { + batchStringElementsAndAssertAfterBatching(100, 10); + batchStringElementsAndAssertAfterBatching(3, 1); + } + + @Test + void batchElements_WithNonUniformSeparation_ShouldReturnCorrectBatches() { + batchStringElementsAndAssertAfterBatching(100, 9); + batchStringElementsAndAssertAfterBatching(3, 2); + } + + private void batchStringElementsAndAssertAfterBatching( + final int numberOfElements, final int batchSize) { + final List elements = getPrefixedStrings(numberOfElements, "element"); + + final List> batches = batchElements(elements, batchSize); + + final int expectedNumberOfBatches = getExpectedNumberOfBatches(numberOfElements, batchSize); + assertThat(batches.size()).isEqualTo(expectedNumberOfBatches); + + // Assert correct size of elements after batching + final Integer numberOfElementsAfterBatching = + batches.stream().map(List::size).reduce(0, (element1, element2) -> element1 + element2); + + assertThat(numberOfElementsAfterBatching).isEqualTo(numberOfElements); + + // Assert correct splitting of batches + final int remainder = numberOfElements % batchSize; + if (remainder == 0) { + final int numberOfDistinctBatchSizes = + batches.stream().collect(Collectors.groupingBy(List::size)).size(); + assertThat(numberOfDistinctBatchSizes).isEqualTo(1); + } else { + final List lastBatch = batches.get(batches.size() - 1); + assertThat(lastBatch).hasSize(remainder); } - @Test - void getResourceIdentifierWithKey_WithNullReference_ShouldReturnNull() { - final ResourceIdentifier resourceIdentifier = getResourceIdentifierWithKey(null); - assertThat(resourceIdentifier).isNull(); + // Assert that all elements have been batched in correct order + final List flatBatches = + batches.stream().flatMap(Collection::stream).collect(Collectors.toList()); + IntStream.range(0, flatBatches.size()) + .forEach( + index -> assertThat(flatBatches.get(index)).isEqualTo(format("element#%s", index + 1))); + } + + @Nonnull + private List getPrefixedStrings( + final int numberOfElements, @Nonnull final String prefix) { + return IntStream.range(1, numberOfElements + 1) + .mapToObj(i -> format("%s#%s", prefix, i)) + .collect(Collectors.toList()); + } + + private int getExpectedNumberOfBatches(int numberOfElements, int batchSize) { + return (int) (Math.ceil((double) numberOfElements / batchSize)); + } + + @Test + void batchElements_WithEmptyListAndAnySize_ShouldReturnNoBatches() { + final List> batches = batchElements(new ArrayList<>(), 100); + assertThat(batches.size()).isEqualTo(0); + } + + @Test + void batchElements_WithNegativeSize_ShouldReturnNoBatches() { + final int numberOfCategoryDrafts = 160; + final ArrayList categoryDrafts = new ArrayList<>(); + + for (int i = 0; i < numberOfCategoryDrafts; i++) { + categoryDrafts.add( + getMockCategoryDraft( + Locale.ENGLISH, "name", "key" + i, "parentKey", "customTypeId", new HashMap<>())); } + final List> batches = batchElements(categoryDrafts, -100); + assertThat(batches.size()).isEqualTo(0); + } + + @Test + void getReferenceWithKeyReplaced_WithNullReference_ShouldReturnNullReference() { + final Reference keyReplacedReference = + getReferenceWithKeyReplaced(null, () -> Reference.of(Category.referenceTypeId(), "id")); + assertThat(keyReplacedReference).isNull(); + } + + @Test + void + getReferenceWithKeyReplaced_WithExpandedCategoryReference_ShouldReturnCategoryReferenceWithKey() { + final String categoryKey = "categoryKey"; + final Category mockCategory = mock(Category.class); + when(mockCategory.getKey()).thenReturn(categoryKey); + + final Reference categoryReference = + Reference.ofResourceTypeIdAndObj(Category.referenceTypeId(), mockCategory); + + final Reference keyReplacedReference = + getReferenceWithKeyReplaced( + categoryReference, () -> Category.referenceOfId(categoryReference.getObj().getKey())); + + assertThat(keyReplacedReference).isNotNull(); + assertThat(keyReplacedReference.getId()).isEqualTo(categoryKey); + } + + @Test + void + getResourceIdentifierWithKey_WithExpandedReference_ShouldReturnCategoryResourceIdentifierWithKey() { + final String categoryKey = "categoryKey"; + final Category mockCategory = mock(Category.class); + when(mockCategory.getKey()).thenReturn(categoryKey); + + final Reference categoryReference = + Reference.ofResourceTypeIdAndObj(Category.referenceTypeId(), mockCategory); + + final ResourceIdentifier resourceIdentifier = + getResourceIdentifierWithKey(categoryReference); + + assertThat(resourceIdentifier).isNotNull(); + assertThat(resourceIdentifier.getKey()).isEqualTo(categoryKey); + } + + @Test + void getReferenceWithKey_WithNonExpandedCategoryReference_ShouldReturnReferenceWithoutKey() { + final String categoryUuid = UUID.randomUUID().toString(); + final Reference categoryReference = + Reference.ofResourceTypeIdAndId(Category.referenceTypeId(), categoryUuid); + + final Reference keyReplacedReference = + getReferenceWithKeyReplaced( + categoryReference, () -> Category.referenceOfId(categoryReference.getObj().getKey())); + + assertThat(keyReplacedReference).isNotNull(); + assertThat(keyReplacedReference.getId()).isEqualTo(categoryUuid); + } + + @Test + void getResourceIdentifierWithKey_WithNonExpandedReference_ShouldReturnResIdentifierWithoutKey() { + final String categoryUuid = UUID.randomUUID().toString(); + final Reference categoryReference = + Reference.ofResourceTypeIdAndId(Category.referenceTypeId(), categoryUuid); + + final ResourceIdentifier resourceIdentifier = + getResourceIdentifierWithKey(categoryReference); + + assertThat(resourceIdentifier).isNotNull(); + assertThat(resourceIdentifier.getId()).isEqualTo(categoryUuid); + } + + @Test + void getResourceIdentifierWithKey_WithNullReference_ShouldReturnNull() { + final ResourceIdentifier resourceIdentifier = getResourceIdentifierWithKey(null); + assertThat(resourceIdentifier).isNull(); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/TriFunctionTest.java b/src/test/java/com/commercetools/sync/commons/utils/TriFunctionTest.java index 37b4d9c384..ce5b80fdf4 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/TriFunctionTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/TriFunctionTest.java @@ -1,87 +1,87 @@ package com.commercetools.sync.commons.utils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.ChangeName; import io.sphere.sdk.products.commands.updateactions.SetSku; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.stream.Collectors; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class TriFunctionTest { - @Test - void apply_WithUpdateActionLocaleFilter_ShouldReturnCorrectResult() { - final TriFunction>, ProductDraft, Product, List>> - updateActionFilter = TriFunctionTest::filterEnglishNameChangesOnly; - - final Product oldProduct = readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - final LocalizedString oldName = oldProduct.getMasterData().getStaged().getName(); - - final ProductDraft newDraftWithNoEnglishNameChange = mock(ProductDraft.class); - final LocalizedString newNameWithSameEnglishLocale = oldName.plus(Locale.GERMAN, "bar"); - when(newDraftWithNoEnglishNameChange.getName()).thenReturn(newNameWithSameEnglishLocale); - - List> updateActions = Arrays.asList(ChangeName.of(newNameWithSameEnglishLocale), - SetSku.of(1, "sku")); - - assertThat(updateActionFilter.apply(updateActions, newDraftWithNoEnglishNameChange, oldProduct)) - .isEmpty(); - - final ProductDraft newDraftWithEnglishNameChange = mock(ProductDraft.class); - final LocalizedString newNameWithDiffEnglishLocale = LocalizedString.ofEnglish("foo"); - when(newDraftWithEnglishNameChange.getName()).thenReturn(newNameWithDiffEnglishLocale); - - updateActions = Arrays.asList(ChangeName.of(newNameWithDiffEnglishLocale), SetSku.of(1, "sku")); - - final List> filteredActions = updateActionFilter - .apply(updateActions, newDraftWithEnglishNameChange, oldProduct); - assertThat(filteredActions).isNotEmpty(); - assertThat(filteredActions).isEqualTo(updateActions); - } - - - private static List> filterEnglishNameChangesOnly( - @Nonnull final List> updateActions, - @Nonnull final ProductDraft newDraft, - @Nonnull final Product oldProduct) { - return updateActions.stream() - .filter(action -> { - final String englishOldName = - oldProduct.getMasterData().getStaged().getName().get(Locale.ENGLISH); - final String englishNewName = newDraft.getName().get(Locale.ENGLISH); - return !Objects.equals(englishOldName, englishNewName); - }) - .collect(Collectors.toList()); - } - - @Test - void apply_WithIntegerParams_ShouldReturnCorrectResult() { - final TriFunction - sumToString = (num1, num2, num3) -> { - num1 = num1 != null ? num1 : 0; - num2 = num2 != null ? num2 : 0; - num3 = num3 != null ? num3 : 0; - return String.format("The sum is: %d", num1 + num2 + num3); - }; - - final String threeOnes = sumToString.apply(1, 1, 1); - final String threeNulls = sumToString.apply(null, null, null); - - assertThat(threeOnes).isEqualTo(String.format("The sum is: %d", 3)); - assertThat(threeNulls).isEqualTo(String.format("The sum is: %d", 0)); - } + @Test + void apply_WithUpdateActionLocaleFilter_ShouldReturnCorrectResult() { + final TriFunction< + List>, ProductDraft, Product, List>> + updateActionFilter = TriFunctionTest::filterEnglishNameChangesOnly; + + final Product oldProduct = readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + final LocalizedString oldName = oldProduct.getMasterData().getStaged().getName(); + + final ProductDraft newDraftWithNoEnglishNameChange = mock(ProductDraft.class); + final LocalizedString newNameWithSameEnglishLocale = oldName.plus(Locale.GERMAN, "bar"); + when(newDraftWithNoEnglishNameChange.getName()).thenReturn(newNameWithSameEnglishLocale); + + List> updateActions = + Arrays.asList(ChangeName.of(newNameWithSameEnglishLocale), SetSku.of(1, "sku")); + + assertThat(updateActionFilter.apply(updateActions, newDraftWithNoEnglishNameChange, oldProduct)) + .isEmpty(); + + final ProductDraft newDraftWithEnglishNameChange = mock(ProductDraft.class); + final LocalizedString newNameWithDiffEnglishLocale = LocalizedString.ofEnglish("foo"); + when(newDraftWithEnglishNameChange.getName()).thenReturn(newNameWithDiffEnglishLocale); + + updateActions = Arrays.asList(ChangeName.of(newNameWithDiffEnglishLocale), SetSku.of(1, "sku")); + + final List> filteredActions = + updateActionFilter.apply(updateActions, newDraftWithEnglishNameChange, oldProduct); + assertThat(filteredActions).isNotEmpty(); + assertThat(filteredActions).isEqualTo(updateActions); + } + + private static List> filterEnglishNameChangesOnly( + @Nonnull final List> updateActions, + @Nonnull final ProductDraft newDraft, + @Nonnull final Product oldProduct) { + return updateActions.stream() + .filter( + action -> { + final String englishOldName = + oldProduct.getMasterData().getStaged().getName().get(Locale.ENGLISH); + final String englishNewName = newDraft.getName().get(Locale.ENGLISH); + return !Objects.equals(englishOldName, englishNewName); + }) + .collect(Collectors.toList()); + } + + @Test + void apply_WithIntegerParams_ShouldReturnCorrectResult() { + final TriFunction sumToString = + (num1, num2, num3) -> { + num1 = num1 != null ? num1 : 0; + num2 = num2 != null ? num2 : 0; + num3 = num3 != null ? num3 : 0; + return String.format("The sum is: %d", num1 + num2 + num3); + }; + + final String threeOnes = sumToString.apply(1, 1, 1); + final String threeNulls = sumToString.apply(null, null, null); + + assertThat(threeOnes).isEqualTo(String.format("The sum is: %d", 3)); + assertThat(threeNulls).isEqualTo(String.format("The sum is: %d", 0)); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/CollectionOfFuturesToFutureOfCollectionTest.java b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/CollectionOfFuturesToFutureOfCollectionTest.java index 1c7021fab4..9434280986 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/CollectionOfFuturesToFutureOfCollectionTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/CollectionOfFuturesToFutureOfCollectionTest.java @@ -1,14 +1,5 @@ package com.commercetools.sync.commons.utils.completablefutureutils; - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - import static com.commercetools.sync.commons.utils.CompletableFutureUtils.collectionOfFuturesToFutureOfCollection; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -20,237 +11,236 @@ import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; -class CollectionOfFuturesToFutureOfCollectionTest { +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.Test; - /** - * List to List : empty values. - * List to Set : empty values. - * Set to Set : empty values. - * Set to List : empty values. - **/ +class CollectionOfFuturesToFutureOfCollectionTest { - @Test - void empty_ListToList_ReturnsFutureOfEmptyList() { - final CompletableFuture> future = collectionOfFuturesToFutureOfCollection( + /** + * List to List : empty values. List to Set : empty values. Set to Set : empty values. Set to List + * : empty values. + */ + @Test + void empty_ListToList_ReturnsFutureOfEmptyList() { + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection( new ArrayList>(), toList()); - assertThat(future.join()).isEqualTo(emptyList()); - } + assertThat(future.join()).isEqualTo(emptyList()); + } - @Test - void empty_ListToSet_ReturnsFutureOfEmptySet() { - final CompletableFuture> future = collectionOfFuturesToFutureOfCollection( + @Test + void empty_ListToSet_ReturnsFutureOfEmptySet() { + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection( new ArrayList>(), toSet()); - assertThat(future.join()).isEqualTo(emptySet()); - } - - @Test - void empty_SetToSet_ReturnsFutureOfEmptySet() { - final CompletableFuture> future = collectionOfFuturesToFutureOfCollection(new HashSet<>(), toSet()); - assertThat(future.join()).isEqualTo(emptySet()); - } - - @Test - void empty_SetToList_ReturnsFutureOfEmptySet() { - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(new HashSet<>(), toList()); - assertThat(future.join()).isEqualTo(emptyList()); - } - - - /** - * List to List : single null value. - * List to Set : single null value. - * Set to Set : single null value. - * Set to List : single null value. - */ - - @Test - void singleNull_ListToList_ReturnsFutureOfEmptyList() { - final CompletableFuture nullFuture = null; - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(singletonList(nullFuture), toList()); - assertThat(future.join()).isEqualTo(emptyList()); - } - - @Test - void singleNull_ListToSet_ReturnsFutureOfEmptySet() { - final CompletableFuture nullFuture = null; - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(singletonList(nullFuture), toSet()); - assertThat(future.join()).isEqualTo(emptySet()); - } - - @Test - void singleNull_SetToSet_ReturnsFutureOfEmptySet() { - final CompletableFuture nullFuture = null; - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(singleton(nullFuture), toSet()); - assertThat(future.join()).isEqualTo(emptySet()); - } - - @Test - void singleNull_SetToList_ReturnsFutureOfEmptyList() { - final CompletableFuture nullFuture = null; - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(singleton(nullFuture), toList()); - assertThat(future.join()).isEqualTo(emptyList()); - } - - /** - * List to List : multiple null value. - * List to Set : multiple null value. - */ - - @Test - void multipleNull_ListToList_ReturnsFutureOfEmptyList() { - final CompletableFuture nullFuture = null; - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(asList(nullFuture, nullFuture), toList()); - assertThat(future.join()).isEqualTo(emptyList()); - } - - @Test - void multipleNull_ListToSet_ReturnsFutureOfEmptySet() { - final CompletableFuture nullFuture = null; - final CompletableFuture> future = collectionOfFuturesToFutureOfCollection( - asList(nullFuture, nullFuture), toSet()); - assertThat(future.join()).isEqualTo(emptySet()); - } - - /** - * * List to List : Single Value. - * * List to Set : Single Value. - * * Set to Set : Single Value. - * * Set to List : Single Value. - */ - - @Test - void single_ListToList_ReturnsFutureOfList() { - final String result = "value1"; - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(singletonList(completedFuture(result)), toList()); - assertThat(future.join()).containsExactly(result); - } - - @Test - void single_ListToSet_ReturnsFutureOfSet() { - final String result = "value1"; - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(singletonList(completedFuture(result)), toSet()); - assertThat(future.join()).containsExactly(result); - } - - @Test - void single_SetToSet_ReturnsFutureOfSet() { - final String result = "value1"; - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(singleton(completedFuture(result)), toSet()); - assertThat(future.join()).containsExactly(result); - } - - @Test - void single_SetToList_ReturnsFutureOfList() { - final String result = "value1"; - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(singleton(completedFuture(result)), toList()); - assertThat(future.join()).containsExactly(result); - } - - /** - * * List to List : Multiple Values : no duplicates. - * * List to Set : Multiple Values : no duplicates. - * * Set to Set : Multiple Values : no duplicates. - * * Set to List : Multiple Values : no duplicates. - */ - - @Test - void multiple_ListToListNoDuplicates_ReturnsFutureOfListOfCompletedValues() { - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(asList(completedFuture("foo"), completedFuture("bar")), toList()); - final List result = future.join(); - assertThat(result).containsExactly("foo", "bar"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_ListToSetNoDuplicates_ReturnsFutureOfSetOfCompletedValues() { - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(asList(completedFuture("foo"), completedFuture("bar")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactlyInAnyOrder("foo", "bar"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToSetNoDuplicates_ReturnsFutureOfSetOfCompletedValues() { - final Set> set = new HashSet<>(); - set.add(completedFuture("foo")); - set.add(completedFuture("bar")); - - final CompletableFuture> future = collectionOfFuturesToFutureOfCollection(set, toSet()); - final Set result = future.join(); - assertThat(result).containsExactlyInAnyOrder("foo", "bar"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToListMappingNoDuplicates_ReturnsFutureOfListOfCompletedValues() { - final Set> set = new HashSet<>(); - set.add(completedFuture("foo")); - set.add(completedFuture("bar")); - - final CompletableFuture> future = collectionOfFuturesToFutureOfCollection(set, toList()); - final List result = future.join(); - assertThat(result).containsExactlyInAnyOrder("foo", "bar"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - /** - * * List to List : Multiple Values : duplicates. - * * List to Set : Multiple Values : duplicates. - * * Set to Set : Multiple Values : duplicates. - * * Set to List : Multiple Values : duplicates. - */ - - @Test - void multiple_ListToListDuplicates_ReturnsFutureOfListOfCompletedValuesDuplicates() { - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(asList(completedFuture("foo"), completedFuture("foo")), toList()); - final List result = future.join(); - assertThat(result).containsExactly("foo", "foo"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_ListToSetDuplicates_ReturnsFutureOfSetOfCompletedValuesNoDuplicates() { - final CompletableFuture> future = - collectionOfFuturesToFutureOfCollection(asList(completedFuture("foo"), completedFuture("foo")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly("foo"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToSetDuplicates_ReturnsFutureOfSetOfCompletedValuesNoDuplicates() { - final Set> set = new HashSet<>(); - set.add(completedFuture("foo")); - set.add(completedFuture("foo")); - - final CompletableFuture> future = collectionOfFuturesToFutureOfCollection(set, toSet()); - final Set result = future.join(); - assertThat(result).containsExactly("foo"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToListDuplicates_ReturnsFutureOfListOfCompletedValuesDuplicates() { - final Set> set = new HashSet<>(); - set.add(completedFuture("foo")); - set.add(completedFuture("foo")); - - final CompletableFuture> future = collectionOfFuturesToFutureOfCollection(set, toList()); - final List result = future.join(); - assertThat(result).containsExactly("foo", "foo"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } + assertThat(future.join()).isEqualTo(emptySet()); + } + + @Test + void empty_SetToSet_ReturnsFutureOfEmptySet() { + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(new HashSet<>(), toSet()); + assertThat(future.join()).isEqualTo(emptySet()); + } + + @Test + void empty_SetToList_ReturnsFutureOfEmptySet() { + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(new HashSet<>(), toList()); + assertThat(future.join()).isEqualTo(emptyList()); + } + + /** + * List to List : single null value. List to Set : single null value. Set to Set : single null + * value. Set to List : single null value. + */ + @Test + void singleNull_ListToList_ReturnsFutureOfEmptyList() { + final CompletableFuture nullFuture = null; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(singletonList(nullFuture), toList()); + assertThat(future.join()).isEqualTo(emptyList()); + } + + @Test + void singleNull_ListToSet_ReturnsFutureOfEmptySet() { + final CompletableFuture nullFuture = null; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(singletonList(nullFuture), toSet()); + assertThat(future.join()).isEqualTo(emptySet()); + } + + @Test + void singleNull_SetToSet_ReturnsFutureOfEmptySet() { + final CompletableFuture nullFuture = null; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(singleton(nullFuture), toSet()); + assertThat(future.join()).isEqualTo(emptySet()); + } + + @Test + void singleNull_SetToList_ReturnsFutureOfEmptyList() { + final CompletableFuture nullFuture = null; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(singleton(nullFuture), toList()); + assertThat(future.join()).isEqualTo(emptyList()); + } + + /** List to List : multiple null value. List to Set : multiple null value. */ + @Test + void multipleNull_ListToList_ReturnsFutureOfEmptyList() { + final CompletableFuture nullFuture = null; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(asList(nullFuture, nullFuture), toList()); + assertThat(future.join()).isEqualTo(emptyList()); + } + + @Test + void multipleNull_ListToSet_ReturnsFutureOfEmptySet() { + final CompletableFuture nullFuture = null; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(asList(nullFuture, nullFuture), toSet()); + assertThat(future.join()).isEqualTo(emptySet()); + } + + /** + * * List to List : Single Value. * List to Set : Single Value. * Set to Set : Single Value. * Set + * to List : Single Value. + */ + @Test + void single_ListToList_ReturnsFutureOfList() { + final String result = "value1"; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(singletonList(completedFuture(result)), toList()); + assertThat(future.join()).containsExactly(result); + } + + @Test + void single_ListToSet_ReturnsFutureOfSet() { + final String result = "value1"; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(singletonList(completedFuture(result)), toSet()); + assertThat(future.join()).containsExactly(result); + } + + @Test + void single_SetToSet_ReturnsFutureOfSet() { + final String result = "value1"; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(singleton(completedFuture(result)), toSet()); + assertThat(future.join()).containsExactly(result); + } + + @Test + void single_SetToList_ReturnsFutureOfList() { + final String result = "value1"; + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(singleton(completedFuture(result)), toList()); + assertThat(future.join()).containsExactly(result); + } + + /** + * * List to List : Multiple Values : no duplicates. * List to Set : Multiple Values : no + * duplicates. * Set to Set : Multiple Values : no duplicates. * Set to List : Multiple Values : + * no duplicates. + */ + @Test + void multiple_ListToListNoDuplicates_ReturnsFutureOfListOfCompletedValues() { + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection( + asList(completedFuture("foo"), completedFuture("bar")), toList()); + final List result = future.join(); + assertThat(result).containsExactly("foo", "bar"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void multiple_ListToSetNoDuplicates_ReturnsFutureOfSetOfCompletedValues() { + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection( + asList(completedFuture("foo"), completedFuture("bar")), toSet()); + final Set result = future.join(); + assertThat(result).containsExactlyInAnyOrder("foo", "bar"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void multiple_SetToSetNoDuplicates_ReturnsFutureOfSetOfCompletedValues() { + final Set> set = new HashSet<>(); + set.add(completedFuture("foo")); + set.add(completedFuture("bar")); + + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(set, toSet()); + final Set result = future.join(); + assertThat(result).containsExactlyInAnyOrder("foo", "bar"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void multiple_SetToListMappingNoDuplicates_ReturnsFutureOfListOfCompletedValues() { + final Set> set = new HashSet<>(); + set.add(completedFuture("foo")); + set.add(completedFuture("bar")); + + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(set, toList()); + final List result = future.join(); + assertThat(result).containsExactlyInAnyOrder("foo", "bar"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + /** + * * List to List : Multiple Values : duplicates. * List to Set : Multiple Values : duplicates. * + * Set to Set : Multiple Values : duplicates. * Set to List : Multiple Values : duplicates. + */ + @Test + void multiple_ListToListDuplicates_ReturnsFutureOfListOfCompletedValuesDuplicates() { + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection( + asList(completedFuture("foo"), completedFuture("foo")), toList()); + final List result = future.join(); + assertThat(result).containsExactly("foo", "foo"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void multiple_ListToSetDuplicates_ReturnsFutureOfSetOfCompletedValuesNoDuplicates() { + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection( + asList(completedFuture("foo"), completedFuture("foo")), toSet()); + final Set result = future.join(); + assertThat(result).containsExactly("foo"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void multiple_SetToSetDuplicates_ReturnsFutureOfSetOfCompletedValuesNoDuplicates() { + final Set> set = new HashSet<>(); + set.add(completedFuture("foo")); + set.add(completedFuture("foo")); + + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(set, toSet()); + final Set result = future.join(); + assertThat(result).containsExactly("foo"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void multiple_SetToListDuplicates_ReturnsFutureOfListOfCompletedValuesDuplicates() { + final Set> set = new HashSet<>(); + set.add(completedFuture("foo")); + set.add(completedFuture("foo")); + + final CompletableFuture> future = + collectionOfFuturesToFutureOfCollection(set, toList()); + final List result = future.join(); + assertThat(result).containsExactly("foo", "foo"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapStreamValuesToFutureOfCompletedValuesTest.java b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapStreamValuesToFutureOfCompletedValuesTest.java index 294c9ef2a8..7f6e17e9e2 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapStreamValuesToFutureOfCompletedValuesTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapStreamValuesToFutureOfCompletedValuesTest.java @@ -1,15 +1,5 @@ package com.commercetools.sync.commons.utils.completablefutureutils; - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; @@ -18,195 +8,214 @@ import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + class MapStreamValuesToFutureOfCompletedValuesTest { - /** - * Stream to List : empty values. - * Stream to Set : empty values. - **/ - - @Test - void empty_StreamToList_ReturnsFutureOfEmptyList() { - final CompletableFuture> futureList = mapValuesToFutureOfCompletedValues( + /** Stream to List : empty values. Stream to Set : empty values. */ + @Test + void empty_StreamToList_ReturnsFutureOfEmptyList() { + final CompletableFuture> futureList = + mapValuesToFutureOfCompletedValues( Stream.empty(), CompletableFuture::completedFuture, toList()); - assertThat(futureList.join()).isEqualTo(emptyList()); - } - - @Test - void empty_StreamToSet_ReturnsFutureOfEmptySet() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(Stream.empty(), - CompletableFuture::completedFuture, toSet()); - assertThat(future.join()).isEqualTo(emptySet()); - } - - - /** - * Stream to List : single null value. - * Stream to Set : single null value. - */ - - @Test - void singleNull_StreamToList_ReturnsFutureOfEmptyList() { - final String nullString = null; - final CompletableFuture> futureList = mapValuesToFutureOfCompletedValues( + assertThat(futureList.join()).isEqualTo(emptyList()); + } + + @Test + void empty_StreamToSet_ReturnsFutureOfEmptySet() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + Stream.empty(), CompletableFuture::completedFuture, toSet()); + assertThat(future.join()).isEqualTo(emptySet()); + } + + /** Stream to List : single null value. Stream to Set : single null value. */ + @Test + void singleNull_StreamToList_ReturnsFutureOfEmptyList() { + final String nullString = null; + final CompletableFuture> futureList = + mapValuesToFutureOfCompletedValues( Stream.of(nullString), CompletableFuture::completedFuture, toList()); - assertThat(futureList.join()).isEqualTo(emptyList()); - } - - @Test - void singleNull_StreamToSet_ReturnsFutureOfEmptySet() { - final String nullString = null; - final CompletableFuture> futureSet = mapValuesToFutureOfCompletedValues( + assertThat(futureList.join()).isEqualTo(emptyList()); + } + + @Test + void singleNull_StreamToSet_ReturnsFutureOfEmptySet() { + final String nullString = null; + final CompletableFuture> futureSet = + mapValuesToFutureOfCompletedValues( Stream.of(nullString), CompletableFuture::completedFuture, toSet()); - assertThat(futureSet.join()).isEqualTo(emptySet()); - } - - /** - * Stream to List : multiple null value. - * Stream to Set : multiple null value. - */ - - @Test - void multipleNull_StreamToList_ReturnsFutureOfEmptyList() { - final String nullString = null; - final CompletableFuture> futureList = mapValuesToFutureOfCompletedValues( + assertThat(futureSet.join()).isEqualTo(emptySet()); + } + + /** Stream to List : multiple null value. Stream to Set : multiple null value. */ + @Test + void multipleNull_StreamToList_ReturnsFutureOfEmptyList() { + final String nullString = null; + final CompletableFuture> futureList = + mapValuesToFutureOfCompletedValues( Stream.of(nullString, nullString), CompletableFuture::completedFuture, toList()); - assertThat(futureList.join()).isEqualTo(emptyList()); - } - - @Test - void multipleNull_StreamToSet_ReturnsFutureOfEmptySet() { - final String nullString = null; - final CompletableFuture> futureSet = mapValuesToFutureOfCompletedValues( + assertThat(futureList.join()).isEqualTo(emptyList()); + } + + @Test + void multipleNull_StreamToSet_ReturnsFutureOfEmptySet() { + final String nullString = null; + final CompletableFuture> futureSet = + mapValuesToFutureOfCompletedValues( Stream.of(nullString, nullString), CompletableFuture::completedFuture, toSet()); - assertThat(futureSet.join()).isEqualTo(emptySet()); - } - - /** - * * Stream to List : Same Type Mapping : Single Value. - * * Stream to Set : Same Type Mapping : Single Value. - * * Stream to List : Diff Type Mapping : Single Value. - * * Stream to Set : Diff Type Mapping : Single Value. - */ - - @Test - void single_StreamToListWithSameTypeMapping_ReturnsFutureOfListOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + assertThat(futureSet.join()).isEqualTo(emptySet()); + } + + /** + * * Stream to List : Same Type Mapping : Single Value. * Stream to Set : Same Type Mapping : + * Single Value. * Stream to List : Diff Type Mapping : Single Value. * Stream to Set : Diff Type + * Mapping : Single Value. + */ + @Test + void single_StreamToListWithSameTypeMapping_ReturnsFutureOfListOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( Stream.of("foo"), element -> completedFuture(element.concat("POSTFIX")), toList()); - final List result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void single_StreamToSetWithSameTypeMapping_ReturnsFutureOfSetOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void single_StreamToSetWithSameTypeMapping_ReturnsFutureOfSetOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( Stream.of("foo"), element -> completedFuture(element.concat("POSTFIX")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void single_StreamToListWithDiffTypeMapping_ReturnsFutureOfListOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Set result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void single_StreamToListWithDiffTypeMapping_ReturnsFutureOfListOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( Stream.of("foo"), element -> completedFuture(element.length()), toList()); - final List result = future.join(); - assertThat(result).containsExactly(3); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void single_StreamToSetWithDiffTypeMapping_ReturnsFutureOfSetOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly(3); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void single_StreamToSetWithDiffTypeMapping_ReturnsFutureOfSetOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( Stream.of("foo"), element -> completedFuture(element.length()), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly(3); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - /** - * * Stream to List : Same Type Mapping : Multiple Values : no duplicates. - * * Stream to Set : Same Type Mapping : Multiple Values : no duplicates. - * * Stream to List : Diff Type Mapping : Multiple Values : no duplicates. - * * Stream to Set : Diff Type Mapping : Multiple Values : no duplicates. - */ - - @Test - void multiple_StreamToListWithSameTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( - Stream.of("foo", "bar"), element -> completedFuture(element.concat("POSTFIX")), toList()); - final List result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX", "barPOSTFIX"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_StreamToSetWithSameTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(Stream.of("foo", "bar"), - element -> completedFuture(element.concat("POSTFIX")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_StreamToListWithDiffTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Set result = future.join(); + assertThat(result).containsExactly(3); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + /** + * * Stream to List : Same Type Mapping : Multiple Values : no duplicates. * Stream to Set : Same + * Type Mapping : Multiple Values : no duplicates. * Stream to List : Diff Type Mapping : Multiple + * Values : no duplicates. * Stream to Set : Diff Type Mapping : Multiple Values : no duplicates. + */ + @Test + void multiple_StreamToListWithSameTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + Stream.of("foo", "bar"), + element -> completedFuture(element.concat("POSTFIX")), + toList()); + final List result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX", "barPOSTFIX"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void multiple_StreamToSetWithSameTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + Stream.of("foo", "bar"), + element -> completedFuture(element.concat("POSTFIX")), + toSet()); + final Set result = future.join(); + assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void multiple_StreamToListWithDiffTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( Stream.of("john", "smith"), element -> completedFuture(element.length()), toList()); - final List result = future.join(); - assertThat(result).containsExactly(4, 5); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_StreamToSetWithDiffTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(Stream.of("john", "smith"), - element -> completedFuture(element.length()), toSet()); - final Set result = future.join(); - assertThat(result).containsExactlyInAnyOrder(4, 5); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - /** - * * Stream to List : Same Type Mapping : Multiple Values : duplicates. - * * Stream to Set : Same Type Mapping : Multiple Values : duplicates. - * * Stream to List : Diff Type Mapping : Multiple Values : duplicates. - * * Stream to Set : Diff Type Mapping : Multiple Values : duplicates. - */ - - @Test - void multiple_StreamToListWithSameTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( - Stream.of("foo", "foo"), element -> completedFuture(element.concat("POSTFIX")), toList()); - final List result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX", "fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_StreamToSetWithSameTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(Stream.of("foo", "foo"), - element -> completedFuture(element.concat("POSTFIX")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_StreamToListWithDiffTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly(4, 5); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void multiple_StreamToSetWithDiffTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + Stream.of("john", "smith"), element -> completedFuture(element.length()), toSet()); + final Set result = future.join(); + assertThat(result).containsExactlyInAnyOrder(4, 5); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + /** + * * Stream to List : Same Type Mapping : Multiple Values : duplicates. * Stream to Set : Same + * Type Mapping : Multiple Values : duplicates. * Stream to List : Diff Type Mapping : Multiple + * Values : duplicates. * Stream to Set : Diff Type Mapping : Multiple Values : duplicates. + */ + @Test + void + multiple_StreamToListWithSameTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + Stream.of("foo", "foo"), + element -> completedFuture(element.concat("POSTFIX")), + toList()); + final List result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX", "fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void + multiple_StreamToSetWithSameTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + Stream.of("foo", "foo"), + element -> completedFuture(element.concat("POSTFIX")), + toSet()); + final Set result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void + multiple_StreamToListWithDiffTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( Stream.of("john", "john"), element -> completedFuture(element.length()), toList()); - final List result = future.join(); - assertThat(result).containsExactly(4, 4); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_StreamToSetWithDiffTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(Stream.of("john", "john"), - element -> completedFuture(element.length()), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly(4); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } + final List result = future.join(); + assertThat(result).containsExactly(4, 4); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void + multiple_StreamToSetWithDiffTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + Stream.of("john", "john"), element -> completedFuture(element.length()), toSet()); + final Set result = future.join(); + assertThat(result).containsExactly(4); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFutureOfCompletedValuesStreamTest.java b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFutureOfCompletedValuesStreamTest.java index 9e521bd721..2d6748be92 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFutureOfCompletedValuesStreamTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFutureOfCompletedValuesStreamTest.java @@ -1,14 +1,5 @@ package com.commercetools.sync.commons.utils.completablefutureutils; - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -18,191 +9,197 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + class MapValuesToFutureOfCompletedValuesStreamTest { - /** - * List to stream : empty values. - * Set to stream : empty values. - **/ - - @Test - void empty_ListToStream_ReturnsFutureOfEmptyStream() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + /** List to stream : empty values. Set to stream : empty values. */ + @Test + void empty_ListToStream_ReturnsFutureOfEmptyStream() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( new ArrayList(), CompletableFuture::completedFuture); - assertThat(future.join().collect(toList())).isEqualTo(emptyList()); - } + assertThat(future.join().collect(toList())).isEqualTo(emptyList()); + } - @Test - void empty_SetToStream_ReturnsFutureOfEmptyStream() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + @Test + void empty_SetToStream_ReturnsFutureOfEmptyStream() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( new HashSet(), CompletableFuture::completedFuture); - assertThat(future.join().collect(toList())).isEqualTo(emptyList()); - } - - - /** - * List to Stream : single null value. - * Set to Stream : single null value. - */ - - @Test - void singleNull_ListToStream_ReturnsFutureOfEmptyStream() { - final String nullString = null; - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + assertThat(future.join().collect(toList())).isEqualTo(emptyList()); + } + + /** List to Stream : single null value. Set to Stream : single null value. */ + @Test + void singleNull_ListToStream_ReturnsFutureOfEmptyStream() { + final String nullString = null; + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singletonList(nullString), CompletableFuture::completedFuture); - assertThat(future.join().collect(toList())).isEqualTo(emptyList()); - } - - @Test - void singleNull_SetToStream_ReturnsFutureOfEmptyStream() { - final String nullString = null; - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + assertThat(future.join().collect(toList())).isEqualTo(emptyList()); + } + + @Test + void singleNull_SetToStream_ReturnsFutureOfEmptyStream() { + final String nullString = null; + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singleton(nullString), CompletableFuture::completedFuture); - assertThat(future.join().collect(toList())).isEqualTo(emptyList()); - } - - /** - * List to Stream : multiple null value. - */ - - @Test - void multipleNull_ListToStream_ReturnsFutureOfEmptyStream() { - final String nullString = null; - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + assertThat(future.join().collect(toList())).isEqualTo(emptyList()); + } + + /** List to Stream : multiple null value. */ + @Test + void multipleNull_ListToStream_ReturnsFutureOfEmptyStream() { + final String nullString = null; + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( asList(nullString, nullString), CompletableFuture::completedFuture); - assertThat(future.join().collect(toList())).isEqualTo(emptyList()); - } - - /** - * * List to Stream : Same Type Mapping : Single Value. - * * Set to Stream : Same Type Mapping : Single Value. - * * List to Stream : Diff Type Mapping : Single Value. - * * Set to Stream : Diff Type Mapping : Single Value. - */ - - @Test - void single_ListWithSameTypeMapping_ReturnsFutureOfStreamOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + assertThat(future.join().collect(toList())).isEqualTo(emptyList()); + } + + /** + * * List to Stream : Same Type Mapping : Single Value. * Set to Stream : Same Type Mapping : + * Single Value. * List to Stream : Diff Type Mapping : Single Value. * Set to Stream : Diff Type + * Mapping : Single Value. + */ + @Test + void single_ListWithSameTypeMapping_ReturnsFutureOfStreamOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singletonList("foo"), element -> completedFuture(element.concat("POSTFIX"))); - final Stream result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - } - - @Test - void single_SetWithSameTypeMapping_ReturnsFutureOfStreamOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Stream result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + } + + @Test + void single_SetWithSameTypeMapping_ReturnsFutureOfStreamOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singleton("foo"), element -> completedFuture(element.concat("POSTFIX"))); - final Stream result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - } - - @Test - void single_ListWithDiffTypeMapping_ReturnsFutureOfStreamOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Stream result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + } + + @Test + void single_ListWithDiffTypeMapping_ReturnsFutureOfStreamOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singletonList("foo"), element -> completedFuture(element.length())); - final Stream result = future.join(); - assertThat(result).containsExactly(3); - } - - @Test - void single_SetWithDiffTypeMapping_ReturnsFutureOfStreamOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Stream result = future.join(); + assertThat(result).containsExactly(3); + } + + @Test + void single_SetWithDiffTypeMapping_ReturnsFutureOfStreamOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singleton("foo"), element -> completedFuture(element.length())); - final Stream result = future.join(); - assertThat(result).containsExactly(3); - } - - - /** - * * List to Stream : Same Type Mapping : Multiple Values : no duplicates. - * * Set to Stream : Same Type Mapping : Multiple Values : no duplicates. - * * List to Stream : Diff Type Mapping : Multiple Values : no duplicates. - * * Set to Stream : Diff Type Mapping : Multiple Values : no duplicates. - */ - - @Test - void multiple_ListWithSameTypeMappingNoDuplicates_ReturnsFutureOfStreamOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Stream result = future.join(); + assertThat(result).containsExactly(3); + } + + /** + * * List to Stream : Same Type Mapping : Multiple Values : no duplicates. * Set to Stream : Same + * Type Mapping : Multiple Values : no duplicates. * List to Stream : Diff Type Mapping : Multiple + * Values : no duplicates. * Set to Stream : Diff Type Mapping : Multiple Values : no duplicates. + */ + @Test + void multiple_ListWithSameTypeMappingNoDuplicates_ReturnsFutureOfStreamOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( asList("foo", "bar"), element -> completedFuture(element.concat("POSTFIX"))); - final Stream result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX", "barPOSTFIX"); - } - - @Test - void multiple_SetWithSameTypeMappingNoDuplicates_ReturnsFutureOfStreamOfMappedValues() { - final Set set = new HashSet<>(); - set.add("foo"); - set.add("bar"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture(element.concat("POSTFIX"))); - final Stream result = future.join(); - assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); - } - - @Test - void multiple_ListWithDiffTypeMappingNoDuplicates_ReturnsFutureOfStreamOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Stream result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX", "barPOSTFIX"); + } + + @Test + void multiple_SetWithSameTypeMappingNoDuplicates_ReturnsFutureOfStreamOfMappedValues() { + final Set set = new HashSet<>(); + set.add("foo"); + set.add("bar"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + set, element -> completedFuture(element.concat("POSTFIX"))); + final Stream result = future.join(); + assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); + } + + @Test + void multiple_ListWithDiffTypeMappingNoDuplicates_ReturnsFutureOfStreamOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( asList("john", "smith"), element -> completedFuture(element.length())); - final Stream result = future.join(); - assertThat(result).containsExactly(4, 5); - } - - @Test - void multiple_SetWithDiffTypeMappingNoDuplicates_ReturnsFutureOfStreamOfMappedValues() { - final Set set = new HashSet<>(); - set.add("john"); - set.add("smith"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture(element.length())); - final Stream result = future.join(); - assertThat(result).containsExactlyInAnyOrder(4, 5); - } - - /** - * * List to Stream : Same Type Mapping : Multiple Values : duplicates. - * * Set to Stream : Same Type Mapping : Multiple Values : duplicates. - * * List to Stream : Diff Type Mapping : Multiple Values : duplicates. - * * Set to Stream : Diff Type Mapping : Multiple Values : duplicates. - */ - - @Test - void multiple_ListWithSameTypeMappingDuplicates_ReturnsFutureOfStreamOfMappedValuesWithDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Stream result = future.join(); + assertThat(result).containsExactly(4, 5); + } + + @Test + void multiple_SetWithDiffTypeMappingNoDuplicates_ReturnsFutureOfStreamOfMappedValues() { + final Set set = new HashSet<>(); + set.add("john"); + set.add("smith"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues(set, element -> completedFuture(element.length())); + final Stream result = future.join(); + assertThat(result).containsExactlyInAnyOrder(4, 5); + } + + /** + * * List to Stream : Same Type Mapping : Multiple Values : duplicates. * Set to Stream : Same + * Type Mapping : Multiple Values : duplicates. * List to Stream : Diff Type Mapping : Multiple + * Values : duplicates. * Set to Stream : Diff Type Mapping : Multiple Values : duplicates. + */ + @Test + void + multiple_ListWithSameTypeMappingDuplicates_ReturnsFutureOfStreamOfMappedValuesWithDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( asList("foo", "foo"), element -> completedFuture(element.concat("POSTFIX"))); - final Stream result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX", "fooPOSTFIX"); - } - - @Test - void multiple_SetWithSameTypeMappingDuplicates_ReturnsFutureOfStreamOfMappedValuesWithDuplicates() { - final Set set = new HashSet<>(); - set.add("foo"); - set.add("bar"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture("constantResult")); - final Stream result = future.join(); - assertThat(result).containsExactly("constantResult", "constantResult"); - } - - @Test - void multiple_ListWithDiffTypeMappingDuplicates_ReturnsFutureOfStreamOfMappedValuesWithDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Stream result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX", "fooPOSTFIX"); + } + + @Test + void + multiple_SetWithSameTypeMappingDuplicates_ReturnsFutureOfStreamOfMappedValuesWithDuplicates() { + final Set set = new HashSet<>(); + set.add("foo"); + set.add("bar"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues(set, element -> completedFuture("constantResult")); + final Stream result = future.join(); + assertThat(result).containsExactly("constantResult", "constantResult"); + } + + @Test + void + multiple_ListWithDiffTypeMappingDuplicates_ReturnsFutureOfStreamOfMappedValuesWithDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( asList("john", "john"), element -> completedFuture(element.length())); - final Stream result = future.join(); - assertThat(result).containsExactly(4, 4); - } - - @Test - void multiple_SetWithDiffTypeMappingDuplicates_ReturnsFutureOfStreamOfMappedValuesWithDuplicates() { - final Set set = new HashSet<>(); - set.add("foo"); - set.add("bar"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture(element.length())); - final Stream result = future.join(); - assertThat(result).containsExactly(3, 3); - } + final Stream result = future.join(); + assertThat(result).containsExactly(4, 4); + } + + @Test + void + multiple_SetWithDiffTypeMappingDuplicates_ReturnsFutureOfStreamOfMappedValuesWithDuplicates() { + final Set set = new HashSet<>(); + set.add("foo"); + set.add("bar"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues(set, element -> completedFuture(element.length())); + final Stream result = future.join(); + assertThat(result).containsExactly(3, 3); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFutureOfCompletedValuesTest.java b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFutureOfCompletedValuesTest.java index d98c2564b9..7ff89a4478 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFutureOfCompletedValuesTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFutureOfCompletedValuesTest.java @@ -1,14 +1,5 @@ package com.commercetools.sync.commons.utils.completablefutureutils; - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -20,384 +11,413 @@ import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.Test; + class MapValuesToFutureOfCompletedValuesTest { - /** - * List to List : empty values. - * List to Set : empty values. - * Set to Set : empty values. - * Set to List : empty values. - **/ - - @Test - void empty_ListToList_ReturnsFutureOfEmptyList() { - final CompletableFuture> futureList = mapValuesToFutureOfCompletedValues( + /** + * List to List : empty values. List to Set : empty values. Set to Set : empty values. Set to List + * : empty values. + */ + @Test + void empty_ListToList_ReturnsFutureOfEmptyList() { + final CompletableFuture> futureList = + mapValuesToFutureOfCompletedValues( new ArrayList(), CompletableFuture::completedFuture, toList()); - assertThat(futureList.join()).isEqualTo(emptyList()); - } + assertThat(futureList.join()).isEqualTo(emptyList()); + } - @Test - void empty_ListToSet_ReturnsFutureOfEmptySet() { - final CompletableFuture> futureSet = mapValuesToFutureOfCompletedValues( + @Test + void empty_ListToSet_ReturnsFutureOfEmptySet() { + final CompletableFuture> futureSet = + mapValuesToFutureOfCompletedValues( new ArrayList(), CompletableFuture::completedFuture, toSet()); - assertThat(futureSet.join()).isEqualTo(emptySet()); - } + assertThat(futureSet.join()).isEqualTo(emptySet()); + } - @Test - void empty_SetToSet_ReturnsFutureOfEmptyList() { - final CompletableFuture> futureList = mapValuesToFutureOfCompletedValues( + @Test + void empty_SetToSet_ReturnsFutureOfEmptyList() { + final CompletableFuture> futureList = + mapValuesToFutureOfCompletedValues( new HashSet(), CompletableFuture::completedFuture, toList()); - assertThat(futureList.join()).isEqualTo(emptyList()); - } + assertThat(futureList.join()).isEqualTo(emptyList()); + } - @Test - void empty_SetToList_ReturnsFutureOfEmptySet() { - final CompletableFuture> futureSet = mapValuesToFutureOfCompletedValues( + @Test + void empty_SetToList_ReturnsFutureOfEmptySet() { + final CompletableFuture> futureSet = + mapValuesToFutureOfCompletedValues( new HashSet(), CompletableFuture::completedFuture, toSet()); - assertThat(futureSet.join()).isEqualTo(emptySet()); - } - - - /** - * List to List : single null value. - * List to Set : single null value. - * Set to Set : single null value. - * Set to List : single null value. - */ - - @Test - void singleNull_ListToList_ReturnsFutureOfEmptyList() { - final String nullString = null; - final CompletableFuture> futureList = mapValuesToFutureOfCompletedValues( + assertThat(futureSet.join()).isEqualTo(emptySet()); + } + + /** + * List to List : single null value. List to Set : single null value. Set to Set : single null + * value. Set to List : single null value. + */ + @Test + void singleNull_ListToList_ReturnsFutureOfEmptyList() { + final String nullString = null; + final CompletableFuture> futureList = + mapValuesToFutureOfCompletedValues( singletonList(nullString), CompletableFuture::completedFuture, toList()); - assertThat(futureList.join()).isEqualTo(emptyList()); - } - - @Test - void singleNull_ListToSet_ReturnsFutureOfEmptySet() { - final String nullString = null; - final CompletableFuture> futureSet = mapValuesToFutureOfCompletedValues( + assertThat(futureList.join()).isEqualTo(emptyList()); + } + + @Test + void singleNull_ListToSet_ReturnsFutureOfEmptySet() { + final String nullString = null; + final CompletableFuture> futureSet = + mapValuesToFutureOfCompletedValues( singletonList(nullString), CompletableFuture::completedFuture, toSet()); - assertThat(futureSet.join()).isEqualTo(emptySet()); - } - - @Test - void singleNull_SetToSet_ReturnsFutureOfEmptyList() { - final String nullString = null; - final CompletableFuture> futureList = mapValuesToFutureOfCompletedValues( + assertThat(futureSet.join()).isEqualTo(emptySet()); + } + + @Test + void singleNull_SetToSet_ReturnsFutureOfEmptyList() { + final String nullString = null; + final CompletableFuture> futureList = + mapValuesToFutureOfCompletedValues( singleton(nullString), CompletableFuture::completedFuture, toList()); - assertThat(futureList.join()).isEqualTo(emptyList()); - } - - @Test - void singleNull_SetToList_ReturnsFutureOfEmptySet() { - final String nullString = null; - final CompletableFuture> futureSet = mapValuesToFutureOfCompletedValues( + assertThat(futureList.join()).isEqualTo(emptyList()); + } + + @Test + void singleNull_SetToList_ReturnsFutureOfEmptySet() { + final String nullString = null; + final CompletableFuture> futureSet = + mapValuesToFutureOfCompletedValues( singleton(nullString), CompletableFuture::completedFuture, toSet()); - assertThat(futureSet.join()).isEqualTo(emptySet()); - } - - /** - * List to List : multiple null value. - * List to Set : multiple null value. - */ - - @Test - void multipleNull_ListToList_ReturnsFutureOfEmptyList() { - final String nullString = null; - final CompletableFuture> futureList = mapValuesToFutureOfCompletedValues( + assertThat(futureSet.join()).isEqualTo(emptySet()); + } + + /** List to List : multiple null value. List to Set : multiple null value. */ + @Test + void multipleNull_ListToList_ReturnsFutureOfEmptyList() { + final String nullString = null; + final CompletableFuture> futureList = + mapValuesToFutureOfCompletedValues( asList(nullString, nullString), CompletableFuture::completedFuture, toList()); - assertThat(futureList.join()).isEqualTo(emptyList()); - } - - @Test - void multipleNull_ListToSet_ReturnsFutureOfEmptySet() { - final String nullString = null; - final CompletableFuture> futureSet = mapValuesToFutureOfCompletedValues( + assertThat(futureList.join()).isEqualTo(emptyList()); + } + + @Test + void multipleNull_ListToSet_ReturnsFutureOfEmptySet() { + final String nullString = null; + final CompletableFuture> futureSet = + mapValuesToFutureOfCompletedValues( asList(nullString, nullString), CompletableFuture::completedFuture, toSet()); - assertThat(futureSet.join()).isEqualTo(emptySet()); - } - - /** - * * List to List : Same Type Mapping : Single Value. - * * List to Set : Same Type Mapping : Single Value. - * * Set to Set : Same Type Mapping : Single Value. - * * Set to List : Same Type Mapping : Single Value. - * - *

List to List : Diff Type Mapping : Single Value. - * * List to Set : Diff Type Mapping : Single Value. - * * Set to Set : Diff Type Mapping : Single Value. - * * Set to List : Diff Type Mapping : Single Value. - */ - - @Test - void single_ListToListWithSameTypeMapping_ReturnsFutureOfListOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + assertThat(futureSet.join()).isEqualTo(emptySet()); + } + + /** + * * List to List : Same Type Mapping : Single Value. * List to Set : Same Type Mapping : Single + * Value. * Set to Set : Same Type Mapping : Single Value. * Set to List : Same Type Mapping : + * Single Value. + * + *

List to List : Diff Type Mapping : Single Value. * List to Set : Diff Type Mapping : Single + * Value. * Set to Set : Diff Type Mapping : Single Value. * Set to List : Diff Type Mapping : + * Single Value. + */ + @Test + void single_ListToListWithSameTypeMapping_ReturnsFutureOfListOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singletonList("foo"), element -> completedFuture(element.concat("POSTFIX")), toList()); - final List result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void single_ListToSetWithSameTypeMapping_ReturnsFutureOfSetOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void single_ListToSetWithSameTypeMapping_ReturnsFutureOfSetOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singletonList("foo"), element -> completedFuture(element.concat("POSTFIX")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void single_SetToSetWithSameTypeMapping_ReturnsFutureOfSetOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Set result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void single_SetToSetWithSameTypeMapping_ReturnsFutureOfSetOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singleton("foo"), element -> completedFuture(element.concat("POSTFIX")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void single_SetToListWithSameTypeMapping_ReturnsFutureOfListOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Set result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void single_SetToListWithSameTypeMapping_ReturnsFutureOfListOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singleton("foo"), element -> completedFuture(element.concat("POSTFIX")), toList()); - final List result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void single_ListToListWithDiffTypeMapping_ReturnsFutureOfListOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void single_ListToListWithDiffTypeMapping_ReturnsFutureOfListOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singletonList("foo"), element -> completedFuture(element.length()), toList()); - final List result = future.join(); - assertThat(result).containsExactly(3); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void single_ListToSetWithDiffTypeMapping_ReturnsFutureOfSetOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly(3); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void single_ListToSetWithDiffTypeMapping_ReturnsFutureOfSetOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singletonList("foo"), element -> completedFuture(element.length()), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly(3); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void single_SetToSetWithDiffTypeMapping_ReturnsFutureOfSetOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Set result = future.join(); + assertThat(result).containsExactly(3); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void single_SetToSetWithDiffTypeMapping_ReturnsFutureOfSetOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singleton("foo"), element -> completedFuture(element.length()), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly(3); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void single_SetToListWithDiffTypeMapping_ReturnsFutureOfListOfMappedValue() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final Set result = future.join(); + assertThat(result).containsExactly(3); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void single_SetToListWithDiffTypeMapping_ReturnsFutureOfListOfMappedValue() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( singleton("foo"), element -> completedFuture(element.length()), toList()); - final List result = future.join(); - assertThat(result).containsExactly(3); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - /** - * * List to List : Same Type Mapping : Multiple Values : no duplicates. - * * List to Set : Same Type Mapping : Multiple Values : no duplicates. - * * Set to Set : Same Type Mapping : Multiple Values : no duplicates. - * * Set to List : Same Type Mapping : Multiple Values : no duplicates. - * - *

List to List : Diff Type Mapping : Multiple Values : no duplicates. - * * List to Set : Diff Type Mapping : Multiple Values : no duplicates. - * * Set to Set : Diff Type Mapping : Multiple Values : no duplicates. - * * Set to List : Diff Type Mapping : Multiple Values : no duplicates. - */ - - @Test - void multiple_ListToListWithSameTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly(3); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + /** + * * List to List : Same Type Mapping : Multiple Values : no duplicates. * List to Set : Same Type + * Mapping : Multiple Values : no duplicates. * Set to Set : Same Type Mapping : Multiple Values : + * no duplicates. * Set to List : Same Type Mapping : Multiple Values : no duplicates. + * + *

List to List : Diff Type Mapping : Multiple Values : no duplicates. * List to Set : Diff + * Type Mapping : Multiple Values : no duplicates. * Set to Set : Diff Type Mapping : Multiple + * Values : no duplicates. * Set to List : Diff Type Mapping : Multiple Values : no duplicates. + */ + @Test + void multiple_ListToListWithSameTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( asList("foo", "bar"), element -> completedFuture(element.concat("POSTFIX")), toList()); - final List result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX", "barPOSTFIX"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_ListToSetWithSameTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(asList("foo", "bar"), - element -> completedFuture(element.concat("POSTFIX")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToSetWithSameTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { - final Set set = new HashSet<>(); - set.add("foo"); - set.add("bar"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture(element.concat("POSTFIX")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToListWithSameTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { - final Set set = new HashSet<>(); - set.add("foo"); - set.add("bar"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture(element.concat("POSTFIX")), toList()); - final List result = future.join(); - assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_ListToListWithDiffTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX", "barPOSTFIX"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void multiple_ListToSetWithSameTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + asList("foo", "bar"), element -> completedFuture(element.concat("POSTFIX")), toSet()); + final Set result = future.join(); + assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void multiple_SetToSetWithSameTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { + final Set set = new HashSet<>(); + set.add("foo"); + set.add("bar"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + set, element -> completedFuture(element.concat("POSTFIX")), toSet()); + final Set result = future.join(); + assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void multiple_SetToListWithSameTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { + final Set set = new HashSet<>(); + set.add("foo"); + set.add("bar"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + set, element -> completedFuture(element.concat("POSTFIX")), toList()); + final List result = future.join(); + assertThat(result).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void multiple_ListToListWithDiffTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( asList("john", "smith"), element -> completedFuture(element.length()), toList()); - final List result = future.join(); - assertThat(result).containsExactly(4, 5); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_ListToSetWithDiffTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(asList("john", "smith"), - element -> completedFuture(element.length()), toSet()); - final Set result = future.join(); - assertThat(result).containsExactlyInAnyOrder(4, 5); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToSetWithDiffTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { - final Set set = new HashSet<>(); - set.add("john"); - set.add("smith"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture(element.length()), toSet()); - final Set result = future.join(); - assertThat(result).containsExactlyInAnyOrder(4, 5); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToListWithDiffTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { - final Set set = new HashSet<>(); - set.add("john"); - set.add("smith"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture(element.length()), toList()); - final List result = future.join(); - assertThat(result).containsExactlyInAnyOrder(4, 5); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - /** - * * List to List : Same Type Mapping : Multiple Values : duplicates. - * * List to Set : Same Type Mapping : Multiple Values : duplicates. - * * Set to Set : Same Type Mapping : Multiple Values : duplicates. - * * Set to List : Same Type Mapping : Multiple Values : duplicates. - * - *

List to List : Diff Type Mapping : Multiple Values : duplicates. - * * List to Set : Diff Type Mapping : Multiple Values : duplicates. - * * Set to Set : Diff Type Mapping : Multiple Values : duplicates. - * * Set to List : Diff Type Mapping : Multiple Values : duplicates. - */ - - @Test - void multiple_ListToListWithSameTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly(4, 5); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void multiple_ListToSetWithDiffTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + asList("john", "smith"), element -> completedFuture(element.length()), toSet()); + final Set result = future.join(); + assertThat(result).containsExactlyInAnyOrder(4, 5); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void multiple_SetToSetWithDiffTypeMappingNoDuplicates_ReturnsFutureOfSetOfMappedValues() { + final Set set = new HashSet<>(); + set.add("john"); + set.add("smith"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + set, element -> completedFuture(element.length()), toSet()); + final Set result = future.join(); + assertThat(result).containsExactlyInAnyOrder(4, 5); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void multiple_SetToListWithDiffTypeMappingNoDuplicates_ReturnsFutureOfListOfMappedValues() { + final Set set = new HashSet<>(); + set.add("john"); + set.add("smith"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + set, element -> completedFuture(element.length()), toList()); + final List result = future.join(); + assertThat(result).containsExactlyInAnyOrder(4, 5); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + /** + * * List to List : Same Type Mapping : Multiple Values : duplicates. * List to Set : Same Type + * Mapping : Multiple Values : duplicates. * Set to Set : Same Type Mapping : Multiple Values : + * duplicates. * Set to List : Same Type Mapping : Multiple Values : duplicates. + * + *

List to List : Diff Type Mapping : Multiple Values : duplicates. * List to Set : Diff Type + * Mapping : Multiple Values : duplicates. * Set to Set : Diff Type Mapping : Multiple Values : + * duplicates. * Set to List : Diff Type Mapping : Multiple Values : duplicates. + */ + @Test + void + multiple_ListToListWithSameTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( asList("foo", "foo"), element -> completedFuture(element.concat("POSTFIX")), toList()); - final List result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX", "fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_ListToSetWithSameTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(asList("foo", "foo"), - element -> completedFuture(element.concat("POSTFIX")), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly("fooPOSTFIX"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToSetWithSameTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { - final Set set = new HashSet<>(); - set.add("foo"); - set.add("bar"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture("constantResult"), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly("constantResult"); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToListWithSameTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { - final Set set = new HashSet<>(); - set.add("foo"); - set.add("bar"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture("constantResult"), toList()); - final List result = future.join(); - assertThat(result).containsExactly("constantResult", "constantResult"); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_ListToListWithDiffTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues( + final List result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX", "fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void + multiple_ListToSetWithSameTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + asList("foo", "foo"), element -> completedFuture(element.concat("POSTFIX")), toSet()); + final Set result = future.join(); + assertThat(result).containsExactly("fooPOSTFIX"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void + multiple_SetToSetWithSameTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { + final Set set = new HashSet<>(); + set.add("foo"); + set.add("bar"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + set, element -> completedFuture("constantResult"), toSet()); + final Set result = future.join(); + assertThat(result).containsExactly("constantResult"); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void + multiple_SetToListWithSameTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { + final Set set = new HashSet<>(); + set.add("foo"); + set.add("bar"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + set, element -> completedFuture("constantResult"), toList()); + final List result = future.join(); + assertThat(result).containsExactly("constantResult", "constantResult"); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void + multiple_ListToListWithDiffTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( asList("john", "john"), element -> completedFuture(element.length()), toList()); - final List result = future.join(); - assertThat(result).containsExactly(4, 4); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } - - @Test - void multiple_ListToSetWithDiffTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(asList("john", "john"), - element -> completedFuture(element.length()), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly(4); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToSetWithDiffTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { - final Set set = new HashSet<>(); - set.add("foo"); - set.add("bar"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture(element.length()), toSet()); - final Set result = future.join(); - assertThat(result).containsExactly(3); - assertThat(result).isExactlyInstanceOf(HashSet.class); - } - - @Test - void multiple_SetToListWithDiffTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { - final Set set = new HashSet<>(); - set.add("foo"); - set.add("bar"); - - final CompletableFuture> future = mapValuesToFutureOfCompletedValues(set, - element -> completedFuture(element.length()), toList()); - final List result = future.join(); - assertThat(result).containsExactly(3, 3); - assertThat(result).isExactlyInstanceOf(ArrayList.class); - } + final List result = future.join(); + assertThat(result).containsExactly(4, 4); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } + + @Test + void + multiple_ListToSetWithDiffTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + asList("john", "john"), element -> completedFuture(element.length()), toSet()); + final Set result = future.join(); + assertThat(result).containsExactly(4); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void + multiple_SetToSetWithDiffTypeMappingDuplicates_ReturnsFutureOfSetOfMappedValuesNoDuplicates() { + final Set set = new HashSet<>(); + set.add("foo"); + set.add("bar"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + set, element -> completedFuture(element.length()), toSet()); + final Set result = future.join(); + assertThat(result).containsExactly(3); + assertThat(result).isExactlyInstanceOf(HashSet.class); + } + + @Test + void + multiple_SetToListWithDiffTypeMappingDuplicates_ReturnsFutureOfListOfMappedValuesWithDuplicates() { + final Set set = new HashSet<>(); + set.add("foo"); + set.add("bar"); + + final CompletableFuture> future = + mapValuesToFutureOfCompletedValues( + set, element -> completedFuture(element.length()), toList()); + final List result = future.join(); + assertThat(result).containsExactly(3, 3); + assertThat(result).isExactlyInstanceOf(ArrayList.class); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFuturesTest.java b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFuturesTest.java index b4d60c8400..873fb65926 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFuturesTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/MapValuesToFuturesTest.java @@ -1,7 +1,10 @@ package com.commercetools.sync.commons.utils.completablefutureutils; - -import org.junit.jupiter.api.Test; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutures; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.HashSet; @@ -9,260 +12,231 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; - -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutures; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class MapValuesToFuturesTest { - /** - * Stream to List : empty values. - * Stream to Set : empty values. - **/ - - @Test - void empty_StreamToList_ReturnsEmptyList() { - final List> futures = mapValuesToFutures(Stream.empty(), - CompletableFuture::completedFuture, toList()); - assertThat(futures).isEmpty(); - } - - @Test - void empty_StreamToSet_ReturnsEmptySet() { - final Set> futures = mapValuesToFutures(Stream.empty(), - CompletableFuture::completedFuture, toSet()); - assertThat(futures).isEmpty(); - } - - - /** - * Stream to List : single null value. - * Stream to Set : single null value. - */ - - @Test - void singleNull_StreamToList_ReturnsEmptyList() { - final String nullString = null; - final List> futures = mapValuesToFutures(Stream.of(nullString), - CompletableFuture::completedFuture, toList()); - assertThat(futures).isEmpty(); - } - - @Test - void singleNull_StreamToSet_ReturnsEmptySet() { - final String nullString = null; - final Set> futures = mapValuesToFutures(Stream.of(nullString), - CompletableFuture::completedFuture, toSet()); - assertThat(futures).isEmpty(); - } - - /** - * Stream to List : multiple null value. - * Stream to Set : multiple null value. - */ - @Test - void multipleNull_StreamToList_ReturnsEmptyList() { - final String nullString = null; - final List> futures = mapValuesToFutures(Stream.of(nullString, nullString), - CompletableFuture::completedFuture, toList()); - assertThat(futures).isEmpty(); - } - - @Test - void multipleNull_StreamToSet_ReturnsEmptySet() { - final String nullString = null; - final Set> futures = mapValuesToFutures(Stream.of(nullString, nullString), - CompletableFuture::completedFuture, toSet()); - assertThat(futures).isEmpty(); - } - - /** - * * Stream to List : Same Type Mapping : Single Value. - * * Stream to Set : Same Type Mapping : Single Value. - * * Stream to List : Diff Type Mapping : Single Value. - * * Stream to Set : Diff Type Mapping : Single Value. - */ - - @Test - void single_StreamToListWithSameTypeMapping_ReturnsListOfMappedValue() { - final List> futures = mapValuesToFutures(Stream.of("foo"), - element -> completedFuture(element.concat("POSTFIX")), toList()); - - assertThat(futures).hasSize(1); - assertThat(futures).isExactlyInstanceOf(ArrayList.class); - assertThat(futures.get(0).join()).isEqualTo("fooPOSTFIX"); - } - - @Test - void single_StreamToSetWithSameTypeMapping_ReturnsSetOfMappedValue() { - final Set> futures = mapValuesToFutures(Stream.of("foo"), - element -> completedFuture(element.concat("POSTFIX")), toSet()); - - assertThat(futures).hasSize(1); - assertThat(futures).isExactlyInstanceOf(HashSet.class); - assertThat(futures.iterator().next().join()).isEqualTo("fooPOSTFIX"); - } - - @Test - void single_StreamToListWithDiffTypeMapping_ReturnsListOfMappedValue() { - final List> futures = mapValuesToFutures(Stream.of("foo"), - element -> completedFuture(element.length()), toList()); - - assertThat(futures).hasSize(1); - assertThat(futures).isExactlyInstanceOf(ArrayList.class); - assertThat(futures.iterator().next().join()).isEqualTo(3); - } - - @Test - void single_StreamToSetWithDiffTypeMapping_ReturnsSetOfMappedValue() { - final Set> futures = mapValuesToFutures(Stream.of("foo"), - element -> completedFuture(element.length()), toSet()); - - assertThat(futures).hasSize(1); - assertThat(futures).isExactlyInstanceOf(HashSet.class); - assertThat(futures.iterator().next().join()).isEqualTo(3); - } - - /** - * * Stream to List : Same Type Mapping : Multiple Values : no duplicates. - * * Stream to Set : Same Type Mapping : Multiple Values : no duplicates. - * * Stream to List : Diff Type Mapping : Multiple Values : no duplicates. - * * Stream to Set : Diff Type Mapping : Multiple Values : no duplicates. - */ - - @Test - void multiple_StreamToListWithSameTypeMappingNoDuplicates_ReturnsListOfMappedValues() { - final List> futures = mapValuesToFutures(Stream.of("foo", "bar"), - element -> completedFuture(element.concat("POSTFIX")), toList()); - - assertThat(futures).hasSize(2); - assertThat(futures).isExactlyInstanceOf(ArrayList.class); - - final List results = futures.stream() - .map(CompletableFuture::join).collect(toList()); - - assertThat(results).containsExactly("fooPOSTFIX", "barPOSTFIX"); - } - - @Test - void multiple_StreamToSetWithSameTypeMappingNoDuplicates_ReturnsSetOfMappedValues() { - final Set> futures = mapValuesToFutures(Stream.of("foo", "bar"), - element -> completedFuture(element.concat("POSTFIX")), toSet()); - - assertThat(futures).hasSize(2); - assertThat(futures).isExactlyInstanceOf(HashSet.class); - - final List results = futures.stream() - .map(CompletableFuture::join) - .collect(toList()); - - assertThat(results).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); - - } - - @Test - void multiple_StreamToListWithDiffTypeMappingNoDuplicates_ReturnsListOfMappedValues() { - final List> futures = mapValuesToFutures(Stream.of("john", "smith"), - element -> completedFuture(element.length()), toList()); - - assertThat(futures).hasSize(2); - assertThat(futures).isExactlyInstanceOf(ArrayList.class); - - final List results = futures.stream() - .map(CompletableFuture::join) - .collect(toList()); - - - assertThat(results).containsExactly(4, 5); - } - - @Test - void multiple_StreamToSetWithDiffTypeMappingNoDuplicates_ReturnsSetOfMappedValues() { - final Set> futures = mapValuesToFutures(Stream.of("john", "smith"), - element -> completedFuture(element.length()), toSet()); - - assertThat(futures).hasSize(2); - assertThat(futures).isExactlyInstanceOf(HashSet.class); - - final List results = futures.stream() - .map(CompletableFuture::join) - .collect(toList()); - - - assertThat(results).containsExactlyInAnyOrder(4, 5); - - } - - /** - * * Stream to List : Same Type Mapping : Multiple Values : duplicates. - * * Stream to Set : Same Type Mapping : Multiple Values : duplicates. - * * Stream to List : Diff Type Mapping : Multiple Values : duplicates. - * * Stream to Set : Diff Type Mapping : Multiple Values : duplicates. - */ - - @Test - void multiple_StreamToListWithSameTypeMappingDuplicates_ReturnsListOfMappedValuesWithDuplicates() { - final List> futures = mapValuesToFutures(Stream.of("john", "john"), - CompletableFuture::completedFuture, toList()); - - assertThat(futures).hasSize(2); - assertThat(futures).isExactlyInstanceOf(ArrayList.class); - - final List results = futures.stream() - .map(CompletableFuture::join) - .collect(toList()); - - - assertThat(results).containsExactly("john", "john"); - } - - @Test - void multiple_StreamToSetWithSameTypeMappingDuplicates_ReturnsSetOfMappedValuesDuplicates() { - final Set> futures = mapValuesToFutures(Stream.of("john", "john"), - CompletableFuture::completedFuture, toSet()); - - assertThat(futures).hasSize(2); - assertThat(futures).isExactlyInstanceOf(HashSet.class); + /** Stream to List : empty values. Stream to Set : empty values. */ + @Test + void empty_StreamToList_ReturnsEmptyList() { + final List> futures = + mapValuesToFutures(Stream.empty(), CompletableFuture::completedFuture, toList()); + assertThat(futures).isEmpty(); + } + + @Test + void empty_StreamToSet_ReturnsEmptySet() { + final Set> futures = + mapValuesToFutures(Stream.empty(), CompletableFuture::completedFuture, toSet()); + assertThat(futures).isEmpty(); + } + + /** Stream to List : single null value. Stream to Set : single null value. */ + @Test + void singleNull_StreamToList_ReturnsEmptyList() { + final String nullString = null; + final List> futures = + mapValuesToFutures(Stream.of(nullString), CompletableFuture::completedFuture, toList()); + assertThat(futures).isEmpty(); + } + + @Test + void singleNull_StreamToSet_ReturnsEmptySet() { + final String nullString = null; + final Set> futures = + mapValuesToFutures(Stream.of(nullString), CompletableFuture::completedFuture, toSet()); + assertThat(futures).isEmpty(); + } + + /** Stream to List : multiple null value. Stream to Set : multiple null value. */ + @Test + void multipleNull_StreamToList_ReturnsEmptyList() { + final String nullString = null; + final List> futures = + mapValuesToFutures( + Stream.of(nullString, nullString), CompletableFuture::completedFuture, toList()); + assertThat(futures).isEmpty(); + } + + @Test + void multipleNull_StreamToSet_ReturnsEmptySet() { + final String nullString = null; + final Set> futures = + mapValuesToFutures( + Stream.of(nullString, nullString), CompletableFuture::completedFuture, toSet()); + assertThat(futures).isEmpty(); + } + + /** + * * Stream to List : Same Type Mapping : Single Value. * Stream to Set : Same Type Mapping : + * Single Value. * Stream to List : Diff Type Mapping : Single Value. * Stream to Set : Diff Type + * Mapping : Single Value. + */ + @Test + void single_StreamToListWithSameTypeMapping_ReturnsListOfMappedValue() { + final List> futures = + mapValuesToFutures( + Stream.of("foo"), element -> completedFuture(element.concat("POSTFIX")), toList()); + + assertThat(futures).hasSize(1); + assertThat(futures).isExactlyInstanceOf(ArrayList.class); + assertThat(futures.get(0).join()).isEqualTo("fooPOSTFIX"); + } + + @Test + void single_StreamToSetWithSameTypeMapping_ReturnsSetOfMappedValue() { + final Set> futures = + mapValuesToFutures( + Stream.of("foo"), element -> completedFuture(element.concat("POSTFIX")), toSet()); + + assertThat(futures).hasSize(1); + assertThat(futures).isExactlyInstanceOf(HashSet.class); + assertThat(futures.iterator().next().join()).isEqualTo("fooPOSTFIX"); + } + + @Test + void single_StreamToListWithDiffTypeMapping_ReturnsListOfMappedValue() { + final List> futures = + mapValuesToFutures( + Stream.of("foo"), element -> completedFuture(element.length()), toList()); + + assertThat(futures).hasSize(1); + assertThat(futures).isExactlyInstanceOf(ArrayList.class); + assertThat(futures.iterator().next().join()).isEqualTo(3); + } + + @Test + void single_StreamToSetWithDiffTypeMapping_ReturnsSetOfMappedValue() { + final Set> futures = + mapValuesToFutures(Stream.of("foo"), element -> completedFuture(element.length()), toSet()); + + assertThat(futures).hasSize(1); + assertThat(futures).isExactlyInstanceOf(HashSet.class); + assertThat(futures.iterator().next().join()).isEqualTo(3); + } + + /** + * * Stream to List : Same Type Mapping : Multiple Values : no duplicates. * Stream to Set : Same + * Type Mapping : Multiple Values : no duplicates. * Stream to List : Diff Type Mapping : Multiple + * Values : no duplicates. * Stream to Set : Diff Type Mapping : Multiple Values : no duplicates. + */ + @Test + void multiple_StreamToListWithSameTypeMappingNoDuplicates_ReturnsListOfMappedValues() { + final List> futures = + mapValuesToFutures( + Stream.of("foo", "bar"), + element -> completedFuture(element.concat("POSTFIX")), + toList()); + + assertThat(futures).hasSize(2); + assertThat(futures).isExactlyInstanceOf(ArrayList.class); + + final List results = futures.stream().map(CompletableFuture::join).collect(toList()); + + assertThat(results).containsExactly("fooPOSTFIX", "barPOSTFIX"); + } + + @Test + void multiple_StreamToSetWithSameTypeMappingNoDuplicates_ReturnsSetOfMappedValues() { + final Set> futures = + mapValuesToFutures( + Stream.of("foo", "bar"), + element -> completedFuture(element.concat("POSTFIX")), + toSet()); + + assertThat(futures).hasSize(2); + assertThat(futures).isExactlyInstanceOf(HashSet.class); + + final List results = futures.stream().map(CompletableFuture::join).collect(toList()); + + assertThat(results).containsExactlyInAnyOrder("fooPOSTFIX", "barPOSTFIX"); + } + + @Test + void multiple_StreamToListWithDiffTypeMappingNoDuplicates_ReturnsListOfMappedValues() { + final List> futures = + mapValuesToFutures( + Stream.of("john", "smith"), element -> completedFuture(element.length()), toList()); + + assertThat(futures).hasSize(2); + assertThat(futures).isExactlyInstanceOf(ArrayList.class); + + final List results = futures.stream().map(CompletableFuture::join).collect(toList()); + + assertThat(results).containsExactly(4, 5); + } + + @Test + void multiple_StreamToSetWithDiffTypeMappingNoDuplicates_ReturnsSetOfMappedValues() { + final Set> futures = + mapValuesToFutures( + Stream.of("john", "smith"), element -> completedFuture(element.length()), toSet()); + + assertThat(futures).hasSize(2); + assertThat(futures).isExactlyInstanceOf(HashSet.class); + + final List results = futures.stream().map(CompletableFuture::join).collect(toList()); + + assertThat(results).containsExactlyInAnyOrder(4, 5); + } + + /** + * * Stream to List : Same Type Mapping : Multiple Values : duplicates. * Stream to Set : Same + * Type Mapping : Multiple Values : duplicates. * Stream to List : Diff Type Mapping : Multiple + * Values : duplicates. * Stream to Set : Diff Type Mapping : Multiple Values : duplicates. + */ + @Test + void + multiple_StreamToListWithSameTypeMappingDuplicates_ReturnsListOfMappedValuesWithDuplicates() { + final List> futures = + mapValuesToFutures(Stream.of("john", "john"), CompletableFuture::completedFuture, toList()); + + assertThat(futures).hasSize(2); + assertThat(futures).isExactlyInstanceOf(ArrayList.class); - final List results = futures.stream() - .map(CompletableFuture::join) - .collect(toList()); + final List results = futures.stream().map(CompletableFuture::join).collect(toList()); + + assertThat(results).containsExactly("john", "john"); + } + @Test + void multiple_StreamToSetWithSameTypeMappingDuplicates_ReturnsSetOfMappedValuesDuplicates() { + final Set> futures = + mapValuesToFutures(Stream.of("john", "john"), CompletableFuture::completedFuture, toSet()); - assertThat(results).containsExactly("john", "john"); - } + assertThat(futures).hasSize(2); + assertThat(futures).isExactlyInstanceOf(HashSet.class); - @Test - void multiple_StreamToListWithDiffTypeMappingDuplicates_ReturnsListOfMappedValuesWithDuplicates() { - final List> futures = mapValuesToFutures(Stream.of("john", "john"), - element -> completedFuture(element.length()), toList()); + final List results = futures.stream().map(CompletableFuture::join).collect(toList()); - assertThat(futures).hasSize(2); - assertThat(futures).isExactlyInstanceOf(ArrayList.class); + assertThat(results).containsExactly("john", "john"); + } - final List results = futures.stream() - .map(CompletableFuture::join) - .collect(toList()); + @Test + void + multiple_StreamToListWithDiffTypeMappingDuplicates_ReturnsListOfMappedValuesWithDuplicates() { + final List> futures = + mapValuesToFutures( + Stream.of("john", "john"), element -> completedFuture(element.length()), toList()); + assertThat(futures).hasSize(2); + assertThat(futures).isExactlyInstanceOf(ArrayList.class); - assertThat(results).containsExactly(4, 4); - } + final List results = futures.stream().map(CompletableFuture::join).collect(toList()); - @Test - void multiple_StreamToSetWithDiffTypeMappingDuplicates_ReturnsSetOfMappedValuesWithDuplicates() { - final Set> futures = mapValuesToFutures(Stream.of("john", "john"), - element -> completedFuture(element.length()), toSet()); + assertThat(results).containsExactly(4, 4); + } - assertThat(futures).hasSize(2); - assertThat(futures).isExactlyInstanceOf(HashSet.class); + @Test + void multiple_StreamToSetWithDiffTypeMappingDuplicates_ReturnsSetOfMappedValuesWithDuplicates() { + final Set> futures = + mapValuesToFutures( + Stream.of("john", "john"), element -> completedFuture(element.length()), toSet()); - final List results = futures.stream() - .map(CompletableFuture::join) - .collect(toList()); + assertThat(futures).hasSize(2); + assertThat(futures).isExactlyInstanceOf(HashSet.class); + final List results = futures.stream().map(CompletableFuture::join).collect(toList()); - assertThat(results).containsExactly(4, 4); - } + assertThat(results).containsExactly(4, 4); + } } diff --git a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/ToCompletableFuturesTest.java b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/ToCompletableFuturesTest.java index 58c45b39ad..37dba43228 100644 --- a/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/ToCompletableFuturesTest.java +++ b/src/test/java/com/commercetools/sync/commons/utils/completablefutureutils/ToCompletableFuturesTest.java @@ -1,7 +1,12 @@ package com.commercetools.sync.commons.utils.completablefutureutils; - -import org.junit.jupiter.api.Test; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.toCompletableFutures; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.HashSet; @@ -10,178 +15,156 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.stream.Stream; - -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.toCompletableFutures; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ToCompletableFuturesTest { - /** - * Stream to List : empty values. - * Stream to Set : empty values. - **/ - - @Test - void empty_StreamToList_ReturnsEmptyList() { - final List> futures = - toCompletableFutures(Stream.>empty(), toList()); - assertThat(futures).isEmpty(); - } - - @Test - void empty_StreamToSet_ReturnsEmptySet() { - final Set> futures = toCompletableFutures(Stream.>empty(), - toSet()); - assertThat(futures).isEmpty(); - } - - - /** - * Stream to List : single null value. - * Stream to Set : single null value. - */ - - @Test - void singleNull_StreamToList_ReturnsEmptyList() { - final CompletionStage nullStage = null; - final List> futures = toCompletableFutures(Stream.of(nullStage), toList()); - assertThat(futures).isEmpty(); - } - - @Test - void singleNull_StreamToSet_ReturnsEmptySet() { - final CompletionStage nullStage = null; - final Set> futures = toCompletableFutures(Stream.of(nullStage), toSet()); - assertThat(futures).isEmpty(); - } - - /** - * Stream to List : multiple null value. - * Stream to Set : multiple null value. - */ - @Test - void multipleNull_StreamToList_ReturnsEmptyList() { - final CompletionStage nullStage = null; - final List> futures = toCompletableFutures(Stream.of(nullStage, nullStage), toList()); - assertThat(futures).isEmpty(); - } - - @Test - void multipleNull_StreamToSet_ReturnsEmptySet() { - final CompletionStage nullStage = null; - final Set> futures = toCompletableFutures(Stream.of(nullStage, nullStage), toSet()); - assertThat(futures).isEmpty(); - } - - /** - * * Stream to List : Single Value. - * * Stream to Set : Single Value. - */ - - @Test - void single_StreamToList_ReturnsListOfMappedValue() { - final CompletionStage completionStage = spy(completedFuture("foo")); - final CompletableFuture barFuture = completedFuture("bar"); - when(completionStage.toCompletableFuture()).thenReturn(barFuture); - - - final List> futures = toCompletableFutures(Stream.of(completionStage), toList()); - - assertThat(futures).hasSize(1); - assertThat(futures).isExactlyInstanceOf(ArrayList.class); - assertThat(futures.get(0).join()).isEqualTo("bar"); - } - - @Test - void single_StreamToSet_ReturnsSetOfMappedValue() { - final CompletionStage completionStage = spy(completedFuture("foo")); - final CompletableFuture barFuture = completedFuture("bar"); - when(completionStage.toCompletableFuture()).thenReturn(barFuture); - - - final Set> futures = toCompletableFutures(Stream.of(completionStage), toSet()); - - assertThat(futures).hasSize(1); - assertThat(futures).isExactlyInstanceOf(HashSet.class); - assertThat(futures.iterator().next().join()).isEqualTo("bar"); - } - - /** - * * Stream to List : Multiple Values : no duplicates. - * * Stream to Set : Multiple Values : no duplicates. - */ - - @Test - void multiple_StreamToListNoDuplicates_ReturnsListOfMappedValues() { - final CompletionStage completionStage1 = spy(completedFuture("foo")); - final CompletableFuture barFuture = completedFuture("bar"); - when(completionStage1.toCompletableFuture()).thenReturn(barFuture); - - final CompletionStage completionStage2 = spy(completedFuture("foo1")); - final CompletableFuture bar1Future = completedFuture("bar1"); - when(completionStage2.toCompletableFuture()).thenReturn(bar1Future); - - final List> futures = - toCompletableFutures(Stream.of(completionStage1, completionStage2), toList()); - - assertThat(futures).isNotEmpty(); - assertThat(futures).hasSize(2); - assertThat(futures).containsExactlyInAnyOrder(barFuture, bar1Future); - } - - @Test - void multiple_StreamToSetNoDuplicates_ReturnsSetOfMappedValues() { - final CompletionStage completionStage1 = spy(completedFuture("foo")); - final CompletableFuture barFuture = completedFuture("bar"); - when(completionStage1.toCompletableFuture()).thenReturn(barFuture); - - final CompletionStage completionStage2 = spy(completedFuture("foo1")); - final CompletableFuture bar1Future = completedFuture("bar1"); - when(completionStage2.toCompletableFuture()).thenReturn(bar1Future); - - final Set> futures = - toCompletableFutures((Stream.of(completionStage1, completionStage2)), toSet()); - - assertThat(futures).isNotEmpty(); - assertThat(futures).hasSize(2); - assertThat(futures).containsExactlyInAnyOrder(barFuture, bar1Future); - - } - - /** - * * Stream to List : Multiple Values : duplicates. - * * Stream to Set : Multiple Values : duplicates. - */ - - @Test - void multiple_StreamToListDuplicates_ReturnsListOfMappedValuesWithDuplicates() { - final CompletionStage completionStage = spy(completedFuture("foo")); - final CompletableFuture barFuture = completedFuture("bar"); - when(completionStage.toCompletableFuture()).thenReturn(barFuture); - - final List> futures = - toCompletableFutures(Stream.of(completionStage, completionStage), toList()); - - assertThat(futures).isNotEmpty(); - assertThat(futures).hasSize(2); - assertThat(futures).containsExactlyInAnyOrder(barFuture, barFuture); - } - - @Test - void multiple_StreamToSetDuplicates_ReturnsSetOfMappedValuesNoDuplicates() { - final CompletionStage completionStage = spy(completedFuture("foo")); - final CompletableFuture barFuture = completedFuture("bar"); - when(completionStage.toCompletableFuture()).thenReturn(barFuture); - - final Set> futures = - toCompletableFutures(Stream.of(completionStage, completionStage), toSet()); - - assertThat(futures).isNotEmpty(); - assertThat(futures).hasSize(1); - assertThat(futures).containsExactly(barFuture); - } + /** Stream to List : empty values. Stream to Set : empty values. */ + @Test + void empty_StreamToList_ReturnsEmptyList() { + final List> futures = + toCompletableFutures(Stream.>empty(), toList()); + assertThat(futures).isEmpty(); + } + + @Test + void empty_StreamToSet_ReturnsEmptySet() { + final Set> futures = + toCompletableFutures(Stream.>empty(), toSet()); + assertThat(futures).isEmpty(); + } + + /** Stream to List : single null value. Stream to Set : single null value. */ + @Test + void singleNull_StreamToList_ReturnsEmptyList() { + final CompletionStage nullStage = null; + final List> futures = + toCompletableFutures(Stream.of(nullStage), toList()); + assertThat(futures).isEmpty(); + } + + @Test + void singleNull_StreamToSet_ReturnsEmptySet() { + final CompletionStage nullStage = null; + final Set> futures = + toCompletableFutures(Stream.of(nullStage), toSet()); + assertThat(futures).isEmpty(); + } + + /** Stream to List : multiple null value. Stream to Set : multiple null value. */ + @Test + void multipleNull_StreamToList_ReturnsEmptyList() { + final CompletionStage nullStage = null; + final List> futures = + toCompletableFutures(Stream.of(nullStage, nullStage), toList()); + assertThat(futures).isEmpty(); + } + + @Test + void multipleNull_StreamToSet_ReturnsEmptySet() { + final CompletionStage nullStage = null; + final Set> futures = + toCompletableFutures(Stream.of(nullStage, nullStage), toSet()); + assertThat(futures).isEmpty(); + } + + /** * Stream to List : Single Value. * Stream to Set : Single Value. */ + @Test + void single_StreamToList_ReturnsListOfMappedValue() { + final CompletionStage completionStage = spy(completedFuture("foo")); + final CompletableFuture barFuture = completedFuture("bar"); + when(completionStage.toCompletableFuture()).thenReturn(barFuture); + + final List> futures = + toCompletableFutures(Stream.of(completionStage), toList()); + + assertThat(futures).hasSize(1); + assertThat(futures).isExactlyInstanceOf(ArrayList.class); + assertThat(futures.get(0).join()).isEqualTo("bar"); + } + + @Test + void single_StreamToSet_ReturnsSetOfMappedValue() { + final CompletionStage completionStage = spy(completedFuture("foo")); + final CompletableFuture barFuture = completedFuture("bar"); + when(completionStage.toCompletableFuture()).thenReturn(barFuture); + + final Set> futures = + toCompletableFutures(Stream.of(completionStage), toSet()); + + assertThat(futures).hasSize(1); + assertThat(futures).isExactlyInstanceOf(HashSet.class); + assertThat(futures.iterator().next().join()).isEqualTo("bar"); + } + + /** + * * Stream to List : Multiple Values : no duplicates. * Stream to Set : Multiple Values : no + * duplicates. + */ + @Test + void multiple_StreamToListNoDuplicates_ReturnsListOfMappedValues() { + final CompletionStage completionStage1 = spy(completedFuture("foo")); + final CompletableFuture barFuture = completedFuture("bar"); + when(completionStage1.toCompletableFuture()).thenReturn(barFuture); + + final CompletionStage completionStage2 = spy(completedFuture("foo1")); + final CompletableFuture bar1Future = completedFuture("bar1"); + when(completionStage2.toCompletableFuture()).thenReturn(bar1Future); + + final List> futures = + toCompletableFutures(Stream.of(completionStage1, completionStage2), toList()); + + assertThat(futures).isNotEmpty(); + assertThat(futures).hasSize(2); + assertThat(futures).containsExactlyInAnyOrder(barFuture, bar1Future); + } + + @Test + void multiple_StreamToSetNoDuplicates_ReturnsSetOfMappedValues() { + final CompletionStage completionStage1 = spy(completedFuture("foo")); + final CompletableFuture barFuture = completedFuture("bar"); + when(completionStage1.toCompletableFuture()).thenReturn(barFuture); + + final CompletionStage completionStage2 = spy(completedFuture("foo1")); + final CompletableFuture bar1Future = completedFuture("bar1"); + when(completionStage2.toCompletableFuture()).thenReturn(bar1Future); + + final Set> futures = + toCompletableFutures((Stream.of(completionStage1, completionStage2)), toSet()); + + assertThat(futures).isNotEmpty(); + assertThat(futures).hasSize(2); + assertThat(futures).containsExactlyInAnyOrder(barFuture, bar1Future); + } + + /** + * * Stream to List : Multiple Values : duplicates. * Stream to Set : Multiple Values : + * duplicates. + */ + @Test + void multiple_StreamToListDuplicates_ReturnsListOfMappedValuesWithDuplicates() { + final CompletionStage completionStage = spy(completedFuture("foo")); + final CompletableFuture barFuture = completedFuture("bar"); + when(completionStage.toCompletableFuture()).thenReturn(barFuture); + + final List> futures = + toCompletableFutures(Stream.of(completionStage, completionStage), toList()); + + assertThat(futures).isNotEmpty(); + assertThat(futures).hasSize(2); + assertThat(futures).containsExactlyInAnyOrder(barFuture, barFuture); + } + + @Test + void multiple_StreamToSetDuplicates_ReturnsSetOfMappedValuesNoDuplicates() { + final CompletionStage completionStage = spy(completedFuture("foo")); + final CompletableFuture barFuture = completedFuture("bar"); + when(completionStage.toCompletableFuture()).thenReturn(barFuture); + + final Set> futures = + toCompletableFutures(Stream.of(completionStage, completionStage), toSet()); + + assertThat(futures).isNotEmpty(); + assertThat(futures).hasSize(1); + assertThat(futures).containsExactly(barFuture); + } } diff --git a/src/test/java/com/commercetools/sync/customers/CustomerSyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/customers/CustomerSyncOptionsBuilderTest.java index 95a941c9f1..2f6546a988 100644 --- a/src/test/java/com/commercetools/sync/customers/CustomerSyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/customers/CustomerSyncOptionsBuilderTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.customers; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -10,278 +19,268 @@ import io.sphere.sdk.customers.CustomerDraft; import io.sphere.sdk.customers.CustomerDraftBuilder; import io.sphere.sdk.customers.commands.updateactions.ChangeEmail; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CustomerSyncOptionsBuilderTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private final CustomerSyncOptionsBuilder customerSyncOptionsBuilder = CustomerSyncOptionsBuilder.of(CTP_CLIENT); - - @Test - void of_WithClient_ShouldCreateCustomerSyncOptionsBuilder() { - final CustomerSyncOptionsBuilder builder = CustomerSyncOptionsBuilder.of(CTP_CLIENT); - assertThat(builder).isNotNull(); - } - - @Test - void build_WithClient_ShouldBuildSyncOptions() { - final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); - assertThat(customerSyncOptions).isNotNull(); - assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNull(); - assertThat(customerSyncOptions.getBeforeCreateCallback()).isNull(); - assertThat(customerSyncOptions.getErrorCallback()).isNull(); - assertThat(customerSyncOptions.getWarningCallback()).isNull(); - assertThat(customerSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); - assertThat(customerSyncOptions.getBatchSize()).isEqualTo(CustomerSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(customerSyncOptions.getCacheSize()).isEqualTo(10_000); - } - - @Test - void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { - final TriFunction>, CustomerDraft, Customer, List>> - beforeUpdateCallback = (updateActions, newCustomer, oldCustomer) -> emptyList(); - - customerSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - - final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); - assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNotNull(); - } - - @Test - void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { - customerSyncOptionsBuilder.beforeCreateCallback((newCustomer) -> null); - - final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); - assertThat(customerSyncOptions.getBeforeCreateCallback()).isNotNull(); - } - - @Test - void errorCallBack_WithCallBack_ShouldSetCallBack() { - final QuadConsumer, Optional, List>> - mockErrorCallBack = (exception, newResource, oldResource, updateActions) -> { }; - customerSyncOptionsBuilder.errorCallback(mockErrorCallBack); - - final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); - assertThat(customerSyncOptions.getErrorCallback()).isNotNull(); - } - - @Test - void warningCallBack_WithCallBack_ShouldSetCallBack() { - final TriConsumer, Optional> mockWarningCallBack = - (exception, newResource, oldResource) -> { - }; - customerSyncOptionsBuilder.warningCallback(mockWarningCallBack); - - final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); - assertThat(customerSyncOptions.getWarningCallback()).isNotNull(); - } - - @Test - void getThis_ShouldReturnCorrectInstance() { - final CustomerSyncOptionsBuilder builder = customerSyncOptionsBuilder.getThis(); - assertThat(builder).isNotNull(); - assertThat(builder).isInstanceOf(CustomerSyncOptionsBuilder.class); - assertThat(builder).isEqualTo(customerSyncOptionsBuilder); - } - - @Test - void customerSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { - final CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder - .of(CTP_CLIENT) + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private final CustomerSyncOptionsBuilder customerSyncOptionsBuilder = + CustomerSyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateCustomerSyncOptionsBuilder() { + final CustomerSyncOptionsBuilder builder = CustomerSyncOptionsBuilder.of(CTP_CLIENT); + assertThat(builder).isNotNull(); + } + + @Test + void build_WithClient_ShouldBuildSyncOptions() { + final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); + assertThat(customerSyncOptions).isNotNull(); + assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNull(); + assertThat(customerSyncOptions.getBeforeCreateCallback()).isNull(); + assertThat(customerSyncOptions.getErrorCallback()).isNull(); + assertThat(customerSyncOptions.getWarningCallback()).isNull(); + assertThat(customerSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); + assertThat(customerSyncOptions.getBatchSize()) + .isEqualTo(CustomerSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + assertThat(customerSyncOptions.getCacheSize()).isEqualTo(10_000); + } + + @Test + void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { + final TriFunction< + List>, CustomerDraft, Customer, List>> + beforeUpdateCallback = (updateActions, newCustomer, oldCustomer) -> emptyList(); + + customerSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); + + final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); + assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNotNull(); + } + + @Test + void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { + customerSyncOptionsBuilder.beforeCreateCallback((newCustomer) -> null); + + final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); + assertThat(customerSyncOptions.getBeforeCreateCallback()).isNotNull(); + } + + @Test + void errorCallBack_WithCallBack_ShouldSetCallBack() { + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + mockErrorCallBack = (exception, newResource, oldResource, updateActions) -> {}; + customerSyncOptionsBuilder.errorCallback(mockErrorCallBack); + + final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); + assertThat(customerSyncOptions.getErrorCallback()).isNotNull(); + } + + @Test + void warningCallBack_WithCallBack_ShouldSetCallBack() { + final TriConsumer, Optional> + mockWarningCallBack = (exception, newResource, oldResource) -> {}; + customerSyncOptionsBuilder.warningCallback(mockWarningCallBack); + + final CustomerSyncOptions customerSyncOptions = customerSyncOptionsBuilder.build(); + assertThat(customerSyncOptions.getWarningCallback()).isNotNull(); + } + + @Test + void getThis_ShouldReturnCorrectInstance() { + final CustomerSyncOptionsBuilder builder = customerSyncOptionsBuilder.getThis(); + assertThat(builder).isNotNull(); + assertThat(builder).isInstanceOf(CustomerSyncOptionsBuilder.class); + assertThat(builder).isEqualTo(customerSyncOptionsBuilder); + } + + @Test + void customerSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT) .batchSize(30) .beforeCreateCallback((newCustomer) -> null) .beforeUpdateCallback((updateActions, newCustomer, oldCustomer) -> emptyList()) .build(); - assertThat(customerSyncOptions).isNotNull(); - } - - @Test - void batchSize_WithPositiveValue_ShouldSetBatchSize() { - CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(10) - .build(); - assertThat(customerSyncOptions.getBatchSize()).isEqualTo(10); - } - - @Test - void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final CustomerSyncOptions customerSyncOptionsWithZeroBatchSize = CustomerSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(0) - .build(); - assertThat(customerSyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(CustomerSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - - final CustomerSyncOptions customerSyncOptionsWithNegativeBatchSize = CustomerSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(-100) - .build(); - assertThat(customerSyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(CustomerSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } - - @Test - void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { - final CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder.of(CTP_CLIENT) - .build(); - assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNull(); - - final List> updateActions = singletonList(ChangeEmail.of("mail@mail.com")); - - final List> filteredList = - customerSyncOptions.applyBeforeUpdateCallback(updateActions, mock(CustomerDraft.class), - mock(Customer.class)); - - assertThat(filteredList).isSameAs(updateActions); - } - - @Test - void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { - final TriFunction>, CustomerDraft, Customer, List>> - beforeUpdateCallback = (updateActions, newCustomer, oldCustomer) -> null; - final CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder - .of(CTP_CLIENT) + assertThat(customerSyncOptions).isNotNull(); + } + + @Test + void batchSize_WithPositiveValue_ShouldSetBatchSize() { + CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); + assertThat(customerSyncOptions.getBatchSize()).isEqualTo(10); + } + + @Test + void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final CustomerSyncOptions customerSyncOptionsWithZeroBatchSize = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); + assertThat(customerSyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(CustomerSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + + final CustomerSyncOptions customerSyncOptionsWithNegativeBatchSize = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); + assertThat(customerSyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(CustomerSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + + @Test + void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNull(); + + final List> updateActions = + singletonList(ChangeEmail.of("mail@mail.com")); + + final List> filteredList = + customerSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CustomerDraft.class), mock(Customer.class)); + + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { + final TriFunction< + List>, CustomerDraft, Customer, List>> + beforeUpdateCallback = (updateActions, newCustomer, oldCustomer) -> null; + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT) .beforeUpdateCallback(beforeUpdateCallback) .build(); - assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeEmail.of("mail@mail.com")); - final List> filteredList = - customerSyncOptions.applyBeforeUpdateCallback(updateActions, mock(CustomerDraft.class), - mock(Customer.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - private interface MockTriFunction extends - TriFunction>, CustomerDraft, Customer, List>> { - } - - @Test - void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); - - final CustomerSyncOptions customerSyncOptions = - CustomerSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = emptyList(); - final List> filteredList = - customerSyncOptions.applyBeforeUpdateCallback(updateActions, mock(CustomerDraft.class), - mock(Customer.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - @Test - void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { - final TriFunction>, CustomerDraft, Customer, List>> - beforeUpdateCallback = (updateActions, newType, oldType) -> emptyList(); - - final CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder - .of(CTP_CLIENT) + assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + singletonList(ChangeEmail.of("mail@mail.com")); + final List> filteredList = + customerSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CustomerDraft.class), mock(Customer.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + private interface MockTriFunction + extends TriFunction< + List>, CustomerDraft, Customer, List>> {} + + @Test + void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); + + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT) .beforeUpdateCallback(beforeUpdateCallback) .build(); - assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeEmail.of("mail@mail.com")); - final List> filteredList = - customerSyncOptions - .applyBeforeUpdateCallback(updateActions, mock(CustomerDraft.class), mock(Customer.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { - final Function draftFunction = - customerDraft -> CustomerDraftBuilder.of(customerDraft) - .key(customerDraft.getKey() + "_filteredKey") - .build(); - - final CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - - assertThat(customerSyncOptions.getBeforeCreateCallback()).isNotNull(); - - final CustomerDraft resourceDraft = mock(CustomerDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); - when(resourceDraft.getDefaultBillingAddress()).thenReturn(null); - when(resourceDraft.getDefaultShippingAddress()).thenReturn(null); - - - final Optional filteredDraft = customerSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isNotEmpty(); - assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filteredKey"); - } - - @Test - void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder.of(CTP_CLIENT).build(); - assertThat(customerSyncOptions.getBeforeCreateCallback()).isNull(); - - final CustomerDraft resourceDraft = mock(CustomerDraft.class); - final Optional filteredDraft = customerSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).containsSame(resourceDraft); - } - - @Test - void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { - final Function draftFunction = customerDraft -> null; - final CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - assertThat(customerSyncOptions.getBeforeCreateCallback()).isNotNull(); - - final CustomerDraft resourceDraft = mock(CustomerDraft.class); - final Optional filteredDraft = customerSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isEmpty(); - } - - @Test - void cacheSize_WithPositiveValue_ShouldSetCacheSize() { - CustomerSyncOptions customerSyncOptions = CustomerSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(10) - .build(); - assertThat(customerSyncOptions.getCacheSize()).isEqualTo(10); - } - - @Test - void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final CustomerSyncOptions customerSyncOptionsWithZeroCacheSize = CustomerSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(0) - .build(); - assertThat(customerSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - final CustomerSyncOptions customerSyncOptionsWithNegativeCacheSize = CustomerSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(-100) - .build(); - assertThat(customerSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } + assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = emptyList(); + final List> filteredList = + customerSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CustomerDraft.class), mock(Customer.class)); + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { + final TriFunction< + List>, CustomerDraft, Customer, List>> + beforeUpdateCallback = (updateActions, newType, oldType) -> emptyList(); + + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + assertThat(customerSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + singletonList(ChangeEmail.of("mail@mail.com")); + final List> filteredList = + customerSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CustomerDraft.class), mock(Customer.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { + final Function draftFunction = + customerDraft -> + CustomerDraftBuilder.of(customerDraft) + .key(customerDraft.getKey() + "_filteredKey") + .build(); + + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + + assertThat(customerSyncOptions.getBeforeCreateCallback()).isNotNull(); + + final CustomerDraft resourceDraft = mock(CustomerDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); + when(resourceDraft.getDefaultBillingAddress()).thenReturn(null); + when(resourceDraft.getDefaultShippingAddress()).thenReturn(null); + + final Optional filteredDraft = + customerSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isNotEmpty(); + assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filteredKey"); + } + + @Test + void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(customerSyncOptions.getBeforeCreateCallback()).isNull(); + + final CustomerDraft resourceDraft = mock(CustomerDraft.class); + final Optional filteredDraft = + customerSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).containsSame(resourceDraft); + } + + @Test + void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { + final Function draftFunction = customerDraft -> null; + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(customerSyncOptions.getBeforeCreateCallback()).isNotNull(); + + final CustomerDraft resourceDraft = mock(CustomerDraft.class); + final Optional filteredDraft = + customerSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isEmpty(); + } + + @Test + void cacheSize_WithPositiveValue_ShouldSetCacheSize() { + CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); + assertThat(customerSyncOptions.getCacheSize()).isEqualTo(10); + } + + @Test + void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final CustomerSyncOptions customerSyncOptionsWithZeroCacheSize = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); + assertThat(customerSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); + + final CustomerSyncOptions customerSyncOptionsWithNegativeCacheSize = + CustomerSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); + assertThat(customerSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/customers/CustomerSyncTest.java b/src/test/java/com/commercetools/sync/customers/CustomerSyncTest.java index 231bc423ae..df4045c553 100644 --- a/src/test/java/com/commercetools/sync/customers/CustomerSyncTest.java +++ b/src/test/java/com/commercetools/sync/customers/CustomerSyncTest.java @@ -1,31 +1,5 @@ package com.commercetools.sync.customers; -import com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics; -import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; -import com.commercetools.sync.commons.exceptions.SyncException; -import com.commercetools.sync.customers.helpers.CustomerSyncStatistics; -import com.commercetools.sync.services.CustomerGroupService; -import com.commercetools.sync.services.CustomerService; -import com.commercetools.sync.services.TypeService; -import com.commercetools.sync.services.impl.TypeServiceImpl; -import io.sphere.sdk.client.BadRequestException; -import io.sphere.sdk.client.ConcurrentModificationException; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.customers.Customer; -import io.sphere.sdk.customers.CustomerDraft; -import io.sphere.sdk.customers.CustomerDraftBuilder; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.models.SphereException; -import io.sphere.sdk.types.CustomFieldsDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletionException; - import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; import static com.commercetools.sync.customers.helpers.CustomerBatchValidator.CUSTOMER_DRAFT_EMAIL_NOT_SET; import static com.commercetools.sync.customers.helpers.CustomerBatchValidator.CUSTOMER_DRAFT_IS_NULL; @@ -55,517 +29,557 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics; +import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; +import com.commercetools.sync.commons.exceptions.SyncException; +import com.commercetools.sync.customers.helpers.CustomerSyncStatistics; +import com.commercetools.sync.services.CustomerGroupService; +import com.commercetools.sync.services.CustomerService; +import com.commercetools.sync.services.TypeService; +import com.commercetools.sync.services.impl.TypeServiceImpl; +import io.sphere.sdk.client.BadRequestException; +import io.sphere.sdk.client.ConcurrentModificationException; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.customers.Customer; +import io.sphere.sdk.customers.CustomerDraft; +import io.sphere.sdk.customers.CustomerDraftBuilder; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.models.SphereException; +import io.sphere.sdk.types.CustomFieldsDraft; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletionException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + public class CustomerSyncTest { - private CustomerSyncOptions syncOptions; - private List errorMessages; - private List exceptions; - - @BeforeEach - void setup() { - errorMessages = new ArrayList<>(); - exceptions = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); - - syncOptions = CustomerSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + private CustomerSyncOptions syncOptions; + private List errorMessages; + private List exceptions; + + @BeforeEach + void setup() { + errorMessages = new ArrayList<>(); + exceptions = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); + + syncOptions = + CustomerSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - } + } - @Test - void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - final CustomerSync customerSync = new CustomerSync(syncOptions); + @Test + void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + final CustomerSync customerSync = new CustomerSync(syncOptions); - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(null)) - .toCompletableFuture() - .join(); + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(null)).toCompletableFuture().join(); - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - //assertions - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo(CUSTOMER_DRAFT_IS_NULL); - } + // assertions + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo(CUSTOMER_DRAFT_IS_NULL); + } - @Test - void sync_WithoutKey_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - final CustomerSync customerSync = new CustomerSync(syncOptions); + @Test + void sync_WithoutKey_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + final CustomerSync customerSync = new CustomerSync(syncOptions); - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync .sync(singletonList(CustomerDraftBuilder.of("email", "pass").build())) .toCompletableFuture() .join(); - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - //assertions - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo(format(CUSTOMER_DRAFT_KEY_NOT_SET, "email")); - } + // assertions + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo(format(CUSTOMER_DRAFT_KEY_NOT_SET, "email")); + } - @Test - void sync_WithoutEmail_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - final CustomerSync customerSync = new CustomerSync(syncOptions); + @Test + void sync_WithoutEmail_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + final CustomerSync customerSync = new CustomerSync(syncOptions); - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync .sync(singletonList(CustomerDraftBuilder.of(" ", "pass").key("key").build())) .toCompletableFuture() .join(); - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - - //assertions - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, "key")); - } - - @Test - void sync_WithFailOnCachingKeysToIds_ShouldTriggerErrorCallbackAndReturnProperStats() { - // preparation - final TypeService typeService = spy(new TypeServiceImpl(syncOptions)); - when(typeService.cacheKeysToIds(anySet())) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - final CustomerSync customerSync = new CustomerSync(syncOptions, mock(CustomerService.class), - typeService, mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .customerGroup(ResourceIdentifier.ofKey("customerGroupKey")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) - .stores(asList(ResourceIdentifier.ofKey("storeKey1"), - ResourceIdentifier.ofKey("storeKey2"), - ResourceIdentifier.ofId("storeId3"))) - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) - .toCompletableFuture() - .join(); - - - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)).isEqualTo("Failed to build a cache of keys to ids."); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasCauseExactlyInstanceOf(CompletionException.class) - .hasRootCauseExactlyInstanceOf(SphereException.class); - } - - @Test - void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - final CustomerService mockCustomerService = mock(CustomerService.class); - - when(mockCustomerService.fetchMatchingCustomersByKeys(singleton("customer-key"))) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - final CustomerSync customerSync = new CustomerSync(syncOptions, mockCustomerService, - mock(TypeService.class), mock(CustomerGroupService.class)); - - // test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(CustomerDraftBuilder.of("email", "pass") - .key("customer-key") - .build())) - .toCompletableFuture() - .join(); - - - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo("Failed to fetch existing customers with keys: '[customer-key]'."); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasCauseExactlyInstanceOf(CompletionException.class) - .hasRootCauseExactlyInstanceOf(SphereException.class); - } - - @Test - void sync_WithNonExistingTypeReference_ShouldTriggerErrorCallbackAndReturnProperStats() { - // preparation - final TypeService mockTypeService = mock(TypeService.class); - when(mockTypeService.fetchCachedTypeId(anyString())).thenReturn(completedFuture(empty())); - when(mockTypeService.cacheKeysToIds(anySet())).thenReturn(completedFuture(emptyMap())); - - final CustomerService mockCustomerService = mock(CustomerService.class); - when(mockCustomerService.fetchMatchingCustomersByKeys(singleton("customerKey"))) - .thenReturn(completedFuture(new HashSet<>(singletonList(mock(Customer.class))))); - - final CustomerSync customerSync = new CustomerSync(syncOptions, mockCustomerService, - mockTypeService, mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - - final String expectedExceptionMessage = - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, customerDraft.getKey()); - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, "typeKey")); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .contains(expectedMessageWithCause); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasCauseExactlyInstanceOf(CompletionException.class) - .hasRootCauseExactlyInstanceOf(ReferenceResolutionException.class); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallbackAndIncrementCreated() { - // preparation - final CustomerService mockCustomerService = mock(CustomerService.class); - final Customer mockCustomer = mock(Customer.class); - when(mockCustomerService.fetchMatchingCustomersByKeys(singleton("customerKey"))) - .thenReturn(completedFuture(new HashSet<>(singletonList(mockCustomer)))); - - when(mockCustomerService.createCustomer(any())) - .thenReturn(completedFuture(Optional.of(mockCustomer))); - - final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); - final CustomerSync customerSync = new CustomerSync(spyCustomerSyncOptions, mockCustomerService, - mock(TypeService.class), mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 1, 0, 0); - - verify(spyCustomerSyncOptions).applyBeforeCreateCallback(customerDraft); - verify(spyCustomerSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - @Test - void sync_FailedOnCreation_ShouldCallBeforeCreateCallbackAndIncrementFailed() { - // preparation - final CustomerService mockCustomerService = mock(CustomerService.class); - final Customer mockCustomer = mock(Customer.class); - when(mockCustomerService.fetchMatchingCustomersByKeys(singleton("customerKey"))) - .thenReturn(completedFuture(new HashSet<>(singletonList(mockCustomer)))); - - // simulate an error during create, service will return an empty optional. - when(mockCustomerService.createCustomer(any())) - .thenReturn(completedFuture(Optional.empty())); - - final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); - final CustomerSync customerSync = new CustomerSync(spyCustomerSyncOptions, mockCustomerService, - mock(TypeService.class), mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - - verify(spyCustomerSyncOptions).applyBeforeCreateCallback(customerDraft); - verify(spyCustomerSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - @Test - void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { - // preparation - final CustomerService mockCustomerService = mock(CustomerService.class); - final Customer mockCustomer = mock(Customer.class); - when(mockCustomer.getKey()).thenReturn("customerKey"); - - when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockCustomer))); - - when(mockCustomerService.updateCustomer(any(), anyList())) - .thenReturn(completedFuture(mockCustomer)); - - final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); - final CustomerSync customerSync = new CustomerSync(spyCustomerSyncOptions, mockCustomerService, - mock(TypeService.class), mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 1, 0); - - verify(spyCustomerSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spyCustomerSyncOptions, never()).applyBeforeCreateCallback(customerDraft); - } - - @Test - void sync_WithoutUpdateActions_ShouldNotIncrementUpdated() { - // preparation - final CustomerService mockCustomerService = mock(CustomerService.class); - final Customer mockCustomer = mock(Customer.class); - when(mockCustomer.getKey()).thenReturn("customerKey"); - when(mockCustomer.getEmail()).thenReturn("email"); - - when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockCustomer))); - - final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); - final CustomerSync customerSync = new CustomerSync(spyCustomerSyncOptions, mockCustomerService, - mock(TypeService.class), mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 0); - - verify(spyCustomerSyncOptions).applyBeforeUpdateCallback(emptyList(), customerDraft, mockCustomer); - verify(spyCustomerSyncOptions, never()).applyBeforeCreateCallback(customerDraft); - } - - @Test - void sync_WithBadRequestException_ShouldFailToUpdateAndIncreaseFailedCounter() { - // preparation - final CustomerService mockCustomerService = mock(CustomerService.class); - final Customer mockCustomer = mock(Customer.class); - when(mockCustomer.getKey()).thenReturn("customerKey"); - - when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockCustomer))); - - when(mockCustomerService.updateCustomer(any(), anyList())) - .thenReturn(exceptionallyCompletedFuture(new BadRequestException("Invalid request"))); - - final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); - final CustomerSync customerSync = new CustomerSync(spyCustomerSyncOptions, mockCustomerService, - mock(TypeService.class), mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .contains("Invalid request"); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasRootCauseExactlyInstanceOf(BadRequestException.class); - } - - @Test - void sync_WithConcurrentModificationException_ShouldRetryToUpdateNewCustomerWithSuccess() { - // preparation - final CustomerService mockCustomerService = mock(CustomerService.class); - final Customer mockCustomer = mock(Customer.class); - when(mockCustomer.getKey()).thenReturn("customerKey"); - - when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockCustomer))); - - when(mockCustomerService.updateCustomer(any(), anyList())) - .thenReturn(exceptionallyCompletedFuture(new SphereException(new ConcurrentModificationException()))) - .thenReturn(completedFuture(mockCustomer)); - - when(mockCustomerService.fetchCustomerByKey("customerKey")) - .thenReturn(completedFuture(Optional.of(mockCustomer))); - - final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); - final CustomerSync customerSync = new CustomerSync(spyCustomerSyncOptions, mockCustomerService, - mock(TypeService.class), mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 1, 0); - } - - @Test - void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { - // preparation - final CustomerService mockCustomerService = mock(CustomerService.class); - final Customer mockCustomer = mock(Customer.class); - when(mockCustomer.getKey()).thenReturn("customerKey"); - - when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockCustomer))); - - when(mockCustomerService.updateCustomer(any(), anyList())) - .thenReturn(exceptionallyCompletedFuture(new SphereException(new ConcurrentModificationException()))) - .thenReturn(completedFuture(mockCustomer)); - - when(mockCustomerService.fetchCustomerByKey("customerKey")) - .thenReturn(exceptionallyCompletedFuture(new SphereException())); - - final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); - final CustomerSync customerSync = new CustomerSync(spyCustomerSyncOptions, mockCustomerService, - mock(TypeService.class), mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) - .toCompletableFuture() - .join(); + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, "key")); + } + + @Test + void sync_WithFailOnCachingKeysToIds_ShouldTriggerErrorCallbackAndReturnProperStats() { + // preparation + final TypeService typeService = spy(new TypeServiceImpl(syncOptions)); + when(typeService.cacheKeysToIds(anySet())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final CustomerSync customerSync = + new CustomerSync( + syncOptions, + mock(CustomerService.class), + typeService, + mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("customerKey") + .customerGroup(ResourceIdentifier.ofKey("customerGroupKey")) + .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) + .stores( + asList( + ResourceIdentifier.ofKey("storeKey1"), + ResourceIdentifier.ofKey("storeKey2"), + ResourceIdentifier.ofId("storeId3"))) + .build(); - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .contains("Failed to fetch from CTP while retrying after concurrency modification."); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasRootCauseExactlyInstanceOf(SphereException.class); - } - - @Test - void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { - // preparation - final CustomerService mockCustomerService = mock(CustomerService.class); - final Customer mockCustomer = mock(Customer.class); - when(mockCustomer.getKey()).thenReturn("customerKey"); - - when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockCustomer))); - - when(mockCustomerService.updateCustomer(any(), anyList())) - .thenReturn(exceptionallyCompletedFuture(new SphereException(new ConcurrentModificationException()))) - .thenReturn(completedFuture(mockCustomer)); - - when(mockCustomerService.fetchCustomerByKey("customerKey")) - .thenReturn(completedFuture(Optional.empty())); - - final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); - final CustomerSync customerSync = new CustomerSync(spyCustomerSyncOptions, mockCustomerService, - mock(TypeService.class), mock(CustomerGroupService.class)); - - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .build(); - - //test - final CustomerSyncStatistics customerSyncStatistics = customerSync - .sync(singletonList(customerDraft)) + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo("Failed to build a cache of keys to ids."); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasCauseExactlyInstanceOf(CompletionException.class) + .hasRootCauseExactlyInstanceOf(SphereException.class); + } + + @Test + void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + final CustomerService mockCustomerService = mock(CustomerService.class); + + when(mockCustomerService.fetchMatchingCustomersByKeys(singleton("customer-key"))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final CustomerSync customerSync = + new CustomerSync( + syncOptions, + mockCustomerService, + mock(TypeService.class), + mock(CustomerGroupService.class)); + + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync + .sync( + singletonList(CustomerDraftBuilder.of("email", "pass").key("customer-key").build())) .toCompletableFuture() .join(); - // assertions - AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .contains("Not found when attempting to fetch while retrying after concurrency modification."); + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo("Failed to fetch existing customers with keys: '[customer-key]'."); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasCauseExactlyInstanceOf(CompletionException.class) + .hasRootCauseExactlyInstanceOf(SphereException.class); + } + + @Test + void sync_WithNonExistingTypeReference_ShouldTriggerErrorCallbackAndReturnProperStats() { + // preparation + final TypeService mockTypeService = mock(TypeService.class); + when(mockTypeService.fetchCachedTypeId(anyString())).thenReturn(completedFuture(empty())); + when(mockTypeService.cacheKeysToIds(anySet())).thenReturn(completedFuture(emptyMap())); + + final CustomerService mockCustomerService = mock(CustomerService.class); + when(mockCustomerService.fetchMatchingCustomersByKeys(singleton("customerKey"))) + .thenReturn(completedFuture(new HashSet<>(singletonList(mock(Customer.class))))); + + final CustomerSync customerSync = + new CustomerSync( + syncOptions, mockCustomerService, mockTypeService, mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("customerKey") + .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) + .build(); - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasNoCause(); - } + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + + final String expectedExceptionMessage = + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, customerDraft.getKey()); + final String expectedMessageWithCause = + format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, "typeKey")); + + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .contains(expectedMessageWithCause); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasCauseExactlyInstanceOf(CompletionException.class) + .hasRootCauseExactlyInstanceOf(ReferenceResolutionException.class); + } + + @Test + void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallbackAndIncrementCreated() { + // preparation + final CustomerService mockCustomerService = mock(CustomerService.class); + final Customer mockCustomer = mock(Customer.class); + when(mockCustomerService.fetchMatchingCustomersByKeys(singleton("customerKey"))) + .thenReturn(completedFuture(new HashSet<>(singletonList(mockCustomer)))); + + when(mockCustomerService.createCustomer(any())) + .thenReturn(completedFuture(Optional.of(mockCustomer))); + + final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); + final CustomerSync customerSync = + new CustomerSync( + spyCustomerSyncOptions, + mockCustomerService, + mock(TypeService.class), + mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("customerKey").build(); + + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 1, 0, 0); + + verify(spyCustomerSyncOptions).applyBeforeCreateCallback(customerDraft); + verify(spyCustomerSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void sync_FailedOnCreation_ShouldCallBeforeCreateCallbackAndIncrementFailed() { + // preparation + final CustomerService mockCustomerService = mock(CustomerService.class); + final Customer mockCustomer = mock(Customer.class); + when(mockCustomerService.fetchMatchingCustomersByKeys(singleton("customerKey"))) + .thenReturn(completedFuture(new HashSet<>(singletonList(mockCustomer)))); + + // simulate an error during create, service will return an empty optional. + when(mockCustomerService.createCustomer(any())).thenReturn(completedFuture(Optional.empty())); + + final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); + final CustomerSync customerSync = + new CustomerSync( + spyCustomerSyncOptions, + mockCustomerService, + mock(TypeService.class), + mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("customerKey").build(); + + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + + verify(spyCustomerSyncOptions).applyBeforeCreateCallback(customerDraft); + verify(spyCustomerSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { + // preparation + final CustomerService mockCustomerService = mock(CustomerService.class); + final Customer mockCustomer = mock(Customer.class); + when(mockCustomer.getKey()).thenReturn("customerKey"); + + when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockCustomer))); + + when(mockCustomerService.updateCustomer(any(), anyList())) + .thenReturn(completedFuture(mockCustomer)); + + final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); + final CustomerSync customerSync = + new CustomerSync( + spyCustomerSyncOptions, + mockCustomerService, + mock(TypeService.class), + mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("customerKey").build(); + + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 1, 0); + + verify(spyCustomerSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spyCustomerSyncOptions, never()).applyBeforeCreateCallback(customerDraft); + } + + @Test + void sync_WithoutUpdateActions_ShouldNotIncrementUpdated() { + // preparation + final CustomerService mockCustomerService = mock(CustomerService.class); + final Customer mockCustomer = mock(Customer.class); + when(mockCustomer.getKey()).thenReturn("customerKey"); + when(mockCustomer.getEmail()).thenReturn("email"); + + when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockCustomer))); + + final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); + final CustomerSync customerSync = + new CustomerSync( + spyCustomerSyncOptions, + mockCustomerService, + mock(TypeService.class), + mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("customerKey").build(); + + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 0); + + verify(spyCustomerSyncOptions) + .applyBeforeUpdateCallback(emptyList(), customerDraft, mockCustomer); + verify(spyCustomerSyncOptions, never()).applyBeforeCreateCallback(customerDraft); + } + + @Test + void sync_WithBadRequestException_ShouldFailToUpdateAndIncreaseFailedCounter() { + // preparation + final CustomerService mockCustomerService = mock(CustomerService.class); + final Customer mockCustomer = mock(Customer.class); + when(mockCustomer.getKey()).thenReturn("customerKey"); + + when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockCustomer))); + + when(mockCustomerService.updateCustomer(any(), anyList())) + .thenReturn(exceptionallyCompletedFuture(new BadRequestException("Invalid request"))); + + final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); + final CustomerSync customerSync = + new CustomerSync( + spyCustomerSyncOptions, + mockCustomerService, + mock(TypeService.class), + mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("customerKey").build(); + + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages).hasSize(1).singleElement(as(STRING)).contains("Invalid request"); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasRootCauseExactlyInstanceOf(BadRequestException.class); + } + + @Test + void sync_WithConcurrentModificationException_ShouldRetryToUpdateNewCustomerWithSuccess() { + // preparation + final CustomerService mockCustomerService = mock(CustomerService.class); + final Customer mockCustomer = mock(Customer.class); + when(mockCustomer.getKey()).thenReturn("customerKey"); + + when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockCustomer))); + + when(mockCustomerService.updateCustomer(any(), anyList())) + .thenReturn( + exceptionallyCompletedFuture( + new SphereException(new ConcurrentModificationException()))) + .thenReturn(completedFuture(mockCustomer)); + + when(mockCustomerService.fetchCustomerByKey("customerKey")) + .thenReturn(completedFuture(Optional.of(mockCustomer))); + + final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); + final CustomerSync customerSync = + new CustomerSync( + spyCustomerSyncOptions, + mockCustomerService, + mock(TypeService.class), + mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("customerKey").build(); + + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 1, 0); + } + + @Test + void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { + // preparation + final CustomerService mockCustomerService = mock(CustomerService.class); + final Customer mockCustomer = mock(Customer.class); + when(mockCustomer.getKey()).thenReturn("customerKey"); + + when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockCustomer))); + + when(mockCustomerService.updateCustomer(any(), anyList())) + .thenReturn( + exceptionallyCompletedFuture( + new SphereException(new ConcurrentModificationException()))) + .thenReturn(completedFuture(mockCustomer)); + + when(mockCustomerService.fetchCustomerByKey("customerKey")) + .thenReturn(exceptionallyCompletedFuture(new SphereException())); + + final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); + final CustomerSync customerSync = + new CustomerSync( + spyCustomerSyncOptions, + mockCustomerService, + mock(TypeService.class), + mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("customerKey").build(); + + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .contains("Failed to fetch from CTP while retrying after concurrency modification."); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasRootCauseExactlyInstanceOf(SphereException.class); + } + + @Test + void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { + // preparation + final CustomerService mockCustomerService = mock(CustomerService.class); + final Customer mockCustomer = mock(Customer.class); + when(mockCustomer.getKey()).thenReturn("customerKey"); + + when(mockCustomerService.fetchMatchingCustomersByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockCustomer))); + + when(mockCustomerService.updateCustomer(any(), anyList())) + .thenReturn( + exceptionallyCompletedFuture( + new SphereException(new ConcurrentModificationException()))) + .thenReturn(completedFuture(mockCustomer)); + + when(mockCustomerService.fetchCustomerByKey("customerKey")) + .thenReturn(completedFuture(Optional.empty())); + + final CustomerSyncOptions spyCustomerSyncOptions = spy(syncOptions); + final CustomerSync customerSync = + new CustomerSync( + spyCustomerSyncOptions, + mockCustomerService, + mock(TypeService.class), + mock(CustomerGroupService.class)); + + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("customerKey").build(); + + // test + final CustomerSyncStatistics customerSyncStatistics = + customerSync.sync(singletonList(customerDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(customerSyncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .contains( + "Not found when attempting to fetch while retrying after concurrency modification."); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasNoCause(); + } } diff --git a/src/test/java/com/commercetools/sync/customers/helpers/CustomerBatchValidatorTest.java b/src/test/java/com/commercetools/sync/customers/helpers/CustomerBatchValidatorTest.java index 5986b603ae..a87dd3bf27 100644 --- a/src/test/java/com/commercetools/sync/customers/helpers/CustomerBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/customers/helpers/CustomerBatchValidatorTest.java @@ -1,24 +1,5 @@ package com.commercetools.sync.customers.helpers; -import com.commercetools.sync.customers.CustomerSyncOptions; -import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; -import com.neovisionaries.i18n.CountryCode; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.customers.CustomerDraft; -import io.sphere.sdk.customers.CustomerDraftBuilder; -import io.sphere.sdk.models.Address; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.types.CustomFieldsDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - import static com.commercetools.sync.customers.helpers.CustomerBatchValidator.ADDRESSES_ARE_NULL; import static com.commercetools.sync.customers.helpers.CustomerBatchValidator.ADDRESSES_THAT_KEYS_NOT_SET; import static com.commercetools.sync.customers.helpers.CustomerBatchValidator.ADDRESSES_THAT_KEYS_NOT_UNIQUE; @@ -37,367 +18,395 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import com.commercetools.sync.customers.CustomerSyncOptions; +import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; +import com.neovisionaries.i18n.CountryCode; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.customers.CustomerDraft; +import io.sphere.sdk.customers.CustomerDraftBuilder; +import io.sphere.sdk.models.Address; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.types.CustomFieldsDraft; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class CustomerBatchValidatorTest { - private CustomerSyncOptions syncOptions; - private CustomerSyncStatistics syncStatistics; - private List errorCallBackMessages; + private CustomerSyncOptions syncOptions; + private CustomerSyncStatistics syncStatistics; + private List errorCallBackMessages; + + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); + + syncOptions = + CustomerSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallBackMessages.add(exception.getMessage())) + .build(); + syncStatistics = mock(CustomerSyncStatistics.class); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(emptyList()); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullCustomerDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(singletonList(null)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(CUSTOMER_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCustomerDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", "pass").build(); + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CUSTOMER_DRAFT_KEY_NOT_SET, customerDraft.getEmail())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCustomerDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", "pass").key(EMPTY).build(); + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CUSTOMER_DRAFT_KEY_NOT_SET, customerDraft.getEmail())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCustomerDraftWithNullEmail_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = CustomerDraftBuilder.of(null, "pass").key("key").build(); + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, customerDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCustomerDraftWithEmptyEmail_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = CustomerDraftBuilder.of(EMPTY, "pass").key("key").build(); + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, customerDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCustomerDraftWithNullPassword_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", null).key("key").build(); + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CUSTOMER_DRAFT_PASSWORD_NOT_SET, customerDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithCustomerDraftWithEmptyPassword_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", EMPTY).key("key").build(); + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CUSTOMER_DRAFT_PASSWORD_NOT_SET, customerDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithNullAddressList_ShouldNotHaveValidationError() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("key").addresses(null).build(); + + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).containsExactly(customerDraft); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyAddressList_ShouldNotHaveValidationError() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass").key("key").addresses(emptyList()).build(); + + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).containsExactly(customerDraft); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullAddresses_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("key") + .addresses(asList(null, Address.of(CountryCode.DE), null)) + .build(); + + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(format(ADDRESSES_ARE_NULL, "key", "[0, 2]")); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithBlankKeysInAddresses_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("key") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key1"), + Address.of(CountryCode.DE).withKey(null), + Address.of(CountryCode.US).withKey("address-key2"), + Address.of(CountryCode.AC).withKey(" "))) + .build(); + + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(ADDRESSES_THAT_KEYS_NOT_SET, "key", "[1, 3]")); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithDuplicateKeysInAddresses_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("key") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key1"), + Address.of(CountryCode.FR).withKey("address-key2"), + Address.of(CountryCode.DE).withKey("address-key3"), + Address.of(CountryCode.US).withKey("address-key1"), + Address.of(CountryCode.US).withKey("address-key3"))) + .build(); + + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(ADDRESSES_THAT_KEYS_NOT_UNIQUE, "key", "[0, 2, 3, 4]")); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithInvalidBillingAddresses_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("key") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key1"), + Address.of(CountryCode.FR).withKey("address-key2"), + Address.of(CountryCode.US).withKey("address-key3"))) + .billingAddresses(asList(null, -1, 1, 2)) + .build(); + + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(BILLING_ADDRESSES_ARE_NOT_VALID, "key", "[null, -1]")); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithInvalidShippingAddresses_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("key") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key1"), + Address.of(CountryCode.FR).withKey("address-key2"), + Address.of(CountryCode.US).withKey("address-key3"))) + .shippingAddresses(asList(1, 2, 3, 4, null)) + .build(); + + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(SHIPPING_ADDRESSES_ARE_NOT_VALID, "key", "[3, 4, null]")); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithAllValidAddresses_ShouldNotHaveValidationError() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("key") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key1"), + Address.of(CountryCode.FR).withKey("address-key2"), + Address.of(CountryCode.US).withKey("address-key3"))) + .defaultShippingAddress(0) + .shippingAddresses(asList(0, 1)) + .defaultBillingAddress(1) + .billingAddresses(asList(1, 2)) + .build(); + + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).containsExactly(customerDraft); + } + + @Test + void + validateAndCollectReferencedKeys_WithEmptyBillingAndShippingAddresses_ShouldNotHaveValidationError() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("key") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key1"), + Address.of(CountryCode.FR).withKey("address-key2"), + Address.of(CountryCode.US).withKey("address-key3"))) + .defaultShippingAddress(0) + .shippingAddresses(emptyList()) + .defaultBillingAddress(1) + .billingAddresses(emptyList()) + .build(); + + final Set validDrafts = getValidDrafts(singletonList(customerDraft)); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).containsExactly(customerDraft); + } + + @Test + void + validateAndCollectReferencedKeys_WithIndexesWithoutAddresses_ShouldHaveValidationErrorAndEmptyResult() { + final CustomerDraft customerDraft1 = + CustomerDraftBuilder.of("email", "pass") + .key("key") + .addresses(emptyList()) + .shippingAddresses(asList(0, 1)) + .build(); + + final CustomerDraft customerDraft2 = + CustomerDraftBuilder.of("email", "pass") + .key("key") + .addresses(null) + .billingAddresses(asList(0, 1)) + .build(); + + final Set validDrafts = getValidDrafts(asList(customerDraft1, customerDraft2)); + + assertThat(errorCallBackMessages).hasSize(2); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(SHIPPING_ADDRESSES_ARE_NOT_VALID, "key", "[0, 1]")); + assertThat(errorCallBackMessages.get(1)) + .isEqualTo(format(BILLING_ADDRESSES_ARE_NOT_VALID, "key", "[0, 1]")); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithMixedDrafts_ShouldReturnCorrectResults() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email", "pass") + .key("customerKey") + .customerGroup(ResourceIdentifier.ofKey("customerGroupKey")) + .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) + .stores( + asList( + ResourceIdentifier.ofKey("storeKey1"), + ResourceIdentifier.ofKey("storeKey2"), + ResourceIdentifier.ofId("storeId3"))) + .build(); + + final CustomerDraft customerDraft2 = + CustomerDraftBuilder.of("email", "pass") + .key("customerKey2") + .customerGroup(ResourceIdentifier.ofId("customerGroupId2")) + .custom(CustomFieldsDraft.ofTypeIdAndJson("typeId2", emptyMap())) + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key1"), + Address.of(CountryCode.FR).withKey("address-key2"), + Address.of(CountryCode.US).withKey("address-key3"))) + .build(); - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); + final CustomerDraft customerDraft3 = + CustomerDraftBuilder.of("email", "pass") + .key(" ") + .customerGroup(ResourceIdentifier.ofKey("customerGroupKey3")) + .build(); - syncOptions = CustomerSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorCallBackMessages.add(exception.getMessage())) + final CustomerDraft customerDraft4 = + CustomerDraftBuilder.of("", "pass") + .key("customerKey4") + .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeId4", emptyMap())) .build(); - syncStatistics = mock(CustomerSyncStatistics.class); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(emptyList()); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullCustomerDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(singletonList(null)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(CUSTOMER_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCustomerDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", "pass").build(); - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CUSTOMER_DRAFT_KEY_NOT_SET, customerDraft.getEmail())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCustomerDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", "pass") - .key(EMPTY) - .build(); - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CUSTOMER_DRAFT_KEY_NOT_SET, customerDraft.getEmail())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCustomerDraftWithNullEmail_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = CustomerDraftBuilder.of(null, "pass") - .key("key") - .build(); - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, customerDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCustomerDraftWithEmptyEmail_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = CustomerDraftBuilder.of(EMPTY, "pass") - .key("key") - .build(); - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, customerDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCustomerDraftWithNullPassword_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", null) - .key("key") - .build(); - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CUSTOMER_DRAFT_PASSWORD_NOT_SET, customerDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithCustomerDraftWithEmptyPassword_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", EMPTY) - .key("key") - .build(); - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CUSTOMER_DRAFT_PASSWORD_NOT_SET, customerDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullAddressList_ShouldNotHaveValidationError() { - final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(null) - .build(); - - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).containsExactly(customerDraft); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyAddressList_ShouldNotHaveValidationError() { - final CustomerDraft customerDraft = CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(emptyList()) - .build(); - - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).containsExactly(customerDraft); - } - - @Test - void validateAndCollectReferencedKeys_WithNullAddresses_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(asList(null, Address.of(CountryCode.DE), null)) - .build(); - - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(ADDRESSES_ARE_NULL, "key", "[0, 2]")); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithBlankKeysInAddresses_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key1"), - Address.of(CountryCode.DE).withKey(null), - Address.of(CountryCode.US).withKey("address-key2"), - Address.of(CountryCode.AC).withKey(" "))) - .build(); - - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(ADDRESSES_THAT_KEYS_NOT_SET, "key", "[1, 3]")); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithDuplicateKeysInAddresses_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key1"), - Address.of(CountryCode.FR).withKey("address-key2"), - Address.of(CountryCode.DE).withKey("address-key3"), - Address.of(CountryCode.US).withKey("address-key1"), - Address.of(CountryCode.US).withKey("address-key3"))) - .build(); - - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(ADDRESSES_THAT_KEYS_NOT_UNIQUE, "key", "[0, 2, 3, 4]")); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithInvalidBillingAddresses_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key1"), - Address.of(CountryCode.FR).withKey("address-key2"), - Address.of(CountryCode.US).withKey("address-key3"))) - .billingAddresses(asList(null, -1, 1, 2)) - .build(); - - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(BILLING_ADDRESSES_ARE_NOT_VALID, "key", "[null, -1]")); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithInvalidShippingAddresses_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key1"), - Address.of(CountryCode.FR).withKey("address-key2"), - Address.of(CountryCode.US).withKey("address-key3"))) - .shippingAddresses(asList(1, 2, 3, 4, null)) - .build(); - - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(SHIPPING_ADDRESSES_ARE_NOT_VALID, "key", "[3, 4, null]")); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithAllValidAddresses_ShouldNotHaveValidationError() { - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key1"), - Address.of(CountryCode.FR).withKey("address-key2"), - Address.of(CountryCode.US).withKey("address-key3"))) - .defaultShippingAddress(0) - .shippingAddresses(asList(0,1)) - .defaultBillingAddress(1) - .billingAddresses(asList(1,2)) - .build(); - - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).containsExactly(customerDraft); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyBillingAndShippingAddresses_ShouldNotHaveValidationError() { - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key1"), - Address.of(CountryCode.FR).withKey("address-key2"), - Address.of(CountryCode.US).withKey("address-key3"))) - .defaultShippingAddress(0) - .shippingAddresses(emptyList()) - .defaultBillingAddress(1) - .billingAddresses(emptyList()) - .build(); - - final Set validDrafts = getValidDrafts(singletonList(customerDraft)); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).containsExactly(customerDraft); - } - - @Test - void validateAndCollectReferencedKeys_WithIndexesWithoutAddresses_ShouldHaveValidationErrorAndEmptyResult() { - final CustomerDraft customerDraft1 = - CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(emptyList()) - .shippingAddresses(asList(0, 1)) - .build(); - - final CustomerDraft customerDraft2 = - CustomerDraftBuilder.of("email", "pass") - .key("key") - .addresses(null) - .billingAddresses(asList(0, 1)) - .build(); - - final Set validDrafts = getValidDrafts(asList(customerDraft1, customerDraft2)); - - assertThat(errorCallBackMessages).hasSize(2); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(SHIPPING_ADDRESSES_ARE_NOT_VALID, "key", "[0, 1]")); - assertThat(errorCallBackMessages.get(1)) - .isEqualTo(format(BILLING_ADDRESSES_ARE_NOT_VALID, "key", "[0, 1]")); - assertThat(validDrafts).isEmpty(); - - } - - @Test - void validateAndCollectReferencedKeys_WithMixedDrafts_ShouldReturnCorrectResults() { - final CustomerDraft customerDraft = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey") - .customerGroup(ResourceIdentifier.ofKey("customerGroupKey")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) - .stores(asList(ResourceIdentifier.ofKey("storeKey1"), - ResourceIdentifier.ofKey("storeKey2"), - ResourceIdentifier.ofId("storeId3"))) - .build(); - - final CustomerDraft customerDraft2 = - CustomerDraftBuilder.of("email", "pass") - .key("customerKey2") - .customerGroup(ResourceIdentifier.ofId("customerGroupId2")) - .custom(CustomFieldsDraft.ofTypeIdAndJson("typeId2", emptyMap())) - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key1"), - Address.of(CountryCode.FR).withKey("address-key2"), - Address.of(CountryCode.US).withKey("address-key3"))) - .build(); - - final CustomerDraft customerDraft3 = - CustomerDraftBuilder.of("email", "pass") - .key(" ") - .customerGroup(ResourceIdentifier.ofKey("customerGroupKey3")) - .build(); - - final CustomerDraft customerDraft4 = - CustomerDraftBuilder.of("", "pass") - .key("customerKey4") - .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeId4", emptyMap())) - .build(); - - final CustomerBatchValidator customerBatchValidator = new CustomerBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, CustomerBatchValidator.ReferencedKeys> pair - = customerBatchValidator.validateAndCollectReferencedKeys( + + final CustomerBatchValidator customerBatchValidator = + new CustomerBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, CustomerBatchValidator.ReferencedKeys> pair = + customerBatchValidator.validateAndCollectReferencedKeys( Arrays.asList(customerDraft, customerDraft2, customerDraft3, customerDraft4)); - assertThat(errorCallBackMessages).hasSize(2); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(CUSTOMER_DRAFT_KEY_NOT_SET, customerDraft3.getEmail())); - assertThat(errorCallBackMessages.get(1)) - .isEqualTo(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, customerDraft4.getKey())); - - assertThat(pair.getLeft()).containsExactlyInAnyOrder(customerDraft, customerDraft2); - assertThat(pair.getRight().getTypeKeys()).containsExactlyInAnyOrder("typeKey"); - assertThat(pair.getRight().getCustomerGroupKeys()).containsExactlyInAnyOrder("customerGroupKey"); - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List customerDrafts) { - final CustomerBatchValidator customerBatchValidator = - new CustomerBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, CustomerBatchValidator.ReferencedKeys> pair = - customerBatchValidator.validateAndCollectReferencedKeys(customerDrafts); - return pair.getLeft(); - } + assertThat(errorCallBackMessages).hasSize(2); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(CUSTOMER_DRAFT_KEY_NOT_SET, customerDraft3.getEmail())); + assertThat(errorCallBackMessages.get(1)) + .isEqualTo(format(CUSTOMER_DRAFT_EMAIL_NOT_SET, customerDraft4.getKey())); + + assertThat(pair.getLeft()).containsExactlyInAnyOrder(customerDraft, customerDraft2); + assertThat(pair.getRight().getTypeKeys()).containsExactlyInAnyOrder("typeKey"); + assertThat(pair.getRight().getCustomerGroupKeys()) + .containsExactlyInAnyOrder("customerGroupKey"); + } + + @Nonnull + private Set getValidDrafts(@Nonnull final List customerDrafts) { + final CustomerBatchValidator customerBatchValidator = + new CustomerBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, CustomerBatchValidator.ReferencedKeys> pair = + customerBatchValidator.validateAndCollectReferencedKeys(customerDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/customers/helpers/CustomerReferenceResolverTest.java b/src/test/java/com/commercetools/sync/customers/helpers/CustomerReferenceResolverTest.java index b3f0b9639c..ce734b17f0 100644 --- a/src/test/java/com/commercetools/sync/customers/helpers/CustomerReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/customers/helpers/CustomerReferenceResolverTest.java @@ -1,25 +1,5 @@ package com.commercetools.sync.customers.helpers; -import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; -import com.commercetools.sync.customers.CustomerSyncOptions; -import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; -import com.commercetools.sync.services.CustomerGroupService; -import com.commercetools.sync.services.TypeService; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.customers.CustomerDraft; -import io.sphere.sdk.customers.CustomerDraftBuilder; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.models.SphereException; -import io.sphere.sdk.types.CustomFieldsDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - import static com.commercetools.sync.commons.MockUtils.getMockTypeService; import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; @@ -40,294 +20,350 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; +import com.commercetools.sync.customers.CustomerSyncOptions; +import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; +import com.commercetools.sync.services.CustomerGroupService; +import com.commercetools.sync.services.TypeService; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.customers.CustomerDraft; +import io.sphere.sdk.customers.CustomerDraftBuilder; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.models.SphereException; +import io.sphere.sdk.types.CustomFieldsDraft; +import java.util.HashMap; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class CustomerReferenceResolverTest { - private CustomerReferenceResolver referenceResolver; - private TypeService typeService; - private CustomerGroupService customerGroupService; + private CustomerReferenceResolver referenceResolver; + private TypeService typeService; + private CustomerGroupService customerGroupService; - private static final String CUSTOMER_GROUP_KEY = "customer-group-key"; - private static final String CUSTOMER_GROUP_ID = UUID.randomUUID().toString(); + private static final String CUSTOMER_GROUP_KEY = "customer-group-key"; + private static final String CUSTOMER_GROUP_ID = UUID.randomUUID().toString(); - @BeforeEach - void setup() { - final CustomerSyncOptions syncOptions = CustomerSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); + @BeforeEach + void setup() { + final CustomerSyncOptions syncOptions = + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - typeService = getMockTypeService(); + typeService = getMockTypeService(); - customerGroupService = getMockCustomerGroupService( - getMockCustomerGroup(CUSTOMER_GROUP_ID, CUSTOMER_GROUP_KEY)); + customerGroupService = + getMockCustomerGroupService(getMockCustomerGroup(CUSTOMER_GROUP_ID, CUSTOMER_GROUP_KEY)); - referenceResolver = new CustomerReferenceResolver(syncOptions, typeService, customerGroupService); - } + referenceResolver = + new CustomerReferenceResolver(syncOptions, typeService, customerGroupService); + } - @Test - void resolveReferences_WithoutReferences_ShouldNotResolveReferences() { - final CustomerDraft customerDraft = CustomerDraftBuilder - .of("email@example.com", "secret123") - .build(); + @Test + void resolveReferences_WithoutReferences_ShouldNotResolveReferences() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email@example.com", "secret123").build(); - final CustomerDraft resolvedDraft = referenceResolver - .resolveReferences(customerDraft) - .toCompletableFuture() - .join(); + final CustomerDraft resolvedDraft = + referenceResolver.resolveReferences(customerDraft).toCompletableFuture().join(); - assertThat(resolvedDraft).isEqualTo(customerDraft); - } + assertThat(resolvedDraft).isEqualTo(customerDraft); + } - @Test - void resolveCustomTypeReference_WithNullKeyOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { - final CustomFieldsDraft newCustomFieldsDraft = mock(CustomFieldsDraft.class); - when(newCustomFieldsDraft.getType()).thenReturn(ResourceIdentifier.ofId(null)); + @Test + void + resolveCustomTypeReference_WithNullKeyOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { + final CustomFieldsDraft newCustomFieldsDraft = mock(CustomFieldsDraft.class); + when(newCustomFieldsDraft.getType()).thenReturn(ResourceIdentifier.ofId(null)); - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .key("customer-key") .custom(newCustomFieldsDraft); - assertThat(referenceResolver.resolveCustomTypeReference(customerDraftBuilder).toCompletableFuture()) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on CustomerDraft with " - + "key:'customer-key'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + assertThat( + referenceResolver + .resolveCustomTypeReference(customerDraftBuilder) + .toCompletableFuture()) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on CustomerDraft with " + + "key:'customer-key'. Reason: %s", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void + resolveCustomTypeReference_WithEmptyKeyOnCustomTypeReference_ShouldNotResolveCustomTypeReference() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .key("customer-key") .custom(CustomFieldsDraft.ofTypeKeyAndJson("", emptyMap())); - assertThat(referenceResolver.resolveCustomTypeReference(customerDraftBuilder).toCompletableFuture()) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on CustomerDraft with " - + "key:'customer-key'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { - final String customTypeKey = "nonExistingKey"; - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + assertThat( + referenceResolver + .resolveCustomTypeReference(customerDraftBuilder) + .toCompletableFuture()) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on CustomerDraft with " + + "key:'customer-key'. Reason: %s", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { + final String customTypeKey = "nonExistingKey"; + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .key("customer-key") .custom(CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, emptyMap())); - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(completedFuture(Optional.empty())); - - final String expectedExceptionMessage = - format(FAILED_TO_RESOLVE_CUSTOM_TYPE, customerDraftBuilder.getKey()); - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); - assertThat(referenceResolver.resolveCustomTypeReference(customerDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveReferences_WithAllValidReferences_ShouldResolveReferences() { - final CustomerDraft customerDraft = CustomerDraftBuilder - .of("email@example.com", "secret123") + when(typeService.fetchCachedTypeId(anyString())).thenReturn(completedFuture(Optional.empty())); + + final String expectedExceptionMessage = + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, customerDraftBuilder.getKey()); + final String expectedMessageWithCause = + format( + "%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); + assertThat(referenceResolver.resolveCustomTypeReference(customerDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void resolveReferences_WithAllValidReferences_ShouldResolveReferences() { + final CustomerDraft customerDraft = + CustomerDraftBuilder.of("email@example.com", "secret123") .key("customer-key") .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) .build(); - final CustomerDraft resolvedDraft = referenceResolver - .resolveReferences(customerDraft) - .toCompletableFuture() - .join(); + final CustomerDraft resolvedDraft = + referenceResolver.resolveReferences(customerDraft).toCompletableFuture().join(); - final CustomerDraft expectedDraft = CustomerDraftBuilder - .of(customerDraft) + final CustomerDraft expectedDraft = + CustomerDraftBuilder.of(customerDraft) .custom(CustomFieldsDraft.ofTypeIdAndJson("typeId", new HashMap<>())) .build(); - assertThat(resolvedDraft).isEqualTo(expectedDraft); - } + assertThat(resolvedDraft).isEqualTo(expectedDraft); + } - @Test - void resolveCustomerGroupReference_WithKeys_ShouldResolveReference() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + @Test + void resolveCustomerGroupReference_WithKeys_ShouldResolveReference() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .customerGroup(ResourceIdentifier.ofKey("customer-group-key")); - final CustomerDraftBuilder resolvedDraft = referenceResolver.resolveCustomerGroupReference(customerDraftBuilder) - .toCompletableFuture().join(); + final CustomerDraftBuilder resolvedDraft = + referenceResolver + .resolveCustomerGroupReference(customerDraftBuilder) + .toCompletableFuture() + .join(); - assertThat(resolvedDraft.getCustomerGroup()).isNotNull(); - assertThat(resolvedDraft.getCustomerGroup().getId()).isEqualTo(CUSTOMER_GROUP_ID); - } + assertThat(resolvedDraft.getCustomerGroup()).isNotNull(); + assertThat(resolvedDraft.getCustomerGroup().getId()).isEqualTo(CUSTOMER_GROUP_ID); + } - @Test - void resolveCustomerGroupReference_WithNonExistentCustomerGroup_ShouldNotResolveReference() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + @Test + void resolveCustomerGroupReference_WithNonExistentCustomerGroup_ShouldNotResolveReference() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .customerGroup(ResourceIdentifier.ofKey("anyKey")) .key("dummyKey"); - when(customerGroupService.fetchCachedCustomerGroupId(anyString())) - .thenReturn(completedFuture(Optional.empty())); - - final String expectedMessageWithCause = format(FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, - "dummyKey", format(CUSTOMER_GROUP_DOES_NOT_EXIST, "anyKey")); - - assertThat(referenceResolver.resolveCustomerGroupReference(customerDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveCustomerGroupReference_WithNullKeyOnCustomerGroupReference_ShouldNotResolveReference() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + when(customerGroupService.fetchCachedCustomerGroupId(anyString())) + .thenReturn(completedFuture(Optional.empty())); + + final String expectedMessageWithCause = + format( + FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, + "dummyKey", + format(CUSTOMER_GROUP_DOES_NOT_EXIST, "anyKey")); + + assertThat(referenceResolver.resolveCustomerGroupReference(customerDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void + resolveCustomerGroupReference_WithNullKeyOnCustomerGroupReference_ShouldNotResolveReference() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .customerGroup(ResourceIdentifier.ofKey(null)) .key("dummyKey"); - assertThat(referenceResolver.resolveCustomerGroupReference(customerDraftBuilder).toCompletableFuture()) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, "dummyKey", + assertThat( + referenceResolver + .resolveCustomerGroupReference(customerDraftBuilder) + .toCompletableFuture()) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, + "dummyKey", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } + } - @Test - void resolveCustomerGroupReference_WithEmptyKeyOnCustomerGroupReference_ShouldNotResolveReference() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + @Test + void + resolveCustomerGroupReference_WithEmptyKeyOnCustomerGroupReference_ShouldNotResolveReference() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .customerGroup(ResourceIdentifier.ofKey("")) .key("dummyKey"); - assertThat(referenceResolver.resolveCustomerGroupReference(customerDraftBuilder).toCompletableFuture()) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, "dummyKey", + assertThat( + referenceResolver + .resolveCustomerGroupReference(customerDraftBuilder) + .toCompletableFuture()) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_CUSTOMER_GROUP_REFERENCE, + "dummyKey", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } + } - @Test - void resolveCustomerGroupReference_WithExceptionOnCustomerGroupFetch_ShouldNotResolveReference() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + @Test + void resolveCustomerGroupReference_WithExceptionOnCustomerGroupFetch_ShouldNotResolveReference() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .customerGroup(ResourceIdentifier.ofKey("anyKey")) .key("dummyKey"); - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(customerGroupService.fetchCachedCustomerGroupId(anyString())).thenReturn(futureThrowingSphereException); - - assertThat(referenceResolver.resolveCustomerGroupReference(customerDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveCustomerGroupReference_WithIdOnCustomerGroupReference_ShouldNotResolveReference() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(customerGroupService.fetchCachedCustomerGroupId(anyString())) + .thenReturn(futureThrowingSphereException); + + assertThat(referenceResolver.resolveCustomerGroupReference(customerDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveCustomerGroupReference_WithIdOnCustomerGroupReference_ShouldNotResolveReference() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .customerGroup(ResourceIdentifier.ofId("existingId")) .key("dummyKey"); - assertThat(referenceResolver.resolveCustomerGroupReference(customerDraftBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> Objects.equals(resolvedDraft.getCustomerGroup(), - customerDraftBuilder.getCustomerGroup())); - } - - @Test - void resolveStoreReferences_WithNullStoreReferences_ShouldNotResolveReferences() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") - .stores(null) - .key("dummyKey"); - - final CustomerDraftBuilder resolvedDraft = referenceResolver - .resolveStoreReferences(customerDraftBuilder) - .toCompletableFuture() - .join(); - - assertThat(resolvedDraft.getStores()).isNull(); - } - - @Test - void resolveStoreReferences_WithEmptyStoreReferences_ShouldNotResolveReferences() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + assertThat( + referenceResolver + .resolveCustomerGroupReference(customerDraftBuilder) + .toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching( + resolvedDraft -> + Objects.equals( + resolvedDraft.getCustomerGroup(), customerDraftBuilder.getCustomerGroup())); + } + + @Test + void resolveStoreReferences_WithNullStoreReferences_ShouldNotResolveReferences() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123").stores(null).key("dummyKey"); + + final CustomerDraftBuilder resolvedDraft = + referenceResolver.resolveStoreReferences(customerDraftBuilder).toCompletableFuture().join(); + + assertThat(resolvedDraft.getStores()).isNull(); + } + + @Test + void resolveStoreReferences_WithEmptyStoreReferences_ShouldNotResolveReferences() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .stores(emptyList()) .key("dummyKey"); - final CustomerDraftBuilder resolvedDraft = referenceResolver - .resolveStoreReferences(customerDraftBuilder) - .toCompletableFuture() - .join(); - - assertThat(resolvedDraft.getStores()).isEmpty(); - } - - @Test - void resolveStoreReferences_WithValidStores_ShouldResolveReferences() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") - .stores(asList(ResourceIdentifier.ofKey("store-key1"), - ResourceIdentifier.ofKey("store-key2"), - ResourceIdentifier.ofId("store-id-3"))) + final CustomerDraftBuilder resolvedDraft = + referenceResolver.resolveStoreReferences(customerDraftBuilder).toCompletableFuture().join(); + + assertThat(resolvedDraft.getStores()).isEmpty(); + } + + @Test + void resolveStoreReferences_WithValidStores_ShouldResolveReferences() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") + .stores( + asList( + ResourceIdentifier.ofKey("store-key1"), + ResourceIdentifier.ofKey("store-key2"), + ResourceIdentifier.ofId("store-id-3"))) .key("dummyKey"); - final CustomerDraftBuilder resolvedDraft = referenceResolver - .resolveStoreReferences(customerDraftBuilder) - .toCompletableFuture() - .join(); + final CustomerDraftBuilder resolvedDraft = + referenceResolver.resolveStoreReferences(customerDraftBuilder).toCompletableFuture().join(); - assertThat(resolvedDraft.getStores()) - .containsExactly(ResourceIdentifier.ofKey("store-key1"), - ResourceIdentifier.ofKey("store-key2"), - ResourceIdentifier.ofId("store-id-3")); - } + assertThat(resolvedDraft.getStores()) + .containsExactly( + ResourceIdentifier.ofKey("store-key1"), + ResourceIdentifier.ofKey("store-key2"), + ResourceIdentifier.ofId("store-id-3")); + } - @Test - void resolveStoreReferences_WithNullStore_ShouldResolveReferences() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + @Test + void resolveStoreReferences_WithNullStore_ShouldResolveReferences() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .stores(singletonList(null)) .key("dummyKey"); - final CustomerDraftBuilder resolvedDraft = referenceResolver - .resolveStoreReferences(customerDraftBuilder) - .toCompletableFuture() - .join(); + final CustomerDraftBuilder resolvedDraft = + referenceResolver.resolveStoreReferences(customerDraftBuilder).toCompletableFuture().join(); - assertThat(resolvedDraft.getStores()).isEmpty(); - } + assertThat(resolvedDraft.getStores()).isEmpty(); + } - @Test - void resolveStoreReferences_WithNullKeyOnStoreReference_ShouldNotResolveReference() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + @Test + void resolveStoreReferences_WithNullKeyOnStoreReference_ShouldNotResolveReference() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .stores(singletonList(ResourceIdentifier.ofKey(null))) .key("dummyKey"); - assertThat(referenceResolver.resolveStoreReferences(customerDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_STORE_REFERENCE, "dummyKey", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } + assertThat(referenceResolver.resolveStoreReferences(customerDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_STORE_REFERENCE, + "dummyKey", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } - @Test - void resolveStoreReferences_WithBlankKeyOnStoreReference_ShouldNotResolveReference() { - final CustomerDraftBuilder customerDraftBuilder = CustomerDraftBuilder - .of("email@example.com", "secret123") + @Test + void resolveStoreReferences_WithBlankKeyOnStoreReference_ShouldNotResolveReference() { + final CustomerDraftBuilder customerDraftBuilder = + CustomerDraftBuilder.of("email@example.com", "secret123") .stores(singletonList(ResourceIdentifier.ofKey(" "))) .key("dummyKey"); - assertThat(referenceResolver.resolveStoreReferences(customerDraftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_STORE_REFERENCE, "dummyKey", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } + assertThat(referenceResolver.resolveStoreReferences(customerDraftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_STORE_REFERENCE, + "dummyKey", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } } diff --git a/src/test/java/com/commercetools/sync/customers/helpers/CustomerSyncStatisticsTest.java b/src/test/java/com/commercetools/sync/customers/helpers/CustomerSyncStatisticsTest.java index 2fcddafa2a..5489c5b1e7 100644 --- a/src/test/java/com/commercetools/sync/customers/helpers/CustomerSyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/customers/helpers/CustomerSyncStatisticsTest.java @@ -1,28 +1,28 @@ package com.commercetools.sync.customers.helpers; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomerSyncStatisticsTest { - private CustomerSyncStatistics customerSyncStatistics; + private CustomerSyncStatistics customerSyncStatistics; - @BeforeEach - void setup() { - customerSyncStatistics = new CustomerSyncStatistics(); - } + @BeforeEach + void setup() { + customerSyncStatistics = new CustomerSyncStatistics(); + } - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - customerSyncStatistics.incrementCreated(1); - customerSyncStatistics.incrementFailed(2); - customerSyncStatistics.incrementUpdated(3); - customerSyncStatistics.incrementProcessed(6); + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + customerSyncStatistics.incrementCreated(1); + customerSyncStatistics.incrementFailed(2); + customerSyncStatistics.incrementUpdated(3); + customerSyncStatistics.incrementProcessed(6); - assertThat(customerSyncStatistics.getReportMessage()) - .isEqualTo("Summary: 6 customers were processed in total " + assertThat(customerSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 6 customers were processed in total " + "(1 created, 3 updated and 2 failed to sync)."); - } + } } diff --git a/src/test/java/com/commercetools/sync/customers/utils/AddressUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/customers/utils/AddressUpdateActionUtilsTest.java index 483437615d..c7fb527276 100644 --- a/src/test/java/com/commercetools/sync/customers/utils/AddressUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/customers/utils/AddressUpdateActionUtilsTest.java @@ -1,27 +1,5 @@ package com.commercetools.sync.customers.utils; -import com.neovisionaries.i18n.CountryCode; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.customers.Customer; -import io.sphere.sdk.customers.CustomerDraft; -import io.sphere.sdk.customers.CustomerDraftBuilder; -import io.sphere.sdk.customers.commands.updateactions.AddAddress; -import io.sphere.sdk.customers.commands.updateactions.AddBillingAddressId; -import io.sphere.sdk.customers.commands.updateactions.AddShippingAddressId; -import io.sphere.sdk.customers.commands.updateactions.ChangeAddress; -import io.sphere.sdk.customers.commands.updateactions.RemoveAddress; -import io.sphere.sdk.customers.commands.updateactions.RemoveBillingAddressId; -import io.sphere.sdk.customers.commands.updateactions.RemoveShippingAddressId; -import io.sphere.sdk.customers.commands.updateactions.SetDefaultBillingAddress; -import io.sphere.sdk.customers.commands.updateactions.SetDefaultShippingAddress; -import io.sphere.sdk.models.Address; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildAddAddressUpdateActions; import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildAddBillingAddressUpdateActions; import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildAddShippingAddressUpdateActions; @@ -41,1577 +19,1727 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.neovisionaries.i18n.CountryCode; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.customers.Customer; +import io.sphere.sdk.customers.CustomerDraft; +import io.sphere.sdk.customers.CustomerDraftBuilder; +import io.sphere.sdk.customers.commands.updateactions.AddAddress; +import io.sphere.sdk.customers.commands.updateactions.AddBillingAddressId; +import io.sphere.sdk.customers.commands.updateactions.AddShippingAddressId; +import io.sphere.sdk.customers.commands.updateactions.ChangeAddress; +import io.sphere.sdk.customers.commands.updateactions.RemoveAddress; +import io.sphere.sdk.customers.commands.updateactions.RemoveBillingAddressId; +import io.sphere.sdk.customers.commands.updateactions.RemoveShippingAddressId; +import io.sphere.sdk.customers.commands.updateactions.SetDefaultBillingAddress; +import io.sphere.sdk.customers.commands.updateactions.SetDefaultShippingAddress; +import io.sphere.sdk.models.Address; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class AddressUpdateActionUtilsTest { - private Customer oldCustomer; - - @BeforeEach - void setup() { - oldCustomer = mock(Customer.class); - } - - @Test - void buildAllAddressUpdateActions_WithDifferentAddresses_ShouldReturnAddressAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2").withBuilding("no 1") - )); - when(oldCustomer.getDefaultShippingAddress()) - .thenReturn(Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2")); - when(oldCustomer.getDefaultBillingAddress()) - .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); - - final Address address2 = Address.of(CountryCode.DE) - .withKey("address-key-2") - .withBuilding("no 2") - .withId("address-id-new-2"); - - final Address address3 = Address.of(CountryCode.DE) - .withKey("address-key-3") - .withId("address-id-new-3"); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(address2, address3)) - .defaultShippingAddress(1) - .defaultBillingAddress(0) - .build(); - - final List> updateActions = - buildAllAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly( + private Customer oldCustomer; + + @BeforeEach + void setup() { + oldCustomer = mock(Customer.class); + } + + @Test + void buildAllAddressUpdateActions_WithDifferentAddresses_ShouldReturnAddressAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withId("address-id-1"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withId("address-id-2") + .withBuilding("no 1"))); + when(oldCustomer.getDefaultShippingAddress()) + .thenReturn(Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2")); + when(oldCustomer.getDefaultBillingAddress()) + .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); + + final Address address2 = + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 2") + .withId("address-id-new-2"); + + final Address address3 = + Address.of(CountryCode.DE).withKey("address-key-3").withId("address-id-new-3"); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(asList(address2, address3)) + .defaultShippingAddress(1) + .defaultBillingAddress(0) + .build(); + + final List> updateActions = + buildAllAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions) + .containsExactly( RemoveAddress.of("address-id-1"), ChangeAddress.of("address-id-2", address2), AddAddress.of(address3), SetDefaultShippingAddress.ofKey("address-key-3"), SetDefaultBillingAddress.ofKey("address-key-2")); - } - - @Test - void buildAllAddressUpdateActions_WithSameAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1").withPostalCode("123"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2").withBuilding("no 1") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1") - .withPostalCode("123") - .withId("address-id-new-1"), - Address.of(CountryCode.DE).withKey("address-key-2") - .withBuilding("no 1") - .withId("address-id-new-2") - )) - .build(); - - final List> updateActions = - buildAllAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAllAddressUpdateActions_withRemovedAddresses_ShouldFilterOutRemoveBillingAndShippingAddressIdActions() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1").withBuilding("no 1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .build(); - // test - final List> updateActions = - buildAllAddressUpdateActions(oldCustomer, newCustomer); - // assertions - assertThat(updateActions).containsExactly(RemoveAddress.of("address-id-1")); - } - - @Test - void buildRemoveAddressUpdateActions_WithoutOldAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(emptyList()); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(singletonList(Address.of(CountryCode.DE).withKey("address-key-1"))) - .build(); - - final List> updateActions = - buildRemoveAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildRemoveAddressUpdateActions_WithEmptyAddressKey_ShouldReturnAction() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1").withBuilding("no 1"))); - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(singletonList(Address.of(CountryCode.DE).withKey(""))) - .build(); - // test - final List> updateActions = - buildRemoveAddressUpdateActions(oldCustomer, newCustomer); - - // assertions - assertThat(updateActions).containsExactly(RemoveAddress.of("address-id-1")); - } - - @Test - void buildRemoveAddressUpdateActions_WithNullNewAddresses_ShouldReturnRemoveAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(null) - .build(); - - final List> updateActions = - buildRemoveAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly( - RemoveAddress.of("address-id-1"), - RemoveAddress.of("address-id-2")); - } + } + + @Test + void buildAllAddressUpdateActions_WithSameAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withId("address-id-1") + .withPostalCode("123"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withId("address-id-2") + .withBuilding("no 1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withPostalCode("123") + .withId("address-id-new-1"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 1") + .withId("address-id-new-2"))) + .build(); + + final List> updateActions = + buildAllAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildAllAddressUpdateActions_withRemovedAddresses_ShouldFilterOutRemoveBillingAndShippingAddressIdActions() { + // preparation + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withId("address-id-1") + .withBuilding("no 1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + final CustomerDraft newCustomer = CustomerDraftBuilder.of("email", "pass").build(); + // test + final List> updateActions = + buildAllAddressUpdateActions(oldCustomer, newCustomer); + // assertions + assertThat(updateActions).containsExactly(RemoveAddress.of("address-id-1")); + } + + @Test + void buildRemoveAddressUpdateActions_WithoutOldAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()).thenReturn(emptyList()); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(singletonList(Address.of(CountryCode.DE).withKey("address-key-1"))) + .build(); + + final List> updateActions = + buildRemoveAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildRemoveAddressUpdateActions_WithEmptyAddressKey_ShouldReturnAction() { + // preparation + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withId("address-id-1") + .withBuilding("no 1"))); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(singletonList(Address.of(CountryCode.DE).withKey(""))) + .build(); + // test + final List> updateActions = + buildRemoveAddressUpdateActions(oldCustomer, newCustomer); + + // assertions + assertThat(updateActions).containsExactly(RemoveAddress.of("address-id-1")); + } + + @Test + void buildRemoveAddressUpdateActions_WithNullNewAddresses_ShouldReturnRemoveAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); - @Test - void buildRemoveAddressUpdateActions_WithEmptyNewAddresses_ShouldReturnRemoveAddressActions() { + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").addresses(null).build(); - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); + final List> updateActions = + buildRemoveAddressUpdateActions(oldCustomer, newCustomer); - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(emptyList()) - .build(); + assertThat(updateActions) + .containsExactly(RemoveAddress.of("address-id-1"), RemoveAddress.of("address-id-2")); + } - final List> updateActions = - buildRemoveAddressUpdateActions(oldCustomer, newCustomer); + @Test + void buildRemoveAddressUpdateActions_WithEmptyNewAddresses_ShouldReturnRemoveAddressActions() { - assertThat(updateActions).containsExactly( - RemoveAddress.of("address-id-1"), - RemoveAddress.of("address-id-2")); - } - - @Test - void buildRemoveAddressUpdateActions_WithNullAddresses_ShouldReturnRemoveAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(null, null)) - .build(); - - final List> updateActions = - buildRemoveAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly( - RemoveAddress.of("address-id-1"), RemoveAddress.of("address-id-2")); - } - - @Test - void buildRemoveAddressUpdateActions_WithAddressesWithoutKeys_ShouldReturnRemoveAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withId("address-id-2") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) - .build(); - - final List> updateActions = - buildRemoveAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(RemoveAddress.of("address-id-2")); - } - - @Test - void buildChangeAddressUpdateActions_WithoutNewAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(null) - .build(); - - final List> updateActions = - buildChangeAddressUpdateActions(oldCustomer, newCustomer); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").addresses(emptyList()).build(); - assertThat(updateActions).isEmpty(); - } + final List> updateActions = + buildRemoveAddressUpdateActions(oldCustomer, newCustomer); - @Test - void buildChangeAddressUpdateActions_WithEmptyNewAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(emptyList()) - .build(); + assertThat(updateActions) + .containsExactly(RemoveAddress.of("address-id-1"), RemoveAddress.of("address-id-2")); + } - final List> updateActions = - buildChangeAddressUpdateActions(oldCustomer, newCustomer); + @Test + void buildRemoveAddressUpdateActions_WithNullAddresses_ShouldReturnRemoveAddressActions() { - assertThat(updateActions).isEmpty(); - } + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); - @Test - void buildChangeAddressUpdateActions_WithSameAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1").withPostalCode("123"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2").withBuilding("no 1") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1") - .withPostalCode("123") - .withId("address-id-new-1"), - Address.of(CountryCode.DE).withKey("address-key-2") - .withBuilding("no 1") - .withId("address-id-new-2") - )) - .build(); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").addresses(asList(null, null)).build(); - final List> updateActions = - buildChangeAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildChangeAddressUpdateActions_WithDifferentAddressesData_ShouldReturnChangeAddressActions() { + final List> updateActions = + buildRemoveAddressUpdateActions(oldCustomer, newCustomer); - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1").withPostalCode("321"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2").withBuilding("no 2") - )); + assertThat(updateActions) + .containsExactly(RemoveAddress.of("address-id-1"), RemoveAddress.of("address-id-2")); + } - final Address address1 = Address.of(CountryCode.DE) - .withKey("address-key-1") - .withPostalCode("123") - .withId("address-id-new-1"); + @Test + void buildRemoveAddressUpdateActions_WithAddressesWithoutKeys_ShouldReturnRemoveAddressActions() { - final Address address2 = Address.of(CountryCode.DE) - .withKey("address-key-2") - .withBuilding("no 2") - .withId("address-id-new-2"); + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withId("address-id-2"))); - final Address address3 = Address.of(CountryCode.DE) - .withKey("address-key-3") - .withBuilding("no 1") - .withId("address-id-new-3"); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(address1, address2, address3)) - .build(); - - final List> updateActions = - buildChangeAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(ChangeAddress.of("address-id-1", address1)); - } - - @Test - void buildChangeAddressUpdateActions_WithNullAddresses_ShouldNotReturnChangeAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(singletonList(null)) - .build(); - - final List> updateActions = - buildChangeAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildChangeAddressUpdateActions_WithAddressesWithoutKeys_ShouldNotReturnChangeAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withId("address-id-1"))) - .build(); - - final List> updateActions = - buildChangeAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddAddressUpdateActions_WithoutNewAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(null) - .build(); - - final List> updateActions = - buildAddAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddAddressUpdateActions_WithEmptyNewAddresses_ShouldNotReturnAction() { + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .build(); - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(emptyList()) - .build(); - - final List> updateActions = - buildAddAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddAddressUpdateActions_WithSameAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1").withPostalCode("123"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2").withBuilding("no 1") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1") - .withPostalCode("123") - .withId("address-id-new-1"), - Address.of(CountryCode.DE).withKey("address-key-2") - .withBuilding("no 1") - .withId("address-id-new-2") - )) - .build(); - - final List> updateActions = - buildAddAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddAddressUpdateActions_WithNewAddresses_ShouldReturnAddAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1").withPostalCode("123"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2").withBuilding("no 1") - )); - - final Address address1 = Address.of(CountryCode.DE) - .withKey("address-key-1") - .withPostalCode("123") - .withId("address-id-new-1"); - - final Address address2 = Address.of(CountryCode.DE) - .withKey("address-key-2") - .withBuilding("no 2") - .withId("address-id-new-2"); - - final Address address3 = Address.of(CountryCode.DE) - .withKey("address-key-3") - .withBuilding("no 1") - .withId("address-id-new-3"); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(address1, address2, address3)) - .build(); - - final List> updateActions = - buildAddAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(AddAddress.of(address3)); - } - - @Test - void buildAddAddressUpdateActions_WithNullAddresses_ShouldNotReturnAddAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(null, null)) - .build(); - - final List> updateActions = - buildAddAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddAddressUpdateActions_WithAddressesWithoutKeys_ShouldNotReturnAddAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withId("address-id-2") - )); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) - .build(); - - final List> updateActions = - buildAddAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddAddressUpdateActions_WithEmptyAddressKey_ShouldNotReturnAction() { - // preparation - final Address address1 = Address.of(CountryCode.DE) - .withKey("address-key-1") - .withBuilding("no 1") - .withId("address-id-1"); - when(oldCustomer.getAddresses()).thenReturn(Collections.singletonList(address1)); - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(singletonList(Address.of(CountryCode.DE).withKey(""))) - .build(); - // test - final List> updateActions = - buildAddAddressUpdateActions(oldCustomer, newCustomer); - - // assertions - assertThat(updateActions).isEmpty(); - } - - @Test - void buildSetDefaultShippingAddressUpdateAction_WithSameDefaultShippingAddress_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getDefaultShippingAddress()) - .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )) - .defaultShippingAddress(0) - .build(); - - final Optional> customerUpdateAction = - buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer); - - assertThat(customerUpdateAction).isNotPresent(); - } - - @Test - void buildSetDefaultShippingAddressUpdateAction_WithDifferentDefaultShippingAddress_ShouldReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getDefaultShippingAddress()) - .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )) - .defaultShippingAddress(1) - .build(); - - final Optional> customerUpdateAction = - buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer); - - assertThat(customerUpdateAction) - .isPresent() - .contains(SetDefaultShippingAddress.ofKey("address-key-2")); - } - - @Test - void buildSetDefaultShippingAddressUpdateAction_WithNoExistingShippingAddress_ShouldReturnAction() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(emptyList()); - when(oldCustomer.getDefaultShippingAddress()).thenReturn(null); - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )) - .defaultShippingAddress(1) - .build(); - // test - final Optional> customerUpdateAction = - buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer); - - // assertions - assertThat(customerUpdateAction) - .isPresent() - .contains(SetDefaultShippingAddress.ofKey("address-key-2")); - } - - @Test - void buildSetDefaultShippingAddressUpdateAction_WithoutDefaultShippingAddress_ShouldReturnUnsetAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getDefaultShippingAddress()) - .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )) - .defaultShippingAddress(null) - .build(); - - - final Optional> customerUpdateAction = - buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer); - - assertThat(customerUpdateAction) - .isPresent() - .contains(SetDefaultShippingAddress.ofKey(null)); - } - - @Test - void buildSetDefaultBillingAddressUpdateAction_WithSameDefaultBillingAddress_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getDefaultBillingAddress()) - .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )) - .defaultBillingAddress(0) - .build(); - - final Optional> customerUpdateAction = - buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer); - - assertThat(customerUpdateAction).isNotPresent(); - } - - @Test - void buildSetDefaultBillingAddressUpdateAction_WithDifferentDefaultBillingAddress_ShouldReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getDefaultBillingAddress()) - .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )) - .defaultBillingAddress(1) - .build(); - - final Optional> customerUpdateAction = - buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer); - - assertThat(customerUpdateAction) - .isPresent() - .contains(SetDefaultBillingAddress.ofKey("address-key-2")); - } - - @Test - void buildSetDefaultBillingAddressUpdateAction_WithoutDefaultBillingAddress_ShouldReturnUnsetAction() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getDefaultBillingAddress()) - .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )) - .defaultBillingAddress(null) - .build(); - - - final Optional> customerUpdateAction = - buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer); - - assertThat(customerUpdateAction) - .isPresent() - .contains(SetDefaultBillingAddress.ofKey(null)); - } - - @Test - void buildSetDefaultBillingAddressUpdateAction_WithNoExistingBillingAddress_ShouldReturnAction() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(emptyList()); - when(oldCustomer.getDefaultBillingAddress()).thenReturn(null); - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )) - .defaultBillingAddress(1) - .build(); - // test - final Optional> customerUpdateAction = - buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer); - - // assertions - assertThat(customerUpdateAction) - .isPresent() - .contains(SetDefaultBillingAddress.ofKey("address-key-2")); - } - - @Test - void buildAddShippingAddressUpdateActions_WithoutNewShippingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .shippingAddresses(null) - .build(); - - final List> updateActions = - buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddShippingAddressUpdateActions_WithEmptyShippingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .shippingAddresses(emptyList()) - .build(); - - final List> updateActions = - buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddShippingAddressUpdateActions_WithSameShippingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .shippingAddresses(singletonList(0)) - .build(); - - final List> updateActions = - buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddShippingAddressUpdateActions_WithEmptyAddressKey_ShouldReturnAction() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("").withId("address-id-1").withPostalCode("123"), - Address.of(CountryCode.DE).withKey("").withId("address-id-2").withBuilding("no 1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("").withId("address-id-1"))); - - final Address address1 = Address.of(CountryCode.DE) - .withKey("address-key-1") - .withPostalCode("123") - .withId("address-id-new-1"); - - final Address address2 = Address.of(CountryCode.DE) - .withKey("address-key-2") - .withBuilding("no 2") - .withId("address-id-new-2"); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(address1, address2)) - .shippingAddresses(singletonList(1)) - .build(); - // test - final List> updateActions = - buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); - - // assertions - assertThat(updateActions).containsExactly(AddShippingAddressId.ofKey("address-key-2")); - } - - @Test - void buildAddShippingAddressUpdateActions_WithNewShippingAddresses_ShouldReturnAddShippingAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1").withPostalCode("123"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2").withBuilding("no 1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final Address address1 = Address.of(CountryCode.DE) - .withKey("address-key-1") - .withPostalCode("123") - .withId("address-id-new-1"); - - final Address address2 = Address.of(CountryCode.DE) - .withKey("address-key-2") - .withBuilding("no 2") - .withId("address-id-new-2"); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(address1, address2)) - .shippingAddresses(asList(0, 1)) - .build(); - - final List> updateActions = - buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(AddShippingAddressId.ofKey("address-key-2")); - } - - @Test - void buildAddShippingAddressUpdateActions_WithShippingAddressIdLessThanZero_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .shippingAddresses(singletonList(-1)) - .build(); - - assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Addresses list does not contain an address at the index: %s", -1)); - } - - @Test - void buildAddShippingAddressUpdateActions_InOutBoundOfTheExistingIndexes_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .shippingAddresses(singletonList(2)) - .build(); - - assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Addresses list does not contain an address at the index: %s", 2)); - } - - @Test - void buildAddShippingAddressUpdateActions_WithAddressListSizeNull_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(null) - .shippingAddresses(singletonList(0)) - .build(); - - assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Addresses list does not contain an address at the index: %s", 0)); - } - - @Test - void buildAddShippingAddressUpdateActions_WithIndexBiggerThanListSize_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .shippingAddresses(singletonList(3)) - .build(); - - assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Addresses list does not contain an address at the index: %s", 3)); - } - - @Test - void buildAddShippingAddressUpdateActions_InWithNullAddress_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList(null)) - .shippingAddresses(singletonList(0)) - .build(); - - assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Address is null at the index: %s of the addresses list.", 0)); - } - - @Test - void buildAddShippingAddressUpdateActions_InWithBlankKey_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withId("address-id-1") - )) - .shippingAddresses(singletonList(0)) - .build(); - - assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Address does not have a key at the index: %s of the addresses list.", 0)); - } - - @Test - void buildRemoveShippingAddressUpdateActions_WithEmptyOldShippingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()).thenReturn(emptyList()); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .build(); - - final List> updateActions = - buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildRemoveShippingAddressUpdateActions_WithBlankOldKey_ShouldReturnAction() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()).thenReturn( - singletonList(Address.of(CountryCode.DE).withKey(""))); - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .build(); - // test - final List> updateActions = - buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); - - // assertions - assertThat(updateActions).containsExactly(RemoveShippingAddressId.of(null)); - } - - @Test - void buildRemoveShippingAddressUpdateActions_WitNullOldShippingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()).thenReturn(null); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .build(); - - final List> updateActions = - buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildRemoveShippingAddressUpdateActions_WithEmptyNewShippingAddresses_ShouldReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .shippingAddresses(emptyList()) - .build(); - - final List> updateActions = - buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(RemoveShippingAddressId.of("address-id-1")); - } - - @Test - void buildRemoveShippingAddressUpdateActions_WithNullNewShippingAddresses_ShouldReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .shippingAddresses(null) - .build(); - - final List> updateActions = - buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(RemoveShippingAddressId.of("address-id-1")); - } - - @Test - void buildRemoveShippingAddressUpdateActions_WithSameShippingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .shippingAddresses(singletonList(0)) - .build(); - - final List> updateActions = - buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildRemoveShippingAddressUpdateActions_WithOldShippingAddresses_ShouldReturnAction() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); - final Address address3 = Address.of(CountryCode.DE) - .withKey("address-key-3") - .withId("address-id-3"); - - final Address address4 = Address.of(CountryCode.DE) - .withKey("address-key-4") - .withId("address-id-new-4"); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(address3, address4)) - .shippingAddresses(singletonList(1)) - .build(); - // test - final List> updateActions = - buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); - // assertions - assertThat(updateActions).containsExactly(RemoveShippingAddressId.of("address-id-1")); - } - - @Test - void buildRemoveShippingAddressUpdateActions_WithLessShippingAddresses_ShouldReturnRemoveShippingAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getShippingAddresses()) - .thenReturn(asList( + final List> updateActions = + buildRemoveAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(RemoveAddress.of("address-id-2")); + } + + @Test + void buildChangeAddressUpdateActions_WithoutNewAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").addresses(null).build(); + + final List> updateActions = + buildChangeAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildChangeAddressUpdateActions_WithEmptyNewAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").addresses(emptyList()).build(); + + final List> updateActions = + buildChangeAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildChangeAddressUpdateActions_WithSameAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withId("address-id-1") + .withPostalCode("123"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withId("address-id-2") + .withBuilding("no 1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withPostalCode("123") + .withId("address-id-new-1"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 1") + .withId("address-id-new-2"))) + .build(); + + final List> updateActions = + buildChangeAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildChangeAddressUpdateActions_WithDifferentAddressesData_ShouldReturnChangeAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withId("address-id-1") + .withPostalCode("321"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withId("address-id-2") + .withBuilding("no 2"))); + + final Address address1 = + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withPostalCode("123") + .withId("address-id-new-1"); + + final Address address2 = + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 2") + .withId("address-id-new-2"); + + final Address address3 = + Address.of(CountryCode.DE) + .withKey("address-key-3") + .withBuilding("no 1") + .withId("address-id-new-3"); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(asList(address1, address2, address3)) + .build(); + + final List> updateActions = + buildChangeAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(ChangeAddress.of("address-id-1", address1)); + } + + @Test + void buildChangeAddressUpdateActions_WithNullAddresses_ShouldNotReturnChangeAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").addresses(singletonList(null)).build(); + + final List> updateActions = + buildChangeAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildChangeAddressUpdateActions_WithAddressesWithoutKeys_ShouldNotReturnChangeAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))) + .build(); + + final List> updateActions = + buildChangeAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddAddressUpdateActions_WithoutNewAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .shippingAddresses(singletonList(0)) - .build(); - - final List> updateActions = - buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(RemoveShippingAddressId.of("address-id-2")); - } - - @Test - void buildAddBillingAddressUpdateActions_WithoutNewBillingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .billingAddresses(null) - .build(); - - final List> updateActions = - buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddBillingAddressUpdateActions_WithEmptyBillingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .billingAddresses(emptyList()) - .build(); - - final List> updateActions = - buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddBillingAddressUpdateActions_WithSameBillingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .billingAddresses(singletonList(0)) - .build(); - - final List> updateActions = - buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAddBillingAddressUpdateActions_WithNewBillingAddresses_ShouldReturnAddBillingAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1").withPostalCode("123"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2").withBuilding("no 1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final Address address1 = Address.of(CountryCode.DE) - .withKey("address-key-1") - .withPostalCode("123") - .withId("address-id-new-1"); - - final Address address2 = Address.of(CountryCode.DE) - .withKey("address-key-2") - .withBuilding("no 2") - .withId("address-id-new-2"); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(address1, address2)) - .billingAddresses(asList(0, 1)) - .build(); - - final List> updateActions = - buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(AddBillingAddressId.ofKey("address-key-2")); - } - - @Test - void buildAddBillingAddressUpdateActions_WithEmptyAddressKey_ShouldReturnAction() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("").withId("address-id-1").withPostalCode("123"), - Address.of(CountryCode.DE).withKey("").withId("address-id-2").withBuilding("no 1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("").withId("address-id-1"))); - - final Address address1 = Address.of(CountryCode.DE) - .withKey("address-key-1") - .withPostalCode("123") - .withId("address-id-new-1"); - - final Address address2 = Address.of(CountryCode.DE) - .withKey("address-key-2") - .withBuilding("no 2") - .withId("address-id-new-2"); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(address1, address2)) - .billingAddresses(singletonList(1)) - .build(); - // test - final List> updateActions = - buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); - - // assertions - assertThat(updateActions).containsExactly(AddBillingAddressId.ofKey("address-key-2")); - } - - @Test - void buildAddBillingAddressUpdateActions_WithShippingAddressIdLessThanZero_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .billingAddresses(singletonList(-1)) - .build(); - - assertThatThrownBy(() -> buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Addresses list does not contain an address at the index: %s", -1)); - } - - @Test - void buildAddBillingAddressUpdateActions_InOutBoundOfTheExistingIndexes_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .billingAddresses(singletonList(2)) - .build(); - - assertThatThrownBy(() -> buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Addresses list does not contain an address at the index: %s", 2)); - } - - @Test - void buildAddBillingAddressUpdateActions_InWithNullAddress_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList(null)) - .billingAddresses(singletonList(0)) - .build(); - - assertThatThrownBy(() -> buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Address is null at the index: %s of the addresses list.", 0)); - } - - @Test - void buildAddBillingAddressUpdateActions_InWithBlankKey_ShouldThrowIllegalArgumentException() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withId("address-id-1") - )) - .billingAddresses(singletonList(0)) - .build(); - - assertThatThrownBy(() -> buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)) - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage(format("Address does not have a key at the index: %s of the addresses list.", 0)); - } - - - @Test - void buildRemoveBillingAddressUpdateActions_WithOldBillingAddresses_ShouldReturnAction() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); - - - final Address address3 = Address.of(CountryCode.DE) - .withKey("address-key-3") - .withId("address-id-3"); - - final Address address4 = Address.of(CountryCode.DE) - .withKey("address-key-4") - .withId("address-id-new-4"); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .addresses(asList(address3, address4)) - .billingAddresses(singletonList(1)) - .build(); - // test - final List> updateActions = - buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); - // assertions - assertThat(updateActions).containsExactly(RemoveBillingAddressId.of("address-id-1")); - } - - @Test - void buildRemoveBillingAddressUpdateActions_WithEmptyOldBillingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()).thenReturn(emptyList()); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .build(); - - final List> updateActions = - buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildRemoveBillingAddressUpdateActions_WitNullOldBillingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()).thenReturn(null); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .build(); - - final List> updateActions = - buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildRemoveBillingAddressUpdateActions_WithEmptyNewBillingAddresses_ShouldReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .billingAddresses(emptyList()) - .build(); - - final List> updateActions = - buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(RemoveBillingAddressId.of("address-id-1")); - } - - @Test - void buildRemoveBillingAddressUpdateActions_WithNullNewBillingAddresses_ShouldReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .billingAddresses(null) - .build(); - - final List> updateActions = - buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).containsExactly(RemoveBillingAddressId.of("address-id-1")); - } - - @Test - void buildRemoveBillingAddressUpdateActions_WithSameBillingAddresses_ShouldNotReturnAction() { - - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); - - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .billingAddresses(singletonList(0)) - .build(); - - final List> updateActions = - buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildRemoveBillingAddressUpdateActions_WithEmptyOldKey_ShouldNotReturnAction() { - // preparation - when(oldCustomer.getAddresses()).thenReturn(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )); - when(oldCustomer.getBillingAddresses()).thenReturn( - singletonList(Address.of(CountryCode.DE).withKey(""))); - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .build(); - // test - final List> updateActions = - buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); - - // assertions - assertThat(updateActions).containsExactly(RemoveBillingAddressId.of(null)); - } - - @Test - void buildRemoveBillingAddressUpdateActions_WithLessBillingAddresses_ShouldReturnRemoveBillingAddressActions() { - - when(oldCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), - Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2") - )); - when(oldCustomer.getBillingAddresses()) - .thenReturn(asList( + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").addresses(null).build(); + + final List> updateActions = + buildAddAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddAddressUpdateActions_WithEmptyNewAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").addresses(emptyList()).build(); + + final List> updateActions = + buildAddAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddAddressUpdateActions_WithSameAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withId("address-id-1") + .withPostalCode("123"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withId("address-id-2") + .withBuilding("no 1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withPostalCode("123") + .withId("address-id-new-1"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 1") + .withId("address-id-new-2"))) + .build(); + + final List> updateActions = + buildAddAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddAddressUpdateActions_WithNewAddresses_ShouldReturnAddAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withId("address-id-1") + .withPostalCode("123"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withId("address-id-2") + .withBuilding("no 1"))); + + final Address address1 = + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withPostalCode("123") + .withId("address-id-new-1"); + + final Address address2 = + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 2") + .withId("address-id-new-2"); + + final Address address3 = + Address.of(CountryCode.DE) + .withKey("address-key-3") + .withBuilding("no 1") + .withId("address-id-new-3"); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(asList(address1, address2, address3)) + .build(); + + final List> updateActions = + buildAddAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(AddAddress.of(address3)); + } + + @Test + void buildAddAddressUpdateActions_WithNullAddresses_ShouldNotReturnAddAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").addresses(asList(null, null)).build(); + + final List> updateActions = + buildAddAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddAddressUpdateActions_WithAddressesWithoutKeys_ShouldNotReturnAddAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withId("address-id-2"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .build(); + + final List> updateActions = + buildAddAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddAddressUpdateActions_WithEmptyAddressKey_ShouldNotReturnAction() { + // preparation + final Address address1 = + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withBuilding("no 1") + .withId("address-id-1"); + when(oldCustomer.getAddresses()).thenReturn(Collections.singletonList(address1)); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(singletonList(Address.of(CountryCode.DE).withKey(""))) + .build(); + // test + final List> updateActions = + buildAddAddressUpdateActions(oldCustomer, newCustomer); + + // assertions + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildSetDefaultShippingAddressUpdateAction_WithSameDefaultShippingAddress_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getDefaultShippingAddress()) + .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))) + .defaultShippingAddress(0) + .build(); + + final Optional> customerUpdateAction = + buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer); + + assertThat(customerUpdateAction).isNotPresent(); + } + + @Test + void + buildSetDefaultShippingAddressUpdateAction_WithDifferentDefaultShippingAddress_ShouldReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getDefaultShippingAddress()) + .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))) + .defaultShippingAddress(1) + .build(); + + final Optional> customerUpdateAction = + buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer); + + assertThat(customerUpdateAction) + .isPresent() + .contains(SetDefaultShippingAddress.ofKey("address-key-2")); + } + + @Test + void + buildSetDefaultShippingAddressUpdateAction_WithNoExistingShippingAddress_ShouldReturnAction() { + // preparation + when(oldCustomer.getAddresses()).thenReturn(emptyList()); + when(oldCustomer.getDefaultShippingAddress()).thenReturn(null); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))) + .defaultShippingAddress(1) + .build(); + // test + final Optional> customerUpdateAction = + buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer); + + // assertions + assertThat(customerUpdateAction) + .isPresent() + .contains(SetDefaultShippingAddress.ofKey("address-key-2")); + } + + @Test + void + buildSetDefaultShippingAddressUpdateAction_WithoutDefaultShippingAddress_ShouldReturnUnsetAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getDefaultShippingAddress()) + .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))) + .defaultShippingAddress(null) + .build(); + + final Optional> customerUpdateAction = + buildSetDefaultShippingAddressUpdateAction(oldCustomer, newCustomer); + + assertThat(customerUpdateAction).isPresent().contains(SetDefaultShippingAddress.ofKey(null)); + } + + @Test + void + buildSetDefaultBillingAddressUpdateAction_WithSameDefaultBillingAddress_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getDefaultBillingAddress()) + .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))) + .defaultBillingAddress(0) + .build(); + + final Optional> customerUpdateAction = + buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer); + + assertThat(customerUpdateAction).isNotPresent(); + } + + @Test + void + buildSetDefaultBillingAddressUpdateAction_WithDifferentDefaultBillingAddress_ShouldReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getDefaultBillingAddress()) + .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))) + .defaultBillingAddress(1) + .build(); + + final Optional> customerUpdateAction = + buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer); + + assertThat(customerUpdateAction) + .isPresent() + .contains(SetDefaultBillingAddress.ofKey("address-key-2")); + } + + @Test + void + buildSetDefaultBillingAddressUpdateAction_WithoutDefaultBillingAddress_ShouldReturnUnsetAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getDefaultBillingAddress()) + .thenReturn(Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1")); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))) + .defaultBillingAddress(null) + .build(); + + final Optional> customerUpdateAction = + buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer); + + assertThat(customerUpdateAction).isPresent().contains(SetDefaultBillingAddress.ofKey(null)); + } + + @Test + void buildSetDefaultBillingAddressUpdateAction_WithNoExistingBillingAddress_ShouldReturnAction() { + // preparation + when(oldCustomer.getAddresses()).thenReturn(emptyList()); + when(oldCustomer.getDefaultBillingAddress()).thenReturn(null); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))) + .defaultBillingAddress(1) + .build(); + // test + final Optional> customerUpdateAction = + buildSetDefaultBillingAddressUpdateAction(oldCustomer, newCustomer); + + // assertions + assertThat(customerUpdateAction) + .isPresent() + .contains(SetDefaultBillingAddress.ofKey("address-key-2")); + } + + @Test + void buildAddShippingAddressUpdateActions_WithoutNewShippingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").shippingAddresses(null).build(); + + final List> updateActions = + buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddShippingAddressUpdateActions_WithEmptyShippingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").shippingAddresses(emptyList()).build(); + + final List> updateActions = + buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddShippingAddressUpdateActions_WithSameShippingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .shippingAddresses(singletonList(0)) + .build(); + + final List> updateActions = + buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddShippingAddressUpdateActions_WithEmptyAddressKey_ShouldReturnAction() { + // preparation + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("").withId("address-id-1").withPostalCode("123"), + Address.of(CountryCode.DE) + .withKey("") + .withId("address-id-2") + .withBuilding("no 1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("").withId("address-id-1"))); + + final Address address1 = + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withPostalCode("123") + .withId("address-id-new-1"); + + final Address address2 = + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 2") + .withId("address-id-new-2"); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(asList(address1, address2)) + .shippingAddresses(singletonList(1)) + .build(); + // test + final List> updateActions = + buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); + + // assertions + assertThat(updateActions).containsExactly(AddShippingAddressId.ofKey("address-key-2")); + } + + @Test + void + buildAddShippingAddressUpdateActions_WithNewShippingAddresses_ShouldReturnAddShippingAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withId("address-id-1") + .withPostalCode("123"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withId("address-id-2") + .withBuilding("no 1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final Address address1 = + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withPostalCode("123") + .withId("address-id-new-1"); + + final Address address2 = + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 2") + .withId("address-id-new-2"); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(asList(address1, address2)) + .shippingAddresses(asList(0, 1)) + .build(); + + final List> updateActions = + buildAddShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(AddShippingAddressId.ofKey("address-key-2")); + } + + @Test + void + buildAddShippingAddressUpdateActions_WithShippingAddressIdLessThanZero_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .shippingAddresses(singletonList(-1)) + .build(); + + assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage(format("Addresses list does not contain an address at the index: %s", -1)); + } + + @Test + void + buildAddShippingAddressUpdateActions_InOutBoundOfTheExistingIndexes_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .shippingAddresses(singletonList(2)) + .build(); + + assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage(format("Addresses list does not contain an address at the index: %s", 2)); + } + + @Test + void + buildAddShippingAddressUpdateActions_WithAddressListSizeNull_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(null) + .shippingAddresses(singletonList(0)) + .build(); + + assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage(format("Addresses list does not contain an address at the index: %s", 0)); + } + + @Test + void + buildAddShippingAddressUpdateActions_WithIndexBiggerThanListSize_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .shippingAddresses(singletonList(3)) + .build(); + + assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage(format("Addresses list does not contain an address at the index: %s", 3)); + } + + @Test + void + buildAddShippingAddressUpdateActions_InWithNullAddress_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(singletonList(null)) + .shippingAddresses(singletonList(0)) + .build(); + + assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage(format("Address is null at the index: %s of the addresses list.", 0)); + } + + @Test + void buildAddShippingAddressUpdateActions_InWithBlankKey_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))) + .shippingAddresses(singletonList(0)) + .build(); + + assertThatThrownBy(() -> buildAddShippingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage( + format("Address does not have a key at the index: %s of the addresses list.", 0)); + } + + @Test + void + buildRemoveShippingAddressUpdateActions_WithEmptyOldShippingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()).thenReturn(emptyList()); + + final CustomerDraft newCustomer = CustomerDraftBuilder.of("email", "pass").build(); + + final List> updateActions = + buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildRemoveShippingAddressUpdateActions_WithBlankOldKey_ShouldReturnAction() { + // preparation + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withKey(""))); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .build(); + // test + final List> updateActions = + buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); + + // assertions + assertThat(updateActions).containsExactly(RemoveShippingAddressId.of(null)); + } + + @Test + void buildRemoveShippingAddressUpdateActions_WitNullOldShippingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()).thenReturn(null); + + final CustomerDraft newCustomer = CustomerDraftBuilder.of("email", "pass").build(); + + final List> updateActions = + buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildRemoveShippingAddressUpdateActions_WithEmptyNewShippingAddresses_ShouldReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .shippingAddresses(emptyList()) + .build(); + + final List> updateActions = + buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(RemoveShippingAddressId.of("address-id-1")); + } + + @Test + void buildRemoveShippingAddressUpdateActions_WithNullNewShippingAddresses_ShouldReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .shippingAddresses(null) + .build(); + + final List> updateActions = + buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(RemoveShippingAddressId.of("address-id-1")); + } + + @Test + void buildRemoveShippingAddressUpdateActions_WithSameShippingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .shippingAddresses(singletonList(0)) + .build(); + + final List> updateActions = + buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildRemoveShippingAddressUpdateActions_WithOldShippingAddresses_ShouldReturnAction() { + // preparation + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); + final Address address3 = + Address.of(CountryCode.DE).withKey("address-key-3").withId("address-id-3"); + + final Address address4 = + Address.of(CountryCode.DE).withKey("address-key-4").withId("address-id-new-4"); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(asList(address3, address4)) + .shippingAddresses(singletonList(1)) + .build(); + // test + final List> updateActions = + buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); + // assertions + assertThat(updateActions).containsExactly(RemoveShippingAddressId.of("address-id-1")); + } + + @Test + void + buildRemoveShippingAddressUpdateActions_WithLessShippingAddresses_ShouldReturnRemoveShippingAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getShippingAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .shippingAddresses(singletonList(0)) + .build(); + + final List> updateActions = + buildRemoveShippingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(RemoveShippingAddressId.of("address-id-2")); + } + + @Test + void buildAddBillingAddressUpdateActions_WithoutNewBillingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").billingAddresses(null).build(); + + final List> updateActions = + buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddBillingAddressUpdateActions_WithEmptyBillingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").billingAddresses(emptyList()).build(); + + final List> updateActions = + buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAddBillingAddressUpdateActions_WithSameBillingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .billingAddresses(singletonList(0)) + .build(); + + final List> updateActions = + buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildAddBillingAddressUpdateActions_WithNewBillingAddresses_ShouldReturnAddBillingAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withId("address-id-1") + .withPostalCode("123"), + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withId("address-id-2") + .withBuilding("no 1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final Address address1 = + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withPostalCode("123") + .withId("address-id-new-1"); + + final Address address2 = + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 2") + .withId("address-id-new-2"); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(asList(address1, address2)) + .billingAddresses(asList(0, 1)) + .build(); + + final List> updateActions = + buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(AddBillingAddressId.ofKey("address-key-2")); + } + + @Test + void buildAddBillingAddressUpdateActions_WithEmptyAddressKey_ShouldReturnAction() { + // preparation + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("").withId("address-id-1").withPostalCode("123"), + Address.of(CountryCode.DE) + .withKey("") + .withId("address-id-2") + .withBuilding("no 1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withKey("").withId("address-id-1"))); + + final Address address1 = + Address.of(CountryCode.DE) + .withKey("address-key-1") + .withPostalCode("123") + .withId("address-id-new-1"); + + final Address address2 = + Address.of(CountryCode.DE) + .withKey("address-key-2") + .withBuilding("no 2") + .withId("address-id-new-2"); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(asList(address1, address2)) + .billingAddresses(singletonList(1)) + .build(); + // test + final List> updateActions = + buildAddBillingAddressUpdateActions(oldCustomer, newCustomer); + + // assertions + assertThat(updateActions).containsExactly(AddBillingAddressId.ofKey("address-key-2")); + } + + @Test + void + buildAddBillingAddressUpdateActions_WithShippingAddressIdLessThanZero_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .billingAddresses(singletonList(-1)) + .build(); + + assertThatThrownBy(() -> buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage(format("Addresses list does not contain an address at the index: %s", -1)); + } + + @Test + void + buildAddBillingAddressUpdateActions_InOutBoundOfTheExistingIndexes_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .billingAddresses(singletonList(2)) + .build(); + + assertThatThrownBy(() -> buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage(format("Addresses list does not contain an address at the index: %s", 2)); + } + + @Test + void buildAddBillingAddressUpdateActions_InWithNullAddress_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(singletonList(null)) + .billingAddresses(singletonList(0)) + .build(); + + assertThatThrownBy(() -> buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage(format("Address is null at the index: %s of the addresses list.", 0)); + } + + @Test + void buildAddBillingAddressUpdateActions_InWithBlankKey_ShouldThrowIllegalArgumentException() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))) + .billingAddresses(singletonList(0)) + .build(); + + assertThatThrownBy(() -> buildAddBillingAddressUpdateActions(oldCustomer, newCustomer)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage( + format("Address does not have a key at the index: %s of the addresses list.", 0)); + } + + @Test + void buildRemoveBillingAddressUpdateActions_WithOldBillingAddresses_ShouldReturnAction() { + // preparation + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); + + final Address address3 = + Address.of(CountryCode.DE).withKey("address-key-3").withId("address-id-3"); + + final Address address4 = + Address.of(CountryCode.DE).withKey("address-key-4").withId("address-id-new-4"); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses(asList(address3, address4)) + .billingAddresses(singletonList(1)) + .build(); + // test + final List> updateActions = + buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); + // assertions + assertThat(updateActions).containsExactly(RemoveBillingAddressId.of("address-id-1")); + } + + @Test + void buildRemoveBillingAddressUpdateActions_WithEmptyOldBillingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()).thenReturn(emptyList()); + + final CustomerDraft newCustomer = CustomerDraftBuilder.of("email", "pass").build(); + + final List> updateActions = + buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildRemoveBillingAddressUpdateActions_WitNullOldBillingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()).thenReturn(null); + + final CustomerDraft newCustomer = CustomerDraftBuilder.of("email", "pass").build(); + + final List> updateActions = + buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildRemoveBillingAddressUpdateActions_WithEmptyNewBillingAddresses_ShouldReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .billingAddresses(emptyList()) + .build(); + + final List> updateActions = + buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(RemoveBillingAddressId.of("address-id-1")); + } + + @Test + void buildRemoveBillingAddressUpdateActions_WithNullNewBillingAddresses_ShouldReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .billingAddresses(null) + .build(); + + final List> updateActions = + buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).containsExactly(RemoveBillingAddressId.of("address-id-1")); + } + + @Test + void buildRemoveBillingAddressUpdateActions_WithSameBillingAddresses_ShouldNotReturnAction() { + + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .billingAddresses(singletonList(0)) + .build(); + + final List> updateActions = + buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildRemoveBillingAddressUpdateActions_WithEmptyOldKey_ShouldNotReturnAction() { + // preparation + when(oldCustomer.getAddresses()) + .thenReturn( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn(singletonList(Address.of(CountryCode.DE).withKey(""))); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .build(); + // test + final List> updateActions = + buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); + + // assertions + assertThat(updateActions).containsExactly(RemoveBillingAddressId.of(null)); + } + + @Test + void + buildRemoveBillingAddressUpdateActions_WithLessBillingAddresses_ShouldReturnRemoveBillingAddressActions() { + + when(oldCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), + Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); + when(oldCustomer.getBillingAddresses()) + .thenReturn( + asList( Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"), Address.of(CountryCode.DE).withKey("address-key-2").withId("address-id-2"))); - final CustomerDraft newCustomer = - CustomerDraftBuilder - .of("email", "pass") - .addresses(singletonList( - Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1") - )) - .billingAddresses(singletonList(0)) - .build(); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .addresses( + singletonList( + Address.of(CountryCode.DE).withKey("address-key-1").withId("address-id-1"))) + .billingAddresses(singletonList(0)) + .build(); - final List> updateActions = - buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); + final List> updateActions = + buildRemoveBillingAddressUpdateActions(oldCustomer, newCustomer); - assertThat(updateActions).containsExactly(RemoveBillingAddressId.of("address-id-2")); - } + assertThat(updateActions).containsExactly(RemoveBillingAddressId.of("address-id-2")); + } } diff --git a/src/test/java/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtilsTest.java index 9c04f66c5a..5b5d496885 100644 --- a/src/test/java/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtilsTest.java @@ -1,5 +1,13 @@ package com.commercetools.sync.customers.utils; +import static com.commercetools.sync.commons.MockUtils.getTypeMock; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroup; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.neovisionaries.i18n.CountryCode; import io.sphere.sdk.customergroups.CustomerGroup; import io.sphere.sdk.customers.Customer; @@ -12,179 +20,182 @@ import io.sphere.sdk.stores.Store; import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.UUID; - -import static com.commercetools.sync.commons.MockUtils.getTypeMock; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroup; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CustomerReferenceResolutionUtilsTest { - @Test - void mapToCustomerDrafts_WithExpandedReferences_ShouldReturnResourceIdentifiersWithKeys() { - final Type mockCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - final CustomerGroup mockCustomerGroup = getMockCustomerGroup(UUID.randomUUID().toString(), "customerGroupKey"); - final String storeKey1 = "storeKey1"; - final String storeKey2 = "storeKey2"; - - final List mockCustomers = new ArrayList<>(); - for (int i = 0; i < 3; i++) { - final Customer mockCustomer = mock(Customer.class); - - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), - mockCustomType); - when(mockCustomFields.getType()).thenReturn(typeReference); - when(mockCustomer.getCustom()).thenReturn(mockCustomFields); - - final Reference customerGroupReference = - Reference.ofResourceTypeIdAndObj(CustomerGroup.referenceTypeId(), mockCustomerGroup); - when(mockCustomer.getCustomerGroup()).thenReturn(customerGroupReference); - - List> keyReferences = asList( - KeyReference.of(storeKey1, Store.referenceTypeId()), - KeyReference.of(storeKey2, Store.referenceTypeId())); - - when(mockCustomer.getStores()).thenReturn(keyReferences); - when(mockCustomer.getAddresses()).thenReturn(null); - - mockCustomers.add(mockCustomer); - } - - final List referenceReplacedDrafts = - CustomerReferenceResolutionUtils.mapToCustomerDrafts(mockCustomers); - - referenceReplacedDrafts.forEach(draft -> { - assertThat(draft.getCustom().getType().getKey()).isEqualTo(mockCustomType.getKey()); - assertThat(draft.getCustomerGroup().getKey()).isEqualTo(mockCustomerGroup.getKey()); - assertThat(draft.getStores().get(0).getKey()).isEqualTo(storeKey1); - assertThat(draft.getStores().get(1).getKey()).isEqualTo(storeKey2); - }); + @Test + void mapToCustomerDrafts_WithExpandedReferences_ShouldReturnResourceIdentifiersWithKeys() { + final Type mockCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + final CustomerGroup mockCustomerGroup = + getMockCustomerGroup(UUID.randomUUID().toString(), "customerGroupKey"); + final String storeKey1 = "storeKey1"; + final String storeKey2 = "storeKey2"; + + final List mockCustomers = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + final Customer mockCustomer = mock(Customer.class); + + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), mockCustomType); + when(mockCustomFields.getType()).thenReturn(typeReference); + when(mockCustomer.getCustom()).thenReturn(mockCustomFields); + + final Reference customerGroupReference = + Reference.ofResourceTypeIdAndObj(CustomerGroup.referenceTypeId(), mockCustomerGroup); + when(mockCustomer.getCustomerGroup()).thenReturn(customerGroupReference); + + List> keyReferences = + asList( + KeyReference.of(storeKey1, Store.referenceTypeId()), + KeyReference.of(storeKey2, Store.referenceTypeId())); + + when(mockCustomer.getStores()).thenReturn(keyReferences); + when(mockCustomer.getAddresses()).thenReturn(null); + + mockCustomers.add(mockCustomer); } - @Test - void mapToCustomerDrafts_WithNonExpandedReferences_ShouldReturnResourceIdentifiersWithoutKeys() { - final String customTypeId = UUID.randomUUID().toString(); - final String customerGroupId = UUID.randomUUID().toString(); - - final List mockCustomers = new ArrayList<>(); - for (int i = 0; i < 3; i++) { - final Customer mockCustomer = mock(Customer.class); - - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = Reference.ofResourceTypeIdAndId("resourceTypeId", - customTypeId); - when(mockCustomFields.getType()).thenReturn(typeReference); - when(mockCustomer.getCustom()).thenReturn(mockCustomFields); + final List referenceReplacedDrafts = + CustomerReferenceResolutionUtils.mapToCustomerDrafts(mockCustomers); - final Reference customerGroupReference = - Reference.ofResourceTypeIdAndId(CustomerGroup.referenceTypeId(), customerGroupId); - when(mockCustomer.getCustomerGroup()).thenReturn(customerGroupReference); + referenceReplacedDrafts.forEach( + draft -> { + assertThat(draft.getCustom().getType().getKey()).isEqualTo(mockCustomType.getKey()); + assertThat(draft.getCustomerGroup().getKey()).isEqualTo(mockCustomerGroup.getKey()); + assertThat(draft.getStores().get(0).getKey()).isEqualTo(storeKey1); + assertThat(draft.getStores().get(1).getKey()).isEqualTo(storeKey2); + }); + } - when(mockCustomer.getStores()).thenReturn(null); - when(mockCustomer.getAddresses()).thenReturn(null); + @Test + void mapToCustomerDrafts_WithNonExpandedReferences_ShouldReturnResourceIdentifiersWithoutKeys() { + final String customTypeId = UUID.randomUUID().toString(); + final String customerGroupId = UUID.randomUUID().toString(); - mockCustomers.add(mockCustomer); - } + final List mockCustomers = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + final Customer mockCustomer = mock(Customer.class); - final List referenceReplacedDrafts = - CustomerReferenceResolutionUtils.mapToCustomerDrafts(mockCustomers); + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = + Reference.ofResourceTypeIdAndId("resourceTypeId", customTypeId); + when(mockCustomFields.getType()).thenReturn(typeReference); + when(mockCustomer.getCustom()).thenReturn(mockCustomFields); - referenceReplacedDrafts.forEach(draft -> { - assertThat(draft.getCustom().getType().getId()).isEqualTo(customTypeId); - assertThat(draft.getCustom().getType().getKey()).isNull(); - assertThat(draft.getCustomerGroup().getId()).isEqualTo(customerGroupId); - assertThat(draft.getCustomerGroup().getKey()).isNull(); - }); - } + final Reference customerGroupReference = + Reference.ofResourceTypeIdAndId(CustomerGroup.referenceTypeId(), customerGroupId); + when(mockCustomer.getCustomerGroup()).thenReturn(customerGroupReference); - @Test - void mapToCustomerDrafts_WithAddresses_ShouldReturnResourceIdentifiersWithCorrectIndexes() { - final Customer mockCustomer = mock(Customer.class); - - when(mockCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withId("address-id1"), - Address.of(CountryCode.FR).withId("address-id2"), - Address.of(CountryCode.US).withId("address-id3"))); - when(mockCustomer.getDefaultBillingAddressId()).thenReturn("address-id1"); - when(mockCustomer.getDefaultShippingAddressId()).thenReturn("address-id2"); - when(mockCustomer.getBillingAddressIds()).thenReturn(asList("address-id1", "address-id3")); - when(mockCustomer.getShippingAddressIds()).thenReturn(asList("address-id2", "address-id3")); - - final List referenceReplacedDrafts = - CustomerReferenceResolutionUtils.mapToCustomerDrafts(singletonList(mockCustomer)); - - final CustomerDraft customerDraft = referenceReplacedDrafts.get(0); - - assertThat(customerDraft.getAddresses()).isEqualTo(mockCustomer.getAddresses()); - assertThat(customerDraft.getDefaultBillingAddress()).isEqualTo(0); - assertThat(customerDraft.getDefaultShippingAddress()).isEqualTo(1); - assertThat(customerDraft.getBillingAddresses()).isEqualTo(asList(0, 2)); - assertThat(customerDraft.getShippingAddresses()).isEqualTo(asList(1, 2)); - } + when(mockCustomer.getStores()).thenReturn(null); + when(mockCustomer.getAddresses()).thenReturn(null); - @Test - void mapToCustomerDrafts_WithMissingAddresses_ShouldReturnResourceIdentifiersWithCorrectIndexes() { - final Customer mockCustomer = mock(Customer.class); - - when(mockCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withId("address-id1"), - Address.of(CountryCode.FR).withId("address-id2"), - Address.of(CountryCode.US).withId("address-id3"))); - when(mockCustomer.getDefaultBillingAddressId()).thenReturn("non-existing-id"); - when(mockCustomer.getDefaultShippingAddressId()).thenReturn(null); - when(mockCustomer.getBillingAddressIds()).thenReturn(asList("address-id1", "non-existing-id")); - when(mockCustomer.getShippingAddressIds()).thenReturn(asList(" ", "address-id3", null)); - - final List referenceReplacedDrafts = - CustomerReferenceResolutionUtils.mapToCustomerDrafts(singletonList(mockCustomer)); - - final CustomerDraft customerDraft = referenceReplacedDrafts.get(0); - - assertThat(customerDraft.getAddresses()).isEqualTo(mockCustomer.getAddresses()); - assertThat(customerDraft.getDefaultBillingAddress()).isNull(); - assertThat(customerDraft.getDefaultShippingAddress()).isNull(); - assertThat(customerDraft.getBillingAddresses()).isEqualTo(asList(0, null)); - assertThat(customerDraft.getShippingAddresses()).isEqualTo(asList(null, 2, null)); + mockCustomers.add(mockCustomer); } - @Test - void mapToCustomerDrafts_WithNullIdOnAddresses_ShouldReturnResourceIdentifiersWithCorrectIndexes() { - final Customer mockCustomer = mock(Customer.class); - - when(mockCustomer.getAddresses()).thenReturn(asList( - Address.of(CountryCode.DE).withId("address-id1"), - Address.of(CountryCode.US).withId(null), - Address.of(CountryCode.US).withId("address-id3"))); - when(mockCustomer.getDefaultBillingAddressId()).thenReturn("address-id1"); - when(mockCustomer.getDefaultShippingAddressId()).thenReturn("address-id2"); - when(mockCustomer.getBillingAddressIds()).thenReturn(asList("address-id1", "address-id3")); - when(mockCustomer.getShippingAddressIds()).thenReturn(null); - - final List referenceReplacedDrafts = - CustomerReferenceResolutionUtils.mapToCustomerDrafts(singletonList(mockCustomer)); - - final CustomerDraft customerDraft = referenceReplacedDrafts.get(0); - - assertThat(customerDraft.getAddresses()).isEqualTo(mockCustomer.getAddresses()); - assertThat(customerDraft.getDefaultBillingAddress()).isEqualTo(0); - assertThat(customerDraft.getDefaultShippingAddress()).isNull(); - assertThat(customerDraft.getBillingAddresses()).isEqualTo(asList(0, 2)); - assertThat(customerDraft.getShippingAddresses()).isNull(); - } + final List referenceReplacedDrafts = + CustomerReferenceResolutionUtils.mapToCustomerDrafts(mockCustomers); - @Test - void buildCustomerQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final CustomerQuery customerQuery = CustomerReferenceResolutionUtils.buildCustomerQuery(); - assertThat(customerQuery.expansionPaths()) - .containsExactly(ExpansionPath.of("customerGroup"), ExpansionPath.of("custom.type")); - } + referenceReplacedDrafts.forEach( + draft -> { + assertThat(draft.getCustom().getType().getId()).isEqualTo(customTypeId); + assertThat(draft.getCustom().getType().getKey()).isNull(); + assertThat(draft.getCustomerGroup().getId()).isEqualTo(customerGroupId); + assertThat(draft.getCustomerGroup().getKey()).isNull(); + }); + } + + @Test + void mapToCustomerDrafts_WithAddresses_ShouldReturnResourceIdentifiersWithCorrectIndexes() { + final Customer mockCustomer = mock(Customer.class); + + when(mockCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withId("address-id1"), + Address.of(CountryCode.FR).withId("address-id2"), + Address.of(CountryCode.US).withId("address-id3"))); + when(mockCustomer.getDefaultBillingAddressId()).thenReturn("address-id1"); + when(mockCustomer.getDefaultShippingAddressId()).thenReturn("address-id2"); + when(mockCustomer.getBillingAddressIds()).thenReturn(asList("address-id1", "address-id3")); + when(mockCustomer.getShippingAddressIds()).thenReturn(asList("address-id2", "address-id3")); + + final List referenceReplacedDrafts = + CustomerReferenceResolutionUtils.mapToCustomerDrafts(singletonList(mockCustomer)); + + final CustomerDraft customerDraft = referenceReplacedDrafts.get(0); + + assertThat(customerDraft.getAddresses()).isEqualTo(mockCustomer.getAddresses()); + assertThat(customerDraft.getDefaultBillingAddress()).isEqualTo(0); + assertThat(customerDraft.getDefaultShippingAddress()).isEqualTo(1); + assertThat(customerDraft.getBillingAddresses()).isEqualTo(asList(0, 2)); + assertThat(customerDraft.getShippingAddresses()).isEqualTo(asList(1, 2)); + } + + @Test + void + mapToCustomerDrafts_WithMissingAddresses_ShouldReturnResourceIdentifiersWithCorrectIndexes() { + final Customer mockCustomer = mock(Customer.class); + + when(mockCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withId("address-id1"), + Address.of(CountryCode.FR).withId("address-id2"), + Address.of(CountryCode.US).withId("address-id3"))); + when(mockCustomer.getDefaultBillingAddressId()).thenReturn("non-existing-id"); + when(mockCustomer.getDefaultShippingAddressId()).thenReturn(null); + when(mockCustomer.getBillingAddressIds()).thenReturn(asList("address-id1", "non-existing-id")); + when(mockCustomer.getShippingAddressIds()).thenReturn(asList(" ", "address-id3", null)); + + final List referenceReplacedDrafts = + CustomerReferenceResolutionUtils.mapToCustomerDrafts(singletonList(mockCustomer)); + + final CustomerDraft customerDraft = referenceReplacedDrafts.get(0); + + assertThat(customerDraft.getAddresses()).isEqualTo(mockCustomer.getAddresses()); + assertThat(customerDraft.getDefaultBillingAddress()).isNull(); + assertThat(customerDraft.getDefaultShippingAddress()).isNull(); + assertThat(customerDraft.getBillingAddresses()).isEqualTo(asList(0, null)); + assertThat(customerDraft.getShippingAddresses()).isEqualTo(asList(null, 2, null)); + } + + @Test + void + mapToCustomerDrafts_WithNullIdOnAddresses_ShouldReturnResourceIdentifiersWithCorrectIndexes() { + final Customer mockCustomer = mock(Customer.class); + + when(mockCustomer.getAddresses()) + .thenReturn( + asList( + Address.of(CountryCode.DE).withId("address-id1"), + Address.of(CountryCode.US).withId(null), + Address.of(CountryCode.US).withId("address-id3"))); + when(mockCustomer.getDefaultBillingAddressId()).thenReturn("address-id1"); + when(mockCustomer.getDefaultShippingAddressId()).thenReturn("address-id2"); + when(mockCustomer.getBillingAddressIds()).thenReturn(asList("address-id1", "address-id3")); + when(mockCustomer.getShippingAddressIds()).thenReturn(null); + + final List referenceReplacedDrafts = + CustomerReferenceResolutionUtils.mapToCustomerDrafts(singletonList(mockCustomer)); + + final CustomerDraft customerDraft = referenceReplacedDrafts.get(0); + + assertThat(customerDraft.getAddresses()).isEqualTo(mockCustomer.getAddresses()); + assertThat(customerDraft.getDefaultBillingAddress()).isEqualTo(0); + assertThat(customerDraft.getDefaultShippingAddress()).isNull(); + assertThat(customerDraft.getBillingAddresses()).isEqualTo(asList(0, 2)); + assertThat(customerDraft.getShippingAddresses()).isNull(); + } + + @Test + void buildCustomerQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final CustomerQuery customerQuery = CustomerReferenceResolutionUtils.buildCustomerQuery(); + assertThat(customerQuery.expansionPaths()) + .containsExactly(ExpansionPath.of("customerGroup"), ExpansionPath.of("custom.type")); + } } diff --git a/src/test/java/com/commercetools/sync/customers/utils/CustomerSyncUtilsTest.java b/src/test/java/com/commercetools/sync/customers/utils/CustomerSyncUtilsTest.java index 73183d6d51..5893ca9eeb 100644 --- a/src/test/java/com/commercetools/sync/customers/utils/CustomerSyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/customers/utils/CustomerSyncUtilsTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.customers.utils; +import static com.commercetools.sync.customers.utils.CustomerSyncUtils.buildActions; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; @@ -14,112 +19,113 @@ import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.CustomFieldsDraftBuilder; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.List; import java.util.Map; - -import static com.commercetools.sync.customers.utils.CustomerSyncUtils.buildActions; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomerSyncUtilsTest { - private static final String CUSTOM_TYPE_ID = "id"; - private static final String CUSTOM_FIELD_NAME = "field"; - private static final String CUSTOM_FIELD_VALUE = "value"; - - private Customer oldCustomer; - - @BeforeEach - void setup() { - oldCustomer = mock(Customer.class); - when(oldCustomer.getEmail()).thenReturn("email"); - - final CustomFields customFields = mock(CustomFields.class); - when(customFields.getType()).thenReturn(Type.referenceOfId(CUSTOM_TYPE_ID)); - - final Map customFieldsJsonMapMock = new HashMap<>(); - customFieldsJsonMapMock.put(CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode(CUSTOM_FIELD_VALUE)); - when(customFields.getFieldsJsonMap()).thenReturn(customFieldsJsonMapMock); - - when(oldCustomer.getCustom()).thenReturn(customFields); - } - - @Test - void buildActions_WithDifferentCustomType_ShouldBuildUpdateAction() { - final CustomFieldsDraft customFieldsDraft = - CustomFieldsDraftBuilder.ofTypeId("newId") - .addObject("newField", "newValue") - .build(); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .custom(customFieldsDraft) - .build(); - - final List> actions = - buildActions(oldCustomer, newCustomer, CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - - assertThat(actions).containsExactly( - SetCustomType.ofTypeIdAndJson(customFieldsDraft.getType().getId(), customFieldsDraft.getFields())); - } - - @Test - void buildActions_WithSameCustomTypeWithNewCustomFields_ShouldBuildUpdateAction() { - final CustomFieldsDraft sameCustomFieldDraftWithNewCustomField = - CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) - .addObject(CUSTOM_FIELD_NAME, CUSTOM_FIELD_VALUE) - .addObject("name_2", "value_2") - .build(); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .custom(sameCustomFieldDraftWithNewCustomField) - .build(); - - final List> actions = - buildActions(oldCustomer, newCustomer, CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - - - assertThat(actions).containsExactly( + private static final String CUSTOM_TYPE_ID = "id"; + private static final String CUSTOM_FIELD_NAME = "field"; + private static final String CUSTOM_FIELD_VALUE = "value"; + + private Customer oldCustomer; + + @BeforeEach + void setup() { + oldCustomer = mock(Customer.class); + when(oldCustomer.getEmail()).thenReturn("email"); + + final CustomFields customFields = mock(CustomFields.class); + when(customFields.getType()).thenReturn(Type.referenceOfId(CUSTOM_TYPE_ID)); + + final Map customFieldsJsonMapMock = new HashMap<>(); + customFieldsJsonMapMock.put( + CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode(CUSTOM_FIELD_VALUE)); + when(customFields.getFieldsJsonMap()).thenReturn(customFieldsJsonMapMock); + + when(oldCustomer.getCustom()).thenReturn(customFields); + } + + @Test + void buildActions_WithDifferentCustomType_ShouldBuildUpdateAction() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraftBuilder.ofTypeId("newId").addObject("newField", "newValue").build(); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").custom(customFieldsDraft).build(); + + final List> actions = + buildActions( + oldCustomer, + newCustomer, + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions) + .containsExactly( + SetCustomType.ofTypeIdAndJson( + customFieldsDraft.getType().getId(), customFieldsDraft.getFields())); + } + + @Test + void buildActions_WithSameCustomTypeWithNewCustomFields_ShouldBuildUpdateAction() { + final CustomFieldsDraft sameCustomFieldDraftWithNewCustomField = + CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) + .addObject(CUSTOM_FIELD_NAME, CUSTOM_FIELD_VALUE) + .addObject("name_2", "value_2") + .build(); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .custom(sameCustomFieldDraftWithNewCustomField) + .build(); + + final List> actions = + buildActions( + oldCustomer, + newCustomer, + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions) + .containsExactly( SetCustomField.ofJson("name_2", JsonNodeFactory.instance.textNode("value_2"))); - } - - @Test - void buildActions_WithSameCustomTypeWithDifferentCustomFieldValues_ShouldBuildUpdateAction() { - - final CustomFieldsDraft sameCustomFieldDraftWithNewValue = - CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) - .addObject(CUSTOM_FIELD_NAME, - "newValue") - .build(); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .custom(sameCustomFieldDraftWithNewValue) - .build(); - - final List> actions = - buildActions(oldCustomer, newCustomer, CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - - assertThat(actions).containsExactly( - SetCustomField.ofJson(CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode("newValue"))); - } - - @Test - void buildActions_WithJustNewCartDiscountHasNullCustomType_ShouldBuildUpdateAction() { - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .custom(null) - .build(); - - final List> actions = - buildActions(oldCustomer, newCustomer, CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - - assertThat(actions).containsExactly(SetCustomType.ofRemoveType()); - } + } + + @Test + void buildActions_WithSameCustomTypeWithDifferentCustomFieldValues_ShouldBuildUpdateAction() { + + final CustomFieldsDraft sameCustomFieldDraftWithNewValue = + CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) + .addObject(CUSTOM_FIELD_NAME, "newValue") + .build(); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").custom(sameCustomFieldDraftWithNewValue).build(); + + final List> actions = + buildActions( + oldCustomer, + newCustomer, + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions) + .containsExactly( + SetCustomField.ofJson( + CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode("newValue"))); + } + + @Test + void buildActions_WithJustNewCartDiscountHasNullCustomType_ShouldBuildUpdateAction() { + final CustomerDraft newCustomer = CustomerDraftBuilder.of("email", "pass").custom(null).build(); + + final List> actions = + buildActions( + oldCustomer, + newCustomer, + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions).containsExactly(SetCustomType.ofRemoveType()); + } } diff --git a/src/test/java/com/commercetools/sync/customers/utils/CustomerUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/customers/utils/CustomerUpdateActionUtilsTest.java index 20eace2487..73eaa9b07d 100644 --- a/src/test/java/com/commercetools/sync/customers/utils/CustomerUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/customers/utils/CustomerUpdateActionUtilsTest.java @@ -1,5 +1,24 @@ package com.commercetools.sync.customers.utils; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.CUSTOMER_NUMBER_EXISTS_WARNING; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildChangeEmailUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetCompanyNameUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetCustomerGroupUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetCustomerNumberUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetDateOfBirthUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetExternalIdUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetFirstNameUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetLastNameUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetLocaleUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetMiddleNameUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetSalutationUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetTitleUpdateAction; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetVatIdUpdateAction; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customers.CustomerSyncOptions; import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; import io.sphere.sdk.client.SphereClient; @@ -24,405 +43,400 @@ import io.sphere.sdk.customers.commands.updateactions.SetVatId; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.UUID; - -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.CUSTOMER_NUMBER_EXISTS_WARNING; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildChangeEmailUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetCompanyNameUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetCustomerGroupUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetCustomerNumberUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetDateOfBirthUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetExternalIdUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetFirstNameUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetLastNameUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetLocaleUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetMiddleNameUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetSalutationUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetTitleUpdateAction; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildSetVatIdUpdateAction; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class CustomerUpdateActionUtilsTest { - private static Customer old; - private static CustomerDraft newSame; - private static CustomerDraft newDifferent; - - @BeforeEach - void setup() { - CustomerName oldCustomerName = CustomerName.of("old-title", "old-firstName", - "old-middleName", "old-lastName"); - - final String key = "key1"; - final String companyName = "companyName1"; - final String salutation = "salutation1"; - final String vatId = "vatId1"; - final String locale = "DE"; - final String birthDate = "1990-10-01"; - final String externalId = "externalId1"; - final String customerNumber = "1234"; - - old = mock(Customer.class); - when(old.getKey()).thenReturn(key); - when(old.getName()).thenReturn(oldCustomerName); - when(old.getEmail()).thenReturn("old-email"); - when(old.getFirstName()).thenReturn("old-firstName"); - when(old.getMiddleName()).thenReturn("old-middleName"); - when(old.getLastName()).thenReturn("old-lastName"); - when(old.getTitle()).thenReturn("old-title"); - when(old.getSalutation()).thenReturn(salutation); - when(old.getCustomerNumber()).thenReturn(customerNumber); - when(old.getExternalId()).thenReturn(externalId); - when(old.getCompanyName()).thenReturn(companyName); - when(old.getDateOfBirth()).thenReturn(LocalDate.parse(birthDate)); - when(old.getVatId()).thenReturn(vatId); - when(old.getLocale()).thenReturn(Locale.forLanguageTag(locale)); - - - newSame = CustomerDraftBuilder.of(oldCustomerName, "old-email", "oldPW") - .key(key) - .companyName(companyName) - .salutation(salutation) - .dateOfBirth(LocalDate.parse(birthDate)) - .locale(Locale.forLanguageTag(locale)) - .vatId(vatId) - .externalId(externalId) - .customerNumber(customerNumber) - .build(); - - CustomerName newCustomerName = CustomerName.of("new-title", "new-firstName", - "new-middleName", "new-lastName"); - - newDifferent = CustomerDraftBuilder.of(newCustomerName, "new-email", "newPW").build(); - } - - @Test - void buildChangeEmailUpdateAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeEmailUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(ChangeEmail.class); - assertThat(result).contains(ChangeEmail.of(newDifferent.getEmail())); - } - - @Test - void buildChangeEmailUpdateAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeEmailUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetFirstNameUpdateAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetFirstNameUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetFirstName.class); - assertThat(result).contains(SetFirstName.of(newDifferent.getFirstName())); - } - - @Test - void buildSetFirstNameUpdateAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetFirstNameUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetLastNameUpdateAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetLastNameUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetLastName.class); - assertThat(result).contains(SetLastName.of(newDifferent.getLastName())); - } - - @Test - void buildSetLastNameUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetLastNameUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetMiddleNameUpdateAction_withDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetMiddleNameUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetMiddleName.class); - assertThat(result).contains(SetMiddleName.of(newDifferent.getMiddleName())); - } - - @Test - void buildSetMiddleNameUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetMiddleNameUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetTitleUpdateAction_withDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetTitleUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetTitle.class); - assertThat(result).contains(SetTitle.of(newDifferent.getTitle())); - } - - @Test - void buildSetTitleUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetTitleUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetSalutationUpdateAction_withDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetSalutationUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetSalutation.class); - assertThat(result).contains(SetSalutation.of(newDifferent.getSalutation())); - } - - @Test - void buildSetSalutationUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetSalutationUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetCustomerNumberUpdateAction_withDifferentValues_ShouldNotReturnActionAndAddWarningCallback() { - final List warningMessages = new ArrayList<>(); - final CustomerSyncOptions customerSyncOptions = - CustomerSyncOptionsBuilder.of(mock(SphereClient.class)) - .warningCallback((exception, oldResource, newResource) - -> warningMessages.add(exception.getMessage())) - .build(); - - final Optional> result = - buildSetCustomerNumberUpdateAction(old, newDifferent, customerSyncOptions); - - assertThat(result).isEmpty(); - assertThat(warningMessages).containsExactly(format(CUSTOMER_NUMBER_EXISTS_WARNING, old.getKey(), "1234")); - } - - @Test - void buildSetCustomerNumberUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final List warningMessages = new ArrayList<>(); - final CustomerSyncOptions customerSyncOptions = - CustomerSyncOptionsBuilder.of(mock(SphereClient.class)) - .warningCallback((exception, oldResource, newResource) - -> warningMessages.add(exception.getMessage())) - .build(); - - final Optional> result = - buildSetCustomerNumberUpdateAction(old, newSame, customerSyncOptions); - - assertThat(result).isEmpty(); - assertThat(warningMessages).isEmpty(); - } - - @Test - void buildSetCustomerNumberUpdateAction_withEmptyOldValueAndANewValue_ShouldReturnAction() { - final List warningMessages = new ArrayList<>(); - final CustomerSyncOptions customerSyncOptions = - CustomerSyncOptionsBuilder.of(mock(SphereClient.class)) - .warningCallback((exception, oldResource, newResource) - -> warningMessages.add(exception.getMessage())) - .build(); - - Customer oldCustomer = mock(Customer.class); - when(oldCustomer.getCustomerNumber()).thenReturn(" "); - - CustomerDraft newCustomer = mock(CustomerDraft.class); - when(newCustomer.getCustomerNumber()).thenReturn("customer-number"); - - final Optional> result = - buildSetCustomerNumberUpdateAction(oldCustomer, newCustomer, customerSyncOptions); - - assertThat(result).containsInstanceOf(SetCustomerNumber.class); - assertThat(result).contains(SetCustomerNumber.of("customer-number")); - assertThat(warningMessages).isEmpty(); - } - - @Test - void buildSetCustomerNumberUpdateAction_withNullOldValueAndANewValue_ShouldReturnAction() { - final List warningMessages = new ArrayList<>(); - final CustomerSyncOptions customerSyncOptions = - CustomerSyncOptionsBuilder.of(mock(SphereClient.class)) - .warningCallback((exception, oldResource, newResource) - -> warningMessages.add(exception.getMessage())) - .build(); - - Customer oldCustomer = mock(Customer.class); - when(oldCustomer.getCustomerNumber()).thenReturn(null); - - CustomerDraft newCustomer = mock(CustomerDraft.class); - when(newCustomer.getCustomerNumber()).thenReturn("customer-number"); - - final Optional> result = - buildSetCustomerNumberUpdateAction(oldCustomer, newCustomer, customerSyncOptions); - - assertThat(result).containsInstanceOf(SetCustomerNumber.class); - assertThat(result).contains(SetCustomerNumber.of("customer-number")); - assertThat(warningMessages).isEmpty(); - } - - @Test - void buildSetExternalIdUpdateAction_withDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetExternalIdUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetExternalId.class); - assertThat(result).contains(SetExternalId.of(newDifferent.getExternalId())); - } - - @Test - void buildSetExternalIdUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetExternalIdUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetCompanyNameUpdateAction_withDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetCompanyNameUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetCompanyName.class); - assertThat(result).contains(SetCompanyName.of(newDifferent.getCompanyName())); - } - - @Test - void buildSetCompanyNameUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetCompanyNameUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetDateOfBirthUpdateAction_withDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetDateOfBirthUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetDateOfBirth.class); - assertThat(result).contains(SetDateOfBirth.of(newDifferent.getDateOfBirth())); - } - - @Test - void buildSetDateOfBirthUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetDateOfBirthUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetVatIdUpdateAction_withDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetVatIdUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetVatId.class); - assertThat(result).contains(SetVatId.of(newDifferent.getVatId())); - } - - @Test - void buildSetVatIdUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetVatIdUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetLocaleUpdateAction_withDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetLocaleUpdateAction(old, newDifferent); - - assertThat(result).containsInstanceOf(SetLocale.class); - assertThat(result).contains(SetLocale.of(newDifferent.getLocale())); - } - - @Test - void buildSetLocaleUpdateAction_withSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetLocaleUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetCustomerGroupAction_WithSameReference_ShouldNotReturnAction() { - final String customerGroupId = UUID.randomUUID().toString(); - final Reference customerGroupReference = - Reference.of(CustomerGroup.referenceTypeId(), customerGroupId); - - final Customer oldCustomer = mock(Customer.class); - when(oldCustomer.getCustomerGroup()).thenReturn(customerGroupReference); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .customerGroup(ResourceIdentifier.ofId(customerGroupId)) - .build(); - - final Optional> result = buildSetCustomerGroupUpdateAction(oldCustomer, newCustomer); - assertThat(result).isNotPresent(); - } - - @Test - void buildSetCustomerGroupAction_WithDifferentReference_ShouldReturnAction() { - final String customerGroupId = UUID.randomUUID().toString(); - final Reference customerGroupReference = - Reference.of(CustomerGroup.referenceTypeId(), customerGroupId); - - final Customer oldCustomer = mock(Customer.class); - when(oldCustomer.getCustomerGroup()).thenReturn(customerGroupReference); - - final String resolvedCustomerGroupId = UUID.randomUUID().toString(); - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .customerGroup(ResourceIdentifier.ofId(resolvedCustomerGroupId)) - .build(); - - final Optional> result = buildSetCustomerGroupUpdateAction(oldCustomer, newCustomer); - assertThat(result).isPresent(); - assertThat(result).containsInstanceOf(SetCustomerGroup.class); - assertThat(((SetCustomerGroup) result.get()).getCustomerGroup()) - .isEqualTo(Reference.of(CustomerGroup.referenceTypeId(), resolvedCustomerGroupId)); - } - - @Test - void buildSetCustomerGroupAction_WithOnlyNewReference_ShouldReturnAction() { - final Customer oldCustomer = mock(Customer.class); - final String newCustomerGroupId = UUID.randomUUID().toString(); - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .customerGroup(ResourceIdentifier.ofId(newCustomerGroupId)) - .build(); - - final Optional> result = buildSetCustomerGroupUpdateAction(oldCustomer, newCustomer); - assertThat(result).isPresent(); - assertThat(result).containsInstanceOf(SetCustomerGroup.class); - assertThat(((SetCustomerGroup) result.get()).getCustomerGroup()) - .isEqualTo(Reference.of(CustomerGroup.referenceTypeId(), newCustomerGroupId)); - } - - @Test - void buildSetCustomerGroupAction_WithoutNewReference_ShouldReturnUnsetAction() { - final String customerGroupId = UUID.randomUUID().toString(); - final Reference customerGroupReference = - Reference.of(CustomerGroup.referenceTypeId(), customerGroupId); - - final Customer oldCustomer = mock(Customer.class); - when(oldCustomer.getCustomerGroup()).thenReturn(customerGroupReference); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .build(); - - final Optional> result = buildSetCustomerGroupUpdateAction(oldCustomer, newCustomer); - assertThat(result).isPresent(); - assertThat(result).containsInstanceOf(SetCustomerGroup.class); - //Note: If the old value is set, but the new one is empty - the command will unset the customer group. - assertThat(((SetCustomerGroup) result.get()).getCustomerGroup()).isNull(); - } + private static Customer old; + private static CustomerDraft newSame; + private static CustomerDraft newDifferent; + + @BeforeEach + void setup() { + CustomerName oldCustomerName = + CustomerName.of("old-title", "old-firstName", "old-middleName", "old-lastName"); + + final String key = "key1"; + final String companyName = "companyName1"; + final String salutation = "salutation1"; + final String vatId = "vatId1"; + final String locale = "DE"; + final String birthDate = "1990-10-01"; + final String externalId = "externalId1"; + final String customerNumber = "1234"; + + old = mock(Customer.class); + when(old.getKey()).thenReturn(key); + when(old.getName()).thenReturn(oldCustomerName); + when(old.getEmail()).thenReturn("old-email"); + when(old.getFirstName()).thenReturn("old-firstName"); + when(old.getMiddleName()).thenReturn("old-middleName"); + when(old.getLastName()).thenReturn("old-lastName"); + when(old.getTitle()).thenReturn("old-title"); + when(old.getSalutation()).thenReturn(salutation); + when(old.getCustomerNumber()).thenReturn(customerNumber); + when(old.getExternalId()).thenReturn(externalId); + when(old.getCompanyName()).thenReturn(companyName); + when(old.getDateOfBirth()).thenReturn(LocalDate.parse(birthDate)); + when(old.getVatId()).thenReturn(vatId); + when(old.getLocale()).thenReturn(Locale.forLanguageTag(locale)); + + newSame = + CustomerDraftBuilder.of(oldCustomerName, "old-email", "oldPW") + .key(key) + .companyName(companyName) + .salutation(salutation) + .dateOfBirth(LocalDate.parse(birthDate)) + .locale(Locale.forLanguageTag(locale)) + .vatId(vatId) + .externalId(externalId) + .customerNumber(customerNumber) + .build(); + + CustomerName newCustomerName = + CustomerName.of("new-title", "new-firstName", "new-middleName", "new-lastName"); + + newDifferent = CustomerDraftBuilder.of(newCustomerName, "new-email", "newPW").build(); + } + + @Test + void buildChangeEmailUpdateAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = buildChangeEmailUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(ChangeEmail.class); + assertThat(result).contains(ChangeEmail.of(newDifferent.getEmail())); + } + + @Test + void buildChangeEmailUpdateAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildChangeEmailUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetFirstNameUpdateAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetFirstNameUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetFirstName.class); + assertThat(result).contains(SetFirstName.of(newDifferent.getFirstName())); + } + + @Test + void buildSetFirstNameUpdateAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetFirstNameUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetLastNameUpdateAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = buildSetLastNameUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetLastName.class); + assertThat(result).contains(SetLastName.of(newDifferent.getLastName())); + } + + @Test + void buildSetLastNameUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetLastNameUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetMiddleNameUpdateAction_withDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetMiddleNameUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetMiddleName.class); + assertThat(result).contains(SetMiddleName.of(newDifferent.getMiddleName())); + } + + @Test + void buildSetMiddleNameUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetMiddleNameUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetTitleUpdateAction_withDifferentValues_ShouldReturnAction() { + final Optional> result = buildSetTitleUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetTitle.class); + assertThat(result).contains(SetTitle.of(newDifferent.getTitle())); + } + + @Test + void buildSetTitleUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetTitleUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetSalutationUpdateAction_withDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetSalutationUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetSalutation.class); + assertThat(result).contains(SetSalutation.of(newDifferent.getSalutation())); + } + + @Test + void buildSetSalutationUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetSalutationUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void + buildSetCustomerNumberUpdateAction_withDifferentValues_ShouldNotReturnActionAndAddWarningCallback() { + final List warningMessages = new ArrayList<>(); + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)) + .warningCallback( + (exception, oldResource, newResource) -> + warningMessages.add(exception.getMessage())) + .build(); + + final Optional> result = + buildSetCustomerNumberUpdateAction(old, newDifferent, customerSyncOptions); + + assertThat(result).isEmpty(); + assertThat(warningMessages) + .containsExactly(format(CUSTOMER_NUMBER_EXISTS_WARNING, old.getKey(), "1234")); + } + + @Test + void buildSetCustomerNumberUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final List warningMessages = new ArrayList<>(); + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)) + .warningCallback( + (exception, oldResource, newResource) -> + warningMessages.add(exception.getMessage())) + .build(); + + final Optional> result = + buildSetCustomerNumberUpdateAction(old, newSame, customerSyncOptions); + + assertThat(result).isEmpty(); + assertThat(warningMessages).isEmpty(); + } + + @Test + void buildSetCustomerNumberUpdateAction_withEmptyOldValueAndANewValue_ShouldReturnAction() { + final List warningMessages = new ArrayList<>(); + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)) + .warningCallback( + (exception, oldResource, newResource) -> + warningMessages.add(exception.getMessage())) + .build(); + + Customer oldCustomer = mock(Customer.class); + when(oldCustomer.getCustomerNumber()).thenReturn(" "); + + CustomerDraft newCustomer = mock(CustomerDraft.class); + when(newCustomer.getCustomerNumber()).thenReturn("customer-number"); + + final Optional> result = + buildSetCustomerNumberUpdateAction(oldCustomer, newCustomer, customerSyncOptions); + + assertThat(result).containsInstanceOf(SetCustomerNumber.class); + assertThat(result).contains(SetCustomerNumber.of("customer-number")); + assertThat(warningMessages).isEmpty(); + } + + @Test + void buildSetCustomerNumberUpdateAction_withNullOldValueAndANewValue_ShouldReturnAction() { + final List warningMessages = new ArrayList<>(); + final CustomerSyncOptions customerSyncOptions = + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)) + .warningCallback( + (exception, oldResource, newResource) -> + warningMessages.add(exception.getMessage())) + .build(); + + Customer oldCustomer = mock(Customer.class); + when(oldCustomer.getCustomerNumber()).thenReturn(null); + + CustomerDraft newCustomer = mock(CustomerDraft.class); + when(newCustomer.getCustomerNumber()).thenReturn("customer-number"); + + final Optional> result = + buildSetCustomerNumberUpdateAction(oldCustomer, newCustomer, customerSyncOptions); + + assertThat(result).containsInstanceOf(SetCustomerNumber.class); + assertThat(result).contains(SetCustomerNumber.of("customer-number")); + assertThat(warningMessages).isEmpty(); + } + + @Test + void buildSetExternalIdUpdateAction_withDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetExternalIdUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetExternalId.class); + assertThat(result).contains(SetExternalId.of(newDifferent.getExternalId())); + } + + @Test + void buildSetExternalIdUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetExternalIdUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetCompanyNameUpdateAction_withDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetCompanyNameUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetCompanyName.class); + assertThat(result).contains(SetCompanyName.of(newDifferent.getCompanyName())); + } + + @Test + void buildSetCompanyNameUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetCompanyNameUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetDateOfBirthUpdateAction_withDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetDateOfBirthUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetDateOfBirth.class); + assertThat(result).contains(SetDateOfBirth.of(newDifferent.getDateOfBirth())); + } + + @Test + void buildSetDateOfBirthUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetDateOfBirthUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetVatIdUpdateAction_withDifferentValues_ShouldReturnAction() { + final Optional> result = buildSetVatIdUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetVatId.class); + assertThat(result).contains(SetVatId.of(newDifferent.getVatId())); + } + + @Test + void buildSetVatIdUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetVatIdUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetLocaleUpdateAction_withDifferentValues_ShouldReturnAction() { + final Optional> result = buildSetLocaleUpdateAction(old, newDifferent); + + assertThat(result).containsInstanceOf(SetLocale.class); + assertThat(result).contains(SetLocale.of(newDifferent.getLocale())); + } + + @Test + void buildSetLocaleUpdateAction_withSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetLocaleUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetCustomerGroupAction_WithSameReference_ShouldNotReturnAction() { + final String customerGroupId = UUID.randomUUID().toString(); + final Reference customerGroupReference = + Reference.of(CustomerGroup.referenceTypeId(), customerGroupId); + + final Customer oldCustomer = mock(Customer.class); + when(oldCustomer.getCustomerGroup()).thenReturn(customerGroupReference); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .customerGroup(ResourceIdentifier.ofId(customerGroupId)) + .build(); + + final Optional> result = + buildSetCustomerGroupUpdateAction(oldCustomer, newCustomer); + assertThat(result).isNotPresent(); + } + + @Test + void buildSetCustomerGroupAction_WithDifferentReference_ShouldReturnAction() { + final String customerGroupId = UUID.randomUUID().toString(); + final Reference customerGroupReference = + Reference.of(CustomerGroup.referenceTypeId(), customerGroupId); + + final Customer oldCustomer = mock(Customer.class); + when(oldCustomer.getCustomerGroup()).thenReturn(customerGroupReference); + + final String resolvedCustomerGroupId = UUID.randomUUID().toString(); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .customerGroup(ResourceIdentifier.ofId(resolvedCustomerGroupId)) + .build(); + + final Optional> result = + buildSetCustomerGroupUpdateAction(oldCustomer, newCustomer); + assertThat(result).isPresent(); + assertThat(result).containsInstanceOf(SetCustomerGroup.class); + assertThat(((SetCustomerGroup) result.get()).getCustomerGroup()) + .isEqualTo(Reference.of(CustomerGroup.referenceTypeId(), resolvedCustomerGroupId)); + } + + @Test + void buildSetCustomerGroupAction_WithOnlyNewReference_ShouldReturnAction() { + final Customer oldCustomer = mock(Customer.class); + final String newCustomerGroupId = UUID.randomUUID().toString(); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .customerGroup(ResourceIdentifier.ofId(newCustomerGroupId)) + .build(); + + final Optional> result = + buildSetCustomerGroupUpdateAction(oldCustomer, newCustomer); + assertThat(result).isPresent(); + assertThat(result).containsInstanceOf(SetCustomerGroup.class); + assertThat(((SetCustomerGroup) result.get()).getCustomerGroup()) + .isEqualTo(Reference.of(CustomerGroup.referenceTypeId(), newCustomerGroupId)); + } + + @Test + void buildSetCustomerGroupAction_WithoutNewReference_ShouldReturnUnsetAction() { + final String customerGroupId = UUID.randomUUID().toString(); + final Reference customerGroupReference = + Reference.of(CustomerGroup.referenceTypeId(), customerGroupId); + + final Customer oldCustomer = mock(Customer.class); + when(oldCustomer.getCustomerGroup()).thenReturn(customerGroupReference); + + final CustomerDraft newCustomer = CustomerDraftBuilder.of("email", "pass").build(); + + final Optional> result = + buildSetCustomerGroupUpdateAction(oldCustomer, newCustomer); + assertThat(result).isPresent(); + assertThat(result).containsInstanceOf(SetCustomerGroup.class); + // Note: If the old value is set, but the new one is empty - the command will unset the customer + // group. + assertThat(((SetCustomerGroup) result.get()).getCustomerGroup()).isNull(); + } } diff --git a/src/test/java/com/commercetools/sync/customers/utils/StoreUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/customers/utils/StoreUpdateActionUtilsTest.java index aa68091df4..e136fc619f 100644 --- a/src/test/java/com/commercetools/sync/customers/utils/StoreUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/customers/utils/StoreUpdateActionUtilsTest.java @@ -1,5 +1,13 @@ package com.commercetools.sync.customers.utils; +import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildStoreUpdateActions; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.customers.Customer; import io.sphere.sdk.customers.CustomerDraft; @@ -11,323 +19,291 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.stores.Store; import java.util.List; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static com.commercetools.sync.customers.utils.CustomerUpdateActionUtils.buildStoreUpdateActions; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - @SuppressWarnings("unchecked") public class StoreUpdateActionUtilsTest { - private Customer oldCustomer; + private Customer oldCustomer; + + @BeforeEach + void setup() { + oldCustomer = mock(Customer.class); + } + + @Test + void buildStoreUpdateActions_WithSameStores_ShouldNotReturnAction() { + + final KeyReference keyReference1 = mock(KeyReference.class); + when(keyReference1.getKey()).thenReturn("store-key"); + when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .stores(singletonList(ResourceIdentifier.ofKey("store-key"))) + .build(); + + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildStoreUpdateActions_WithNullOldStores_ShouldReturnOnlySetStoreAction() { + + when(oldCustomer.getStores()).thenReturn(null); + + final List> newStores = + singletonList(ResourceIdentifier.ofKey("store-key")); - @BeforeEach - void setup() { - oldCustomer = mock(Customer.class); - } + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(newStores).build(); - @Test - void buildStoreUpdateActions_WithSameStores_ShouldNotReturnAction() { + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - final KeyReference keyReference1 = mock(KeyReference.class); - when(keyReference1.getKey()).thenReturn("store-key"); - when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); + assertThat(updateActions).isNotEmpty().containsExactly(SetStores.of(newStores)); + } - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(singletonList(ResourceIdentifier.ofKey("store-key"))) - .build(); + @Test + void buildStoreUpdateActions_WithEmptyOldStores_ShouldReturnOnlySetStoreAction() { - final List> updateActions = buildStoreUpdateActions(oldCustomer, newCustomer); + when(oldCustomer.getStores()).thenReturn(emptyList()); - assertThat(updateActions).isEmpty(); - } + final List> newStores = + singletonList(ResourceIdentifier.ofKey("store-key")); - @Test - void buildStoreUpdateActions_WithNullOldStores_ShouldReturnOnlySetStoreAction() { + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(newStores).build(); - when(oldCustomer.getStores()).thenReturn(null); + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - final List> newStores = singletonList(ResourceIdentifier.ofKey("store-key")); + assertThat(updateActions).isNotEmpty().containsExactly(SetStores.of(newStores)); + } - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(newStores) - .build(); + @Test + void + buildStoreUpdateActions_WithEmptyOldStores_ShouldReturnOnlySetStoreWithoutNullReferencesInIt() { - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + when(oldCustomer.getStores()).thenReturn(emptyList()); - assertThat(updateActions) - .isNotEmpty() - .containsExactly(SetStores.of(newStores)); - } + final List> newStores = + asList(ResourceIdentifier.ofKey("store-key"), null); - @Test - void buildStoreUpdateActions_WithEmptyOldStores_ShouldReturnOnlySetStoreAction() { + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(newStores).build(); - when(oldCustomer.getStores()).thenReturn(emptyList()); + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - final List> newStores = singletonList(ResourceIdentifier.ofKey("store-key")); + assertThat(updateActions) + .isNotEmpty() + .containsExactly(SetStores.of(singletonList(ResourceIdentifier.ofKey("store-key")))); + } - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(newStores) - .build(); + @Test + void buildStoreUpdateActions_WithOnlyNullNewStores_ShouldNotReturnAction() { - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + when(oldCustomer.getStores()).thenReturn(emptyList()); - assertThat(updateActions) - .isNotEmpty() - .containsExactly(SetStores.of(newStores)); - } + final List> newStores = asList(null, null); - @Test - void buildStoreUpdateActions_WithEmptyOldStores_ShouldReturnOnlySetStoreWithoutNullReferencesInIt() { + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(newStores).build(); - when(oldCustomer.getStores()).thenReturn(emptyList()); + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - final List> newStores = - asList(ResourceIdentifier.ofKey("store-key"), null); + assertThat(updateActions).isEmpty(); + } - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(newStores) - .build(); + @Test + void buildStoreUpdateActions_WithBothNullStoreReferences_ShouldNotReturnAction() { - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + when(oldCustomer.getStores()).thenReturn(asList(null, null)); - assertThat(updateActions) - .isNotEmpty() - .containsExactly(SetStores.of(singletonList(ResourceIdentifier.ofKey("store-key")))); - } + final List> newStores = asList(null, null); - @Test - void buildStoreUpdateActions_WithOnlyNullNewStores_ShouldNotReturnAction() { + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(newStores).build(); - when(oldCustomer.getStores()).thenReturn(emptyList()); + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - final List> newStores = - asList(null, null); + assertThat(updateActions).isEmpty(); + } - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(newStores) - .build(); + @Test + void buildStoreUpdateActions_WithNullNewStores_ShouldReturnSetStoreActionWithUnset() { - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + final KeyReference keyReference1 = mock(KeyReference.class); + when(keyReference1.getKey()).thenReturn("store-key"); + when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); - assertThat(updateActions).isEmpty(); - } + final CustomerDraft newCustomer = CustomerDraftBuilder.of("email", "pass").stores(null).build(); - @Test - void buildStoreUpdateActions_WithBothNullStoreReferences_ShouldNotReturnAction() { + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - when(oldCustomer.getStores()).thenReturn(asList(null, null)); + assertThat(updateActions).isNotEmpty().containsExactly(SetStores.of(emptyList())); + } - final List> newStores = - asList(null, null); + @Test + void buildStoreUpdateActions_WithEmptyNewStores_ShouldReturnSetStoreActionWithUnset() { - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(newStores) - .build(); + final KeyReference keyReference1 = mock(KeyReference.class); + when(keyReference1.getKey()).thenReturn("store-key"); + when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(emptyList()).build(); - assertThat(updateActions).isEmpty(); - } + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - @Test - void buildStoreUpdateActions_WithNullNewStores_ShouldReturnSetStoreActionWithUnset() { + assertThat(updateActions).isNotEmpty().containsExactly(SetStores.of(emptyList())); + } - final KeyReference keyReference1 = mock(KeyReference.class); - when(keyReference1.getKey()).thenReturn("store-key"); - when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); + @Test + void buildStoreUpdateActions_WithBothNullStores_ShouldNotReturnAction() { - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(null) - .build(); + when(oldCustomer.getStores()).thenReturn(null); - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + final CustomerDraft newCustomer = CustomerDraftBuilder.of("email", "pass").stores(null).build(); - assertThat(updateActions) - .isNotEmpty() - .containsExactly(SetStores.of(emptyList())); - } + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - @Test - void buildStoreUpdateActions_WithEmptyNewStores_ShouldReturnSetStoreActionWithUnset() { + assertThat(updateActions).isEmpty(); + } - final KeyReference keyReference1 = mock(KeyReference.class); - when(keyReference1.getKey()).thenReturn("store-key"); - when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); + @Test + void buildStoreUpdateActions_WithBothEmptyStores_ShouldNotReturnAction() { - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(emptyList()) - .build(); + when(oldCustomer.getStores()).thenReturn(emptyList()); - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(emptyList()).build(); - assertThat(updateActions) - .isNotEmpty() - .containsExactly(SetStores.of(emptyList())); - } + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - @Test - void buildStoreUpdateActions_WithBothNullStores_ShouldNotReturnAction() { + assertThat(updateActions).isEmpty(); + } - when(oldCustomer.getStores()).thenReturn(null); + @Test + void buildStoreUpdateActions_WithNewStores_ShouldReturnAddStoreActions() { - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(null) - .build(); + final KeyReference keyReference1 = mock(KeyReference.class); + when(keyReference1.getKey()).thenReturn("store-key1"); + when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + final List> newStores = + asList( + ResourceIdentifier.ofKey("store-key1"), + ResourceIdentifier.ofKey("store-key2"), + ResourceIdentifier.ofKey("store-key3")); - assertThat(updateActions).isEmpty(); - } + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(newStores).build(); - @Test - void buildStoreUpdateActions_WithBothEmptyStores_ShouldNotReturnAction() { + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - when(oldCustomer.getStores()).thenReturn(emptyList()); + assertThat(updateActions) + .isNotEmpty() + .containsExactly(AddStore.of(newStores.get(1)), AddStore.of(newStores.get(2))); + } - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(emptyList()) - .build(); + @Test + void buildStoreUpdateActions_WithLessStores_ShouldReturnRemoveStoreActions() { - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + final KeyReference keyReference1 = mock(KeyReference.class); + when(keyReference1.getKey()).thenReturn("store-key1"); + final KeyReference keyReference2 = mock(KeyReference.class); + when(keyReference2.getKey()).thenReturn("store-key2"); + final KeyReference keyReference3 = mock(KeyReference.class); + when(keyReference3.getKey()).thenReturn("store-key3"); - assertThat(updateActions).isEmpty(); - } + final List> keyReferences = + asList(keyReference1, keyReference2, keyReference3); + when(oldCustomer.getStores()).thenReturn(keyReferences); - @Test - void buildStoreUpdateActions_WithNewStores_ShouldReturnAddStoreActions() { + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass") + .stores(singletonList(ResourceIdentifier.ofKey("store-key1"))) + .build(); - final KeyReference keyReference1 = mock(KeyReference.class); - when(keyReference1.getKey()).thenReturn("store-key1"); - when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); - final List> newStores = - asList(ResourceIdentifier.ofKey("store-key1"), ResourceIdentifier.ofKey("store-key2"), - ResourceIdentifier.ofKey("store-key3")); + assertThat(updateActions) + .isNotEmpty() + .containsExactly( + RemoveStore.of(ResourceIdentifier.ofKey("store-key2")), + RemoveStore.of(ResourceIdentifier.ofKey("store-key3"))); + } - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(newStores) - .build(); + @Test + void buildStoreUpdateActions_WithMixedStores_ShouldReturnSetStoresAction() { - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); + final KeyReference keyReference1 = mock(KeyReference.class); + when(keyReference1.getKey()).thenReturn("store-key1"); + final KeyReference keyReference3 = mock(KeyReference.class); + when(keyReference3.getKey()).thenReturn("store-key3"); + final KeyReference keyReference4 = mock(KeyReference.class); + when(keyReference4.getKey()).thenReturn("store-key4"); - assertThat(updateActions) - .isNotEmpty() - .containsExactly(AddStore.of(newStores.get(1)), AddStore.of(newStores.get(2))); - } + final List> keyReferences = + asList(keyReference1, keyReference3, keyReference4); + when(oldCustomer.getStores()).thenReturn(keyReferences); - @Test - void buildStoreUpdateActions_WithLessStores_ShouldReturnRemoveStoreActions() { + final List> newStores = + asList( + ResourceIdentifier.ofKey("store-key1"), + ResourceIdentifier.ofKey("store-key2"), + ResourceIdentifier.ofKey("store-key3")); - final KeyReference keyReference1 = mock(KeyReference.class); - when(keyReference1.getKey()).thenReturn("store-key1"); - final KeyReference keyReference2 = mock(KeyReference.class); - when(keyReference2.getKey()).thenReturn("store-key2"); - final KeyReference keyReference3 = mock(KeyReference.class); - when(keyReference3.getKey()).thenReturn("store-key3"); - - final List> keyReferences = asList(keyReference1, keyReference2, keyReference3); - when(oldCustomer.getStores()).thenReturn(keyReferences); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(singletonList(ResourceIdentifier.ofKey("store-key1"))) - .build(); - - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions) - .isNotEmpty() - .containsExactly( - RemoveStore.of(ResourceIdentifier.ofKey("store-key2")), - RemoveStore.of(ResourceIdentifier.ofKey("store-key3"))); - } - - @Test - void buildStoreUpdateActions_WithMixedStores_ShouldReturnSetStoresAction() { + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(newStores).build(); - final KeyReference keyReference1 = mock(KeyReference.class); - when(keyReference1.getKey()).thenReturn("store-key1"); - final KeyReference keyReference3 = mock(KeyReference.class); - when(keyReference3.getKey()).thenReturn("store-key3"); - final KeyReference keyReference4 = mock(KeyReference.class); - when(keyReference4.getKey()).thenReturn("store-key4"); - - final List> keyReferences = asList(keyReference1, keyReference3, keyReference4); - when(oldCustomer.getStores()).thenReturn(keyReferences); - - final List> newStores = - asList(ResourceIdentifier.ofKey("store-key1"), ResourceIdentifier.ofKey("store-key2"), - ResourceIdentifier.ofKey("store-key3")); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(newStores) - .build(); - - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions) - .isNotEmpty() - .containsExactly(SetStores.of( - asList(ResourceIdentifier.ofKey("store-key1"), - ResourceIdentifier.ofKey("store-key2"), - ResourceIdentifier.ofKey("store-key3")))); - } - - @Test - void buildStoreUpdateActions_WithNewStoresWithOnlyIdReference_ShouldReturnAddStoreActions() { - - final KeyReference keyReference1 = mock(KeyReference.class); - when(keyReference1.getKey()).thenReturn("store-key1"); - when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); - - final List> newStores = - asList(ResourceIdentifier.ofKey("store-key1"), - ResourceIdentifier.ofId("store-id2"), - ResourceIdentifier.ofId("store-id3")); - - final CustomerDraft newCustomer = - CustomerDraftBuilder.of("email", "pass") - .stores(newStores) - .build(); - - final List> updateActions = - buildStoreUpdateActions(oldCustomer, newCustomer); - - assertThat(updateActions) - .isNotEmpty() - .containsExactly(AddStore.of(newStores.get(1)), AddStore.of(newStores.get(2))); - } + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions) + .isNotEmpty() + .containsExactly( + SetStores.of( + asList( + ResourceIdentifier.ofKey("store-key1"), + ResourceIdentifier.ofKey("store-key2"), + ResourceIdentifier.ofKey("store-key3")))); + } + + @Test + void buildStoreUpdateActions_WithNewStoresWithOnlyIdReference_ShouldReturnAddStoreActions() { + + final KeyReference keyReference1 = mock(KeyReference.class); + when(keyReference1.getKey()).thenReturn("store-key1"); + when(oldCustomer.getStores()).thenReturn(singletonList(keyReference1)); + + final List> newStores = + asList( + ResourceIdentifier.ofKey("store-key1"), + ResourceIdentifier.ofId("store-id2"), + ResourceIdentifier.ofId("store-id3")); + + final CustomerDraft newCustomer = + CustomerDraftBuilder.of("email", "pass").stores(newStores).build(); + + final List> updateActions = + buildStoreUpdateActions(oldCustomer, newCustomer); + + assertThat(updateActions) + .isNotEmpty() + .containsExactly(AddStore.of(newStores.get(1)), AddStore.of(newStores.get(2))); + } } diff --git a/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsBuilderTest.java index 9058f8de6a..5faa6100ec 100644 --- a/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsBuilderTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.customobjects; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -9,205 +14,193 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.CustomObjectDraft; -import org.junit.jupiter.api.Test; - import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class CustomObjectSyncOptionsBuilderTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private CustomObjectSyncOptionsBuilder customObjectSyncOptionsBuilder = - CustomObjectSyncOptionsBuilder.of(CTP_CLIENT); - - - @Test - void of_WithClient_ShouldCreateCustomObjectSyncOptionsBuilder() { - final CustomObjectSyncOptionsBuilder builder = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT); - assertThat(builder).isNotNull(); - } - - @Test - void build_WithClient_ShouldBuildSyncOptions() { - final CustomObjectSyncOptions customObjectSyncOptions = customObjectSyncOptionsBuilder.build(); - assertThat(customObjectSyncOptions).isNotNull(); - assertThat(customObjectSyncOptions.getBeforeUpdateCallback()).isNull(); - assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNull(); - assertThat(customObjectSyncOptions.getErrorCallback()).isNull(); - assertThat(customObjectSyncOptions.getWarningCallback()).isNull(); - assertThat(customObjectSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); - assertThat(customObjectSyncOptions.getBatchSize()).isEqualTo(CustomObjectSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(customObjectSyncOptions.getCacheSize()).isEqualTo(10_000); - } - - @Test - void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { - customObjectSyncOptionsBuilder.beforeCreateCallback((newCustomObject) -> null); - final CustomObjectSyncOptions customObjectSyncOptions = customObjectSyncOptionsBuilder.build(); - assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNotNull(); - } - - @Test - void errorCallBack_WithCallBack_ShouldSetCallBack() { - final QuadConsumer>, - Optional>, List>>> - mockErrorCallBack = (exception, newResource, oldResource, updateActions) -> { }; - customObjectSyncOptionsBuilder.errorCallback(mockErrorCallBack); - - final CustomObjectSyncOptions customObjectSyncOptions = customObjectSyncOptionsBuilder.build(); - assertThat(customObjectSyncOptions.getErrorCallback()).isNotNull(); - } - - @Test - void warningCallBack_WithCallBack_ShouldSetCallBack() { - final TriConsumer>, Optional>> mockWarningCallBack = - (exception, newResource, oldResource) -> { }; - customObjectSyncOptionsBuilder.warningCallback(mockWarningCallBack); - final CustomObjectSyncOptions cutomObjectSyncOptions = customObjectSyncOptionsBuilder.build(); - assertThat(cutomObjectSyncOptions.getWarningCallback()).isNotNull(); - } - - @Test - void getThis_ShouldReturnCorrectInstance() { - final CustomObjectSyncOptionsBuilder instance = customObjectSyncOptionsBuilder.getThis(); - assertThat(instance).isNotNull(); - assertThat(instance).isInstanceOf(CustomObjectSyncOptionsBuilder.class); - assertThat(instance).isEqualTo(customObjectSyncOptionsBuilder); - } - - @Test - void customObjectSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder - .of(CTP_CLIENT) + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private CustomObjectSyncOptionsBuilder customObjectSyncOptionsBuilder = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateCustomObjectSyncOptionsBuilder() { + final CustomObjectSyncOptionsBuilder builder = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT); + assertThat(builder).isNotNull(); + } + + @Test + void build_WithClient_ShouldBuildSyncOptions() { + final CustomObjectSyncOptions customObjectSyncOptions = customObjectSyncOptionsBuilder.build(); + assertThat(customObjectSyncOptions).isNotNull(); + assertThat(customObjectSyncOptions.getBeforeUpdateCallback()).isNull(); + assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNull(); + assertThat(customObjectSyncOptions.getErrorCallback()).isNull(); + assertThat(customObjectSyncOptions.getWarningCallback()).isNull(); + assertThat(customObjectSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); + assertThat(customObjectSyncOptions.getBatchSize()) + .isEqualTo(CustomObjectSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + assertThat(customObjectSyncOptions.getCacheSize()).isEqualTo(10_000); + } + + @Test + void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { + customObjectSyncOptionsBuilder.beforeCreateCallback((newCustomObject) -> null); + final CustomObjectSyncOptions customObjectSyncOptions = customObjectSyncOptionsBuilder.build(); + assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNotNull(); + } + + @Test + void errorCallBack_WithCallBack_ShouldSetCallBack() { + final QuadConsumer< + SyncException, + Optional>, + Optional>, + List>>> + mockErrorCallBack = (exception, newResource, oldResource, updateActions) -> {}; + customObjectSyncOptionsBuilder.errorCallback(mockErrorCallBack); + + final CustomObjectSyncOptions customObjectSyncOptions = customObjectSyncOptionsBuilder.build(); + assertThat(customObjectSyncOptions.getErrorCallback()).isNotNull(); + } + + @Test + void warningCallBack_WithCallBack_ShouldSetCallBack() { + final TriConsumer< + SyncException, Optional>, Optional>> + mockWarningCallBack = (exception, newResource, oldResource) -> {}; + customObjectSyncOptionsBuilder.warningCallback(mockWarningCallBack); + final CustomObjectSyncOptions cutomObjectSyncOptions = customObjectSyncOptionsBuilder.build(); + assertThat(cutomObjectSyncOptions.getWarningCallback()).isNotNull(); + } + + @Test + void getThis_ShouldReturnCorrectInstance() { + final CustomObjectSyncOptionsBuilder instance = customObjectSyncOptionsBuilder.getThis(); + assertThat(instance).isNotNull(); + assertThat(instance).isInstanceOf(CustomObjectSyncOptionsBuilder.class); + assertThat(instance).isEqualTo(customObjectSyncOptionsBuilder); + } + + @Test + void customObjectSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) .batchSize(30) .beforeCreateCallback((newCustomObject) -> null) .beforeUpdateCallback((updateActions, newCustomObject, oldCustomObject) -> emptyList()) .build(); - assertThat(customObjectSyncOptions).isNotNull(); - } - - @Test - void batchSize_WithPositiveValue_ShouldSetBatchSize() { - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(10) - .build(); - assertThat(customObjectSyncOptions.getBatchSize()).isEqualTo(10); - } - - @Test - void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final CustomObjectSyncOptions customObjectSyncOptionsWithZeroBatchSize = - CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(0) - .build(); - assertThat(customObjectSyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(CustomObjectSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - final CustomObjectSyncOptions customObjectSyncOptionsWithNegativeBatchSize = CustomObjectSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(-100) + assertThat(customObjectSyncOptions).isNotNull(); + } + + @Test + void batchSize_WithPositiveValue_ShouldSetBatchSize() { + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); + assertThat(customObjectSyncOptions.getBatchSize()).isEqualTo(10); + } + + @Test + void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final CustomObjectSyncOptions customObjectSyncOptionsWithZeroBatchSize = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); + assertThat(customObjectSyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(CustomObjectSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + final CustomObjectSyncOptions customObjectSyncOptionsWithNegativeBatchSize = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); + assertThat(customObjectSyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(CustomObjectSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + + @Test + void + applyBeforeUpdateCallBack_WithNullReturnCallbackAndEmptyUpdateActions_ShouldReturnEmptyList() { + final TriFunction< + List>>, + CustomObjectDraft, + CustomObject, + List>>> + beforeUpdateCallback = (updateActions, newCustomObject, oldCustomObject) -> null; + + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) .build(); - assertThat(customObjectSyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(CustomObjectSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } - - @Test - void applyBeforeUpdateCallBack_WithNullReturnCallbackAndEmptyUpdateActions_ShouldReturnEmptyList() { - final TriFunction>>, CustomObjectDraft, - CustomObject, List>>> - beforeUpdateCallback = (updateActions, newCustomObject, oldCustomObject) -> null; - - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(customObjectSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List>> updateActions = Collections.emptyList(); - - final List>> filteredList = - customObjectSyncOptions.applyBeforeUpdateCallback( - updateActions, mock(CustomObjectDraft.class), mock(CustomObject.class)); - assertThat(filteredList).isEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { - final Function, CustomObjectDraft> draftFunction = - customObjectDraft -> CustomObjectDraft.ofUnversionedUpsert( + assertThat(customObjectSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List>> updateActions = Collections.emptyList(); + + final List>> filteredList = + customObjectSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CustomObjectDraft.class), mock(CustomObject.class)); + assertThat(filteredList).isEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { + final Function, CustomObjectDraft> draftFunction = + customObjectDraft -> + CustomObjectDraft.ofUnversionedUpsert( customObjectDraft.getContainer() + "_filteredContainer", customObjectDraft.getKey() + "_filteredKey", (JsonNode) customObjectDraft.getValue()); - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback( - draftFunction) - .build(); - assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNotNull(); - final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); - when(resourceDraft.getContainer()).thenReturn("myContainer"); - final Optional> filteredDraft = - customObjectSyncOptions.applyBeforeCreateCallback(resourceDraft); - assertThat(filteredDraft).isNotEmpty(); - assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filteredKey"); - assertThat(filteredDraft.get().getContainer()).isEqualTo("myContainer_filteredContainer"); - } - - @Test - void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).build(); - assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNull(); - final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); - final Optional> filteredDraft = - customObjectSyncOptions.applyBeforeCreateCallback(resourceDraft); - assertThat(filteredDraft).containsSame(resourceDraft); - } - - @Test - void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { - final Function, CustomObjectDraft> draftFunction = - customObjectDraft -> null; - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback( - draftFunction) - .build(); - assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNotNull(); - final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); - final Optional> filteredDraft = - customObjectSyncOptions.applyBeforeCreateCallback(resourceDraft); - assertThat(filteredDraft).isEmpty(); - } - - @Test - void cacheSize_WithPositiveValue_ShouldSetCacheSize() { - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(10) - .build(); - assertThat(customObjectSyncOptions.getCacheSize()).isEqualTo(10); - } - - @Test - void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final CustomObjectSyncOptions customObjectSyncOptionsWithZeroCacheSize = - CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(0) - .build(); - assertThat(customObjectSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - - final CustomObjectSyncOptions customObjectSyncOptionsWithNegativeCacheSize = CustomObjectSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(-100) - .build(); - assertThat(customObjectSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } - + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNotNull(); + final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); + when(resourceDraft.getContainer()).thenReturn("myContainer"); + final Optional> filteredDraft = + customObjectSyncOptions.applyBeforeCreateCallback(resourceDraft); + assertThat(filteredDraft).isNotEmpty(); + assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filteredKey"); + assertThat(filteredDraft.get().getContainer()).isEqualTo("myContainer_filteredContainer"); + } + + @Test + void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNull(); + final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); + final Optional> filteredDraft = + customObjectSyncOptions.applyBeforeCreateCallback(resourceDraft); + assertThat(filteredDraft).containsSame(resourceDraft); + } + + @Test + void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { + final Function, CustomObjectDraft> draftFunction = + customObjectDraft -> null; + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(customObjectSyncOptions.getBeforeCreateCallback()).isNotNull(); + final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); + final Optional> filteredDraft = + customObjectSyncOptions.applyBeforeCreateCallback(resourceDraft); + assertThat(filteredDraft).isEmpty(); + } + + @Test + void cacheSize_WithPositiveValue_ShouldSetCacheSize() { + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); + assertThat(customObjectSyncOptions.getCacheSize()).isEqualTo(10); + } + + @Test + void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final CustomObjectSyncOptions customObjectSyncOptionsWithZeroCacheSize = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); + assertThat(customObjectSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); + + final CustomObjectSyncOptions customObjectSyncOptionsWithNegativeCacheSize = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); + assertThat(customObjectSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsTest.java b/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsTest.java index 4626004ca4..40573db119 100644 --- a/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsTest.java +++ b/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncOptionsTest.java @@ -1,20 +1,6 @@ package com.commercetools.sync.customobjects; -import com.commercetools.sync.commons.utils.TriFunction; - -import com.fasterxml.jackson.databind.JsonNode; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.customobjects.CustomObject; -import io.sphere.sdk.customobjects.CustomObjectDraft; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - import static java.util.Collections.emptyList; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.any; @@ -23,120 +9,134 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.commercetools.sync.commons.utils.TriFunction; +import com.fasterxml.jackson.databind.JsonNode; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.customobjects.CustomObject; +import io.sphere.sdk.customobjects.CustomObjectDraft; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import org.junit.jupiter.api.Test; class CustomObjectSyncOptionsTest { - private static SphereClient CTP_CLIENT = mock(SphereClient.class); - - private interface MockTriFunction extends - TriFunction>>, - CustomObjectDraft, CustomObject, List>>> { - } - - @Test - void applyBeforeUpdateCallback_WithNullCallbackAndEmptyUpdateActions_ShouldReturnIdenticalList() { - final CustomObjectSyncOptions customObjectSyncOptions = - CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).build(); - - final List>> updateActions = emptyList(); - - final List>> filteredList = - customObjectSyncOptions.applyBeforeUpdateCallback(updateActions, - mock(CustomObjectDraft.class), mock(CustomObject.class)); - - assertThat(filteredList).isSameAs(updateActions); - } - - - @Test - void applyBeforeUpdateCallback_WithNullReturnCallbackAndEmptyUpdateActions_ShouldReturnEmptyList() { - final TriFunction>>, CustomObjectDraft, - CustomObject, List>>> - beforeUpdateCallback = (updateActions, newCustomObject, oldCustomObject) -> null; - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - final List>> updateActions = emptyList(); - - final List>> filteredList = - customObjectSyncOptions.applyBeforeUpdateCallback( - updateActions, mock(CustomObjectDraft.class), mock(CustomObject.class)); - - assertAll( - () -> assertThat(filteredList).isEqualTo(updateActions), - () -> assertThat(filteredList).isEmpty() - ); - } - - - @Test - void applyBeforeUpdateCallback_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final CustomObjectSyncOptionsTest.MockTriFunction beforeUpdateCallback = - mock(CustomObjectSyncOptionsTest.MockTriFunction.class); - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - - final List>> filteredList = - customObjectSyncOptions.applyBeforeUpdateCallback(emptyList(), - mock(CustomObjectDraft.class), mock(CustomObject.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - - @Test - void applyBeforeCreateCallback_WithCallback_ShouldReturnFilteredDraft() { - - final Function, CustomObjectDraft> draftFunction = - customObjectDraft -> CustomObjectDraft.ofUnversionedUpsert( + private static SphereClient CTP_CLIENT = mock(SphereClient.class); + + private interface MockTriFunction + extends TriFunction< + List>>, + CustomObjectDraft, + CustomObject, + List>>> {} + + @Test + void applyBeforeUpdateCallback_WithNullCallbackAndEmptyUpdateActions_ShouldReturnIdenticalList() { + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).build(); + + final List>> updateActions = emptyList(); + + final List>> filteredList = + customObjectSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CustomObjectDraft.class), mock(CustomObject.class)); + + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void + applyBeforeUpdateCallback_WithNullReturnCallbackAndEmptyUpdateActions_ShouldReturnEmptyList() { + final TriFunction< + List>>, + CustomObjectDraft, + CustomObject, + List>>> + beforeUpdateCallback = (updateActions, newCustomObject, oldCustomObject) -> null; + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + final List>> updateActions = emptyList(); + + final List>> filteredList = + customObjectSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(CustomObjectDraft.class), mock(CustomObject.class)); + + assertAll( + () -> assertThat(filteredList).isEqualTo(updateActions), + () -> assertThat(filteredList).isEmpty()); + } + + @Test + void applyBeforeUpdateCallback_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final CustomObjectSyncOptionsTest.MockTriFunction beforeUpdateCallback = + mock(CustomObjectSyncOptionsTest.MockTriFunction.class); + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + + final List>> filteredList = + customObjectSyncOptions.applyBeforeUpdateCallback( + emptyList(), mock(CustomObjectDraft.class), mock(CustomObject.class)); + + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeCreateCallback_WithCallback_ShouldReturnFilteredDraft() { + + final Function, CustomObjectDraft> draftFunction = + customObjectDraft -> + CustomObjectDraft.ofUnversionedUpsert( customObjectDraft.getContainer() + "_filteredContainer", - customObjectDraft.getKey() + "_filteredKey", customObjectDraft.getValue()); - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback( - draftFunction) - .build(); - final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); - when(resourceDraft.getContainer()).thenReturn("myContainer"); - - final Optional> filteredDraft = customObjectSyncOptions - .applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).hasValueSatisfying(customObjectDraft -> - assertAll( - () -> assertThat(customObjectDraft.getKey()).isEqualTo("myKey_filteredKey"), - () -> assertThat(customObjectDraft.getContainer()).isEqualTo("myContainer_filteredContainer") - )); - } - - @Test - void applyBeforeCreateCallback_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).build(); - final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); - - final Optional> filteredDraft = customObjectSyncOptions - .applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).containsSame(resourceDraft); - } - - @Test - void applyBeforeCreateCallback_WithCallbackReturningNull_ShouldReturnEmptyOptional() { - final Function, CustomObjectDraft> draftFunction = - customObjectDraft -> null; - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback( - draftFunction) - .build(); - final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); - - final Optional> filteredDraft = customObjectSyncOptions - .applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isEmpty(); - } + customObjectDraft.getKey() + "_filteredKey", + customObjectDraft.getValue()); + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); + when(resourceDraft.getContainer()).thenReturn("myContainer"); + + final Optional> filteredDraft = + customObjectSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft) + .hasValueSatisfying( + customObjectDraft -> + assertAll( + () -> assertThat(customObjectDraft.getKey()).isEqualTo("myKey_filteredKey"), + () -> + assertThat(customObjectDraft.getContainer()) + .isEqualTo("myContainer_filteredContainer"))); + } + + @Test + void applyBeforeCreateCallback_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).build(); + final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); + + final Optional> filteredDraft = + customObjectSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).containsSame(resourceDraft); + } + + @Test + void applyBeforeCreateCallback_WithCallbackReturningNull_ShouldReturnEmptyOptional() { + final Function, CustomObjectDraft> draftFunction = + customObjectDraft -> null; + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + final CustomObjectDraft resourceDraft = mock(CustomObjectDraft.class); + + final Optional> filteredDraft = + customObjectSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncTest.java b/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncTest.java index 6ac53fe7cf..64f181c973 100644 --- a/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncTest.java +++ b/src/test/java/com/commercetools/sync/customobjects/CustomObjectSyncTest.java @@ -1,26 +1,5 @@ package com.commercetools.sync.customobjects; -import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; -import com.commercetools.sync.customobjects.helpers.CustomObjectSyncStatistics; -import com.commercetools.sync.services.CustomObjectService; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import io.sphere.sdk.client.ConcurrentModificationException; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.customobjects.CustomObject; -import io.sphere.sdk.customobjects.CustomObjectDraft; -import io.sphere.sdk.models.SphereException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.CompletionException; - import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; import static java.lang.String.format; import static java.util.Collections.emptyList; @@ -39,458 +18,523 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; +import com.commercetools.sync.customobjects.helpers.CustomObjectSyncStatistics; +import com.commercetools.sync.services.CustomObjectService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import io.sphere.sdk.client.ConcurrentModificationException; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.customobjects.CustomObject; +import io.sphere.sdk.customobjects.CustomObjectDraft; +import io.sphere.sdk.models.SphereException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletionException; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class CustomObjectSyncTest { - private CustomObjectDraft newCustomObjectDraft; + private CustomObjectDraft newCustomObjectDraft; - @BeforeEach - void setup() { - newCustomObjectDraft = CustomObjectDraft - .ofUnversionedUpsert("someContainer", "someKey", - JsonNodeFactory.instance.objectNode().put("json-field", "json-value")); - } + @BeforeEach + void setup() { + newCustomObjectDraft = + CustomObjectDraft.ofUnversionedUpsert( + "someContainer", + "someKey", + JsonNodeFactory.instance.objectNode().put("json-field", "json-value")); + } - @Test - void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); + @Test + void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(errorMessages, exceptions); + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(errorMessages, exceptions); - final CustomObjectService mockCustomObjectService = mock(CustomObjectService.class); + final CustomObjectService mockCustomObjectService = mock(CustomObjectService.class); - when(mockCustomObjectService.fetchMatchingCustomObjects(anySet())) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - final CustomObjectSync customObjectSync = - new CustomObjectSync(spyCustomObjectSyncOptions, mockCustomObjectService); + when(mockCustomObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); - // test - final CustomObjectSyncStatistics customObjectSyncStatistics = customObjectSync - .sync(singletonList(newCustomObjectDraft)) - .toCompletableFuture().join(); - - // assertion - assertThat(errorMessages) - .hasSize(1).singleElement().asString() - .isEqualTo("Failed to fetch existing custom objects with keys: " - + "'[someContainer|someKey]'."); - - assertThat(exceptions) - .hasSize(1).singleElement().isInstanceOfSatisfying(Throwable.class, throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + final CustomObjectSync customObjectSync = + new CustomObjectSync(spyCustomObjectSyncOptions, mockCustomObjectService); + + // test + final CustomObjectSyncStatistics customObjectSyncStatistics = + customObjectSync.sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); + + // assertion + assertThat(errorMessages) + .hasSize(1) + .singleElement() + .asString() + .isEqualTo( + "Failed to fetch existing custom objects with keys: " + "'[someContainer|someKey]'."); + + assertThat(exceptions) + .hasSize(1) + .singleElement() + .isInstanceOfSatisfying( + Throwable.class, + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); }); - assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback_ShouldNotCallBeforeUpdateCallback() { - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(emptyList(),emptyList()); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())).thenReturn(completedFuture(emptySet())); - when(customObjectService.upsertCustomObject(any())).thenReturn(completedFuture(Optional.empty())); - - // test + assertThat(customObjectSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void + sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback_ShouldNotCallBeforeUpdateCallback() { + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(emptyList(), emptyList()); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(emptySet())); + when(customObjectService.upsertCustomObject(any())) + .thenReturn(completedFuture(Optional.empty())); + + // test + new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) + .sync(singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + + // assertion + verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); + verify(spyCustomObjectSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void + sync_WithOnlyDraftsToUpdate_ShouldCallBeforeCreateCallback_ShouldNotCallBeforeUpdateCallback() { + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(emptyList(), emptyList()); + + final CustomObject mockedExistingCustomObject = mock(CustomObject.class); + when(mockedExistingCustomObject.getKey()).thenReturn(newCustomObjectDraft.getKey()); + when(mockedExistingCustomObject.getContainer()).thenReturn("differentContainer"); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(singleton(mockedExistingCustomObject))); + when(customObjectService.upsertCustomObject(any())) + .thenReturn(completedFuture(Optional.of(mockedExistingCustomObject))); + + // test + new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) + .sync(singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + + // assertion + verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); + verify(spyCustomObjectSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void sync_WithSameIdentifiersAndDifferentValues_ShouldUpdateSuccessfully() { + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(emptyList(), emptyList()); + + final CustomObject existingCustomObject = mock(CustomObject.class); + when(existingCustomObject.getContainer()).thenReturn("someContainer"); + when(existingCustomObject.getKey()).thenReturn("someKey"); + when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); + + final CustomObject updatedCustomObject = mock(CustomObject.class); + when(updatedCustomObject.getContainer()).thenReturn("someContainer"); + when(updatedCustomObject.getKey()).thenReturn("someKey"); + when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); + + final Set> existingCustomObjectSet = + new HashSet>(); + existingCustomObjectSet.add(existingCustomObject); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(existingCustomObjectSet)); + when(customObjectService.upsertCustomObject(any())) + .thenReturn(completedFuture(Optional.of(updatedCustomObject))); + + // test + CustomObjectSyncStatistics syncStatistics = new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) - .sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); - - // assertion - verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); - verify(spyCustomObjectSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - @Test - void sync_WithOnlyDraftsToUpdate_ShouldCallBeforeCreateCallback_ShouldNotCallBeforeUpdateCallback() { - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(emptyList(), emptyList()); - - final CustomObject mockedExistingCustomObject = mock(CustomObject.class); - when(mockedExistingCustomObject.getKey()).thenReturn(newCustomObjectDraft.getKey()); - when(mockedExistingCustomObject.getContainer()).thenReturn("differentContainer"); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())) - .thenReturn(completedFuture(singleton(mockedExistingCustomObject))); - when(customObjectService.upsertCustomObject(any())) - .thenReturn(completedFuture(Optional.of(mockedExistingCustomObject))); - - // test + .sync(singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + + // assertion + assertAll( + () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0)); + } + + @Test + void sync_WithSameIdentifiersAndIdenticalValues_ShouldProcessedAndNotUpdated() { + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(emptyList(), emptyList()); + + final CustomObject existingCustomObject = mock(CustomObject.class); + when(existingCustomObject.getContainer()).thenReturn("someContainer"); + when(existingCustomObject.getKey()).thenReturn("someKey"); + when(existingCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); + + final CustomObject updatedCustomObject = mock(CustomObject.class); + when(updatedCustomObject.getContainer()).thenReturn("someContainer"); + when(updatedCustomObject.getKey()).thenReturn("someKey"); + when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); + + final Set> existingCustomObjectSet = + new HashSet>(); + existingCustomObjectSet.add(existingCustomObject); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(existingCustomObjectSet)); + when(customObjectService.upsertCustomObject(any())) + .thenReturn(completedFuture(Optional.of(updatedCustomObject))); + + // test + CustomObjectSyncStatistics syncStatistics = new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) - .sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); - - // assertion - verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); - verify(spyCustomObjectSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - @Test - void sync_WithSameIdentifiersAndDifferentValues_ShouldUpdateSuccessfully() { - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(emptyList(), emptyList()); - - final CustomObject existingCustomObject = mock(CustomObject.class); - when(existingCustomObject.getContainer()).thenReturn("someContainer"); - when(existingCustomObject.getKey()).thenReturn("someKey"); - when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); - - final CustomObject updatedCustomObject = mock(CustomObject.class); - when(updatedCustomObject.getContainer()).thenReturn("someContainer"); - when(updatedCustomObject.getKey()).thenReturn("someKey"); - when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); - - final Set> existingCustomObjectSet = new HashSet>(); - existingCustomObjectSet.add(existingCustomObject); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())) - .thenReturn(completedFuture(existingCustomObjectSet)); - when(customObjectService.upsertCustomObject(any())) - .thenReturn(completedFuture(Optional.of(updatedCustomObject))); - - // test - CustomObjectSyncStatistics syncStatistics = - new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) - .sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); - - // assertion - assertAll( - () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0) - ); - } - - @Test - void sync_WithSameIdentifiersAndIdenticalValues_ShouldProcessedAndNotUpdated() { - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(emptyList(), emptyList()); - - final CustomObject existingCustomObject = mock(CustomObject.class); - when(existingCustomObject.getContainer()).thenReturn("someContainer"); - when(existingCustomObject.getKey()).thenReturn("someKey"); - when(existingCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); - - final CustomObject updatedCustomObject = mock(CustomObject.class); - when(updatedCustomObject.getContainer()).thenReturn("someContainer"); - when(updatedCustomObject.getKey()).thenReturn("someKey"); - when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); - - final Set> existingCustomObjectSet = new HashSet>(); - existingCustomObjectSet.add(existingCustomObject); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())) - .thenReturn(completedFuture(existingCustomObjectSet)); - when(customObjectService.upsertCustomObject(any())) - .thenReturn(completedFuture(Optional.of(updatedCustomObject))); - - // test - CustomObjectSyncStatistics syncStatistics = - new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) - .sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); - - // assertion - assertAll( - () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0) - ); - } - - - @Test - void sync_UpdateWithConcurrentModificationExceptionAndRetryWithFetchException_ShouldIncrementFailed() { - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(errorMessages, exceptions); - - final CustomObject existingCustomObject = mock(CustomObject.class); - when(existingCustomObject.getContainer()).thenReturn("someContainer"); - when(existingCustomObject.getKey()).thenReturn("someKey"); - when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); - - final Set> existingCustomObjectSet = new HashSet>(); - existingCustomObjectSet.add(existingCustomObject); - - final CustomObject updatedCustomObject = mock(CustomObject.class); - when(updatedCustomObject.getContainer()).thenReturn("someContainer"); - when(updatedCustomObject.getKey()).thenReturn("someKey"); - when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())) - .thenReturn(completedFuture(existingCustomObjectSet)); - when(customObjectService.upsertCustomObject(any())) - .thenReturn(supplyAsync(() -> { throw new ConcurrentModificationException(); })); - when(customObjectService.fetchCustomObject(any(CustomObjectCompositeIdentifier.class))) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); + .sync(singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + + // assertion + assertAll( + () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0)); + } + + @Test + void + sync_UpdateWithConcurrentModificationExceptionAndRetryWithFetchException_ShouldIncrementFailed() { + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(errorMessages, exceptions); + + final CustomObject existingCustomObject = mock(CustomObject.class); + when(existingCustomObject.getContainer()).thenReturn("someContainer"); + when(existingCustomObject.getKey()).thenReturn("someKey"); + when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); + + final Set> existingCustomObjectSet = + new HashSet>(); + existingCustomObjectSet.add(existingCustomObject); + + final CustomObject updatedCustomObject = mock(CustomObject.class); + when(updatedCustomObject.getContainer()).thenReturn("someContainer"); + when(updatedCustomObject.getKey()).thenReturn("someKey"); + when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(existingCustomObjectSet)); + when(customObjectService.upsertCustomObject(any())) + .thenReturn( + supplyAsync( + () -> { + throw new ConcurrentModificationException(); + })); + when(customObjectService.fetchCustomObject(any(CustomObjectCompositeIdentifier.class))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); })); - // test - CustomObjectSyncStatistics syncStatistics = - new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) - .sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); - // assertion - assertAll( - () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(1) - ); - assertThat(exceptions).hasSize(1); - assertThat(errorMessages) - .hasSize(1) - .singleElement() - .isEqualTo( - format("Failed to update custom object with key: '%s'. Reason: %s", - CustomObjectCompositeIdentifier.of(newCustomObjectDraft).toString(), - "Failed to fetch from CTP while retrying after concurrency modification.") - ); - - verify(customObjectService).fetchCustomObject(any(CustomObjectCompositeIdentifier.class)); - verify(customObjectService).upsertCustomObject(any()); - verify(customObjectService).fetchMatchingCustomObjects(any()); - } - - @Test - void sync_UpdateWithSphereExceptionAndRetryWithFetchException_ShouldIncrementFailed() { - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(errorMessages, exceptions); - - final CustomObject existingCustomObject = mock(CustomObject.class); - when(existingCustomObject.getContainer()).thenReturn("someContainer"); - when(existingCustomObject.getKey()).thenReturn("someKey"); - when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); - - final Set> existingCustomObjectSet = new HashSet>(); - existingCustomObjectSet.add(existingCustomObject); - - final CustomObject updatedCustomObject = mock(CustomObject.class); - when(updatedCustomObject.getContainer()).thenReturn("someContainer"); - when(updatedCustomObject.getKey()).thenReturn("someKey"); - when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())) - .thenReturn(completedFuture(existingCustomObjectSet)); - when(customObjectService.upsertCustomObject(any())) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - when(customObjectService.fetchCustomObject(any(CustomObjectCompositeIdentifier.class))) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); + // test + CustomObjectSyncStatistics syncStatistics = + new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) + .sync(singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + // assertion + assertAll( + () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(1)); + assertThat(exceptions).hasSize(1); + assertThat(errorMessages) + .hasSize(1) + .singleElement() + .isEqualTo( + format( + "Failed to update custom object with key: '%s'. Reason: %s", + CustomObjectCompositeIdentifier.of(newCustomObjectDraft).toString(), + "Failed to fetch from CTP while retrying after concurrency modification.")); + + verify(customObjectService).fetchCustomObject(any(CustomObjectCompositeIdentifier.class)); + verify(customObjectService).upsertCustomObject(any()); + verify(customObjectService).fetchMatchingCustomObjects(any()); + } + + @Test + void sync_UpdateWithSphereExceptionAndRetryWithFetchException_ShouldIncrementFailed() { + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(errorMessages, exceptions); + + final CustomObject existingCustomObject = mock(CustomObject.class); + when(existingCustomObject.getContainer()).thenReturn("someContainer"); + when(existingCustomObject.getKey()).thenReturn("someKey"); + when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); + + final Set> existingCustomObjectSet = + new HashSet>(); + existingCustomObjectSet.add(existingCustomObject); + + final CustomObject updatedCustomObject = mock(CustomObject.class); + when(updatedCustomObject.getContainer()).thenReturn("someContainer"); + when(updatedCustomObject.getKey()).thenReturn("someKey"); + when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(existingCustomObjectSet)); + when(customObjectService.upsertCustomObject(any())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + when(customObjectService.fetchCustomObject(any(CustomObjectCompositeIdentifier.class))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); })); - // test - CustomObjectSyncStatistics syncStatistics = - new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) - .sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); - - // assertion - assertAll( - () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(1) - ); - assertThat(exceptions).hasSize(1); - assertThat(errorMessages) - .hasSize(1) - .singleElement() - .isEqualTo( - format("Failed to update custom object with key: '%s'. Reason: %s", - CustomObjectCompositeIdentifier.of(newCustomObjectDraft).toString(), - exceptions.get(0).getMessage()) - ); - verify(customObjectService).upsertCustomObject(any()); - verify(customObjectService).fetchMatchingCustomObjects(any()); - } - - @Test - void sync_WithDifferentIdentifiers_ShouldCreateSuccessfully() { - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(errorMessages, exceptions); - - final CustomObject existingCustomObject = mock(CustomObject.class); - when(existingCustomObject.getContainer()).thenReturn("otherContainer"); - when(existingCustomObject.getKey()).thenReturn("otherKey"); - when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); - - final CustomObject updatedCustomObject = mock(CustomObject.class); - when(updatedCustomObject.getContainer()).thenReturn("someContainer"); - when(updatedCustomObject.getKey()).thenReturn("someKey"); - when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); - - final Set> existingCustomObjectSet = new HashSet>(); - existingCustomObjectSet.add(existingCustomObject); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())) - .thenReturn(completedFuture(existingCustomObjectSet)); - when(customObjectService.upsertCustomObject(any())) - .thenReturn(completedFuture(Optional.of(updatedCustomObject))); - - // test - CustomObjectSyncStatistics syncStatistics = - new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) - .sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); - - // assertion - assertThat(exceptions).hasSize(0); - assertThat(errorMessages).hasSize(0); - assertAll( - () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0) - ); - verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); - } - - @Test - void sync_WithSameKeysAndDifferentContainers_ShouldCreateSuccessfully() { - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(errorMessages, exceptions); - - final CustomObject existingCustomObject = mock(CustomObject.class); - when(existingCustomObject.getContainer()).thenReturn("otherContainer"); - when(existingCustomObject.getKey()).thenReturn("someKey"); - when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); - - final CustomObject updatedCustomObject = mock(CustomObject.class); - when(updatedCustomObject.getContainer()).thenReturn("someContainer"); - when(updatedCustomObject.getKey()).thenReturn("someKey"); - when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); - - final Set> existingCustomObjectSet = new HashSet>(); - existingCustomObjectSet.add(existingCustomObject); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())) - .thenReturn(completedFuture(existingCustomObjectSet)); - when(customObjectService.upsertCustomObject(any())) - .thenReturn(completedFuture(Optional.of(updatedCustomObject))); - - // test - CustomObjectSyncStatistics syncStatistics = - new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) - .sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); - - // assertion - assertThat(exceptions).hasSize(0); - assertThat(errorMessages).hasSize(0); - assertAll( - () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0) - ); - verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); - } - - @Test - void sync_WithDifferentKeysAndSameContainers_ShouldCreateSuccessfully() { - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(errorMessages, exceptions); - - final CustomObject existingCustomObject = mock(CustomObject.class); - when(existingCustomObject.getContainer()).thenReturn("someContainer"); - when(existingCustomObject.getKey()).thenReturn("otherKey"); - when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); - - final CustomObject updatedCustomObject = mock(CustomObject.class); - when(updatedCustomObject.getContainer()).thenReturn("someContainer"); - when(updatedCustomObject.getKey()).thenReturn("someKey"); - when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); - - final Set> existingCustomObjectSet = new HashSet>(); - existingCustomObjectSet.add(existingCustomObject); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())) - .thenReturn(completedFuture(existingCustomObjectSet)); - when(customObjectService.upsertCustomObject(any())) - .thenReturn(completedFuture(Optional.of(updatedCustomObject))); - - // test - CustomObjectSyncStatistics syncStatistics = - new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) - .sync(singletonList(newCustomObjectDraft)).toCompletableFuture().join(); - - // assertion - assertThat(exceptions).hasSize(0); - assertThat(errorMessages).hasSize(0); - assertAll( - () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0) - ); - verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); - } - - @Test - void sync_WitEmptyValidDrafts_ShouldFailed() { - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final CustomObjectSyncOptions spyCustomObjectSyncOptions = - initCustomObjectSyncOptions(errorMessages, exceptions); - - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchMatchingCustomObjects(anySet())).thenReturn(completedFuture(emptySet())); - when(customObjectService.upsertCustomObject(any())).thenReturn(completedFuture(Optional.empty())); - - // test - CustomObjectSyncStatistics syncStatistics = new CustomObjectSync( - spyCustomObjectSyncOptions, customObjectService).sync( - singletonList(null)).toCompletableFuture().join(); - - // assertion - assertThat(exceptions).hasSize(1); - assertThat(errorMessages).hasSize(1); - assertAll( - () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), - () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), - () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(1) - ); - } - - @Nonnull - private CustomObjectSyncOptions initCustomObjectSyncOptions( - @Nonnull final List errorMessages, - @Nonnull final List exceptions) { - return spy(CustomObjectSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); + // test + CustomObjectSyncStatistics syncStatistics = + new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) + .sync(singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + + // assertion + assertAll( + () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(1)); + assertThat(exceptions).hasSize(1); + assertThat(errorMessages) + .hasSize(1) + .singleElement() + .isEqualTo( + format( + "Failed to update custom object with key: '%s'. Reason: %s", + CustomObjectCompositeIdentifier.of(newCustomObjectDraft).toString(), + exceptions.get(0).getMessage())); + verify(customObjectService).upsertCustomObject(any()); + verify(customObjectService).fetchMatchingCustomObjects(any()); + } + + @Test + void sync_WithDifferentIdentifiers_ShouldCreateSuccessfully() { + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(errorMessages, exceptions); + + final CustomObject existingCustomObject = mock(CustomObject.class); + when(existingCustomObject.getContainer()).thenReturn("otherContainer"); + when(existingCustomObject.getKey()).thenReturn("otherKey"); + when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); + + final CustomObject updatedCustomObject = mock(CustomObject.class); + when(updatedCustomObject.getContainer()).thenReturn("someContainer"); + when(updatedCustomObject.getKey()).thenReturn("someKey"); + when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); + + final Set> existingCustomObjectSet = + new HashSet>(); + existingCustomObjectSet.add(existingCustomObject); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(existingCustomObjectSet)); + when(customObjectService.upsertCustomObject(any())) + .thenReturn(completedFuture(Optional.of(updatedCustomObject))); + + // test + CustomObjectSyncStatistics syncStatistics = + new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) + .sync(singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(exceptions).hasSize(0); + assertThat(errorMessages).hasSize(0); + assertAll( + () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0)); + verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); + } + + @Test + void sync_WithSameKeysAndDifferentContainers_ShouldCreateSuccessfully() { + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(errorMessages, exceptions); + + final CustomObject existingCustomObject = mock(CustomObject.class); + when(existingCustomObject.getContainer()).thenReturn("otherContainer"); + when(existingCustomObject.getKey()).thenReturn("someKey"); + when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); + + final CustomObject updatedCustomObject = mock(CustomObject.class); + when(updatedCustomObject.getContainer()).thenReturn("someContainer"); + when(updatedCustomObject.getKey()).thenReturn("someKey"); + when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); + + final Set> existingCustomObjectSet = + new HashSet>(); + existingCustomObjectSet.add(existingCustomObject); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(existingCustomObjectSet)); + when(customObjectService.upsertCustomObject(any())) + .thenReturn(completedFuture(Optional.of(updatedCustomObject))); + + // test + CustomObjectSyncStatistics syncStatistics = + new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) + .sync(singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(exceptions).hasSize(0); + assertThat(errorMessages).hasSize(0); + assertAll( + () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0)); + verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); + } + + @Test + void sync_WithDifferentKeysAndSameContainers_ShouldCreateSuccessfully() { + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(errorMessages, exceptions); + + final CustomObject existingCustomObject = mock(CustomObject.class); + when(existingCustomObject.getContainer()).thenReturn("someContainer"); + when(existingCustomObject.getKey()).thenReturn("otherKey"); + when(existingCustomObject.getValue()).thenReturn(JsonNodeFactory.instance.numberNode(2020)); + + final CustomObject updatedCustomObject = mock(CustomObject.class); + when(updatedCustomObject.getContainer()).thenReturn("someContainer"); + when(updatedCustomObject.getKey()).thenReturn("someKey"); + when(updatedCustomObject.getValue()).thenReturn(newCustomObjectDraft.getValue()); + + final Set> existingCustomObjectSet = + new HashSet>(); + existingCustomObjectSet.add(existingCustomObject); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(existingCustomObjectSet)); + when(customObjectService.upsertCustomObject(any())) + .thenReturn(completedFuture(Optional.of(updatedCustomObject))); + + // test + CustomObjectSyncStatistics syncStatistics = + new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) + .sync(singletonList(newCustomObjectDraft)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(exceptions).hasSize(0); + assertThat(errorMessages).hasSize(0); + assertAll( + () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(0)); + verify(spyCustomObjectSyncOptions).applyBeforeCreateCallback(newCustomObjectDraft); + } + + @Test + void sync_WitEmptyValidDrafts_ShouldFailed() { + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final CustomObjectSyncOptions spyCustomObjectSyncOptions = + initCustomObjectSyncOptions(errorMessages, exceptions); + + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchMatchingCustomObjects(anySet())) + .thenReturn(completedFuture(emptySet())); + when(customObjectService.upsertCustomObject(any())) + .thenReturn(completedFuture(Optional.empty())); + + // test + CustomObjectSyncStatistics syncStatistics = + new CustomObjectSync(spyCustomObjectSyncOptions, customObjectService) + .sync(singletonList(null)) + .toCompletableFuture() + .join(); + + // assertion + assertThat(exceptions).hasSize(1); + assertThat(errorMessages).hasSize(1); + assertAll( + () -> assertThat(syncStatistics.getProcessed().get()).isEqualTo(1), + () -> assertThat(syncStatistics.getCreated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getUpdated().get()).isEqualTo(0), + () -> assertThat(syncStatistics.getFailed().get()).isEqualTo(1)); + } + + @Nonnull + private CustomObjectSyncOptions initCustomObjectSyncOptions( + @Nonnull final List errorMessages, @Nonnull final List exceptions) { + return spy( + CustomObjectSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); }) - .build()); - } + .build()); + } } diff --git a/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectBatchValidatorTest.java b/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectBatchValidatorTest.java index 6d1826d1e9..e711588a06 100644 --- a/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectBatchValidatorTest.java @@ -1,84 +1,88 @@ package com.commercetools.sync.customobjects.helpers; +import static com.commercetools.sync.customobjects.helpers.CustomObjectBatchValidator.CUSTOM_OBJECT_DRAFT_IS_NULL; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.customobjects.CustomObjectSyncOptions; import com.commercetools.sync.customobjects.CustomObjectSyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customobjects.CustomObjectDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; - -import static com.commercetools.sync.customobjects.helpers.CustomObjectBatchValidator.CUSTOM_OBJECT_DRAFT_IS_NULL; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomObjectBatchValidatorTest { - private List errorCallBackMessages; - private CustomObjectSyncOptions syncOptions; - private CustomObjectSyncStatistics syncStatistics; + private List errorCallBackMessages; + private CustomObjectSyncOptions syncOptions; + private CustomObjectSyncStatistics syncStatistics; - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = CustomObjectSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - }) + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); + syncOptions = + CustomObjectSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + }) .build(); - syncStatistics = new CustomObjectSyncStatistics(); - } + syncStatistics = new CustomObjectSyncStatistics(); + } - @Test - void validateAndCollectReferencedKeys_WithValidDraft_ShouldHaveCorrectResult() { - CustomObjectDraft customObjectDraft = - CustomObjectDraft.ofUnversionedUpsert("container", "key", - JsonNodeFactory.instance.numberNode(1)); + @Test + void validateAndCollectReferencedKeys_WithValidDraft_ShouldHaveCorrectResult() { + CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert( + "container", "key", JsonNodeFactory.instance.numberNode(1)); - final CustomObjectBatchValidator batchValidator = new CustomObjectBatchValidator(syncOptions, syncStatistics); - final ImmutablePair>, Set> result = - batchValidator.validateAndCollectReferencedKeys(singletonList(customObjectDraft)); + final CustomObjectBatchValidator batchValidator = + new CustomObjectBatchValidator(syncOptions, syncStatistics); + final ImmutablePair>, Set> + result = batchValidator.validateAndCollectReferencedKeys(singletonList(customObjectDraft)); - assertThat(result.getLeft()).contains(customObjectDraft); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(result.getRight()).contains(CustomObjectCompositeIdentifier.of(customObjectDraft)); - } + assertThat(result.getLeft()).contains(customObjectDraft); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(result.getRight()).contains(CustomObjectCompositeIdentifier.of(customObjectDraft)); + } - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set> validDrafts = getValidDrafts(emptyList()); + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set> validDrafts = getValidDrafts(emptyList()); - assertThat(validDrafts).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } + assertThat(validDrafts).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } - @Test - void validateAndCollectReferencedKeys_WithNullTypeDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set> validDrafts = getValidDrafts(Collections.singletonList(null)); + @Test + void + validateAndCollectReferencedKeys_WithNullTypeDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set> validDrafts = + getValidDrafts(Collections.singletonList(null)); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(CUSTOM_OBJECT_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(CUSTOM_OBJECT_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } - @Nonnull - private Set> getValidDrafts( - @Nonnull final List> customObjectDrafts) { + @Nonnull + private Set> getValidDrafts( + @Nonnull final List> customObjectDrafts) { - final CustomObjectBatchValidator batchValidator = new CustomObjectBatchValidator(syncOptions, syncStatistics); - final ImmutablePair>, Set> pair = - batchValidator.validateAndCollectReferencedKeys(customObjectDrafts); - return pair.getLeft(); - } + final CustomObjectBatchValidator batchValidator = + new CustomObjectBatchValidator(syncOptions, syncStatistics); + final ImmutablePair>, Set> + pair = batchValidator.validateAndCollectReferencedKeys(customObjectDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectCompositeIdentifierTest.java b/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectCompositeIdentifierTest.java index 414a1a1475..e15123bcbb 100644 --- a/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectCompositeIdentifierTest.java +++ b/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectCompositeIdentifierTest.java @@ -1,10 +1,5 @@ package com.commercetools.sync.customobjects.helpers; -import com.fasterxml.jackson.databind.JsonNode; -import io.sphere.sdk.customobjects.CustomObject; -import io.sphere.sdk.customobjects.CustomObjectDraft; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -14,187 +9,193 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.JsonNode; +import io.sphere.sdk.customobjects.CustomObject; +import io.sphere.sdk.customobjects.CustomObjectDraft; +import org.junit.jupiter.api.Test; + public class CustomObjectCompositeIdentifierTest { - private static final String CONTAINER = "container"; - private static final String KEY = "key"; - - @Test - void of_WithCustomObjectDraft_ShouldCreateCustomObjectCompositeIdentifier() { - final CustomObjectDraft customObjectDraft = - CustomObjectDraft.ofUnversionedUpsert(CONTAINER, KEY, null); - - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(customObjectDraft); - - assertThat(customObjectCompositeIdentifier).isNotNull(); - assertThat(customObjectCompositeIdentifier.getContainer()).isEqualTo(CONTAINER); - assertThat(customObjectCompositeIdentifier.getKey()).isEqualTo(KEY); - } - - @Test - void of_WithContainerAndKeyParams_ShouldCreateCustomObjectCompositeIdentifier() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(KEY, CONTAINER); - - assertThat(customObjectCompositeIdentifier).isNotNull(); - assertThat(customObjectCompositeIdentifier.getContainer()).isEqualTo(CONTAINER); - assertThat(customObjectCompositeIdentifier.getKey()).isEqualTo(KEY); - } - - @Test - void of_WithCustomObject_ShouldCreateCustomObjectCompositeIdentifier() { - final CustomObject customObject = mock(CustomObject.class); - when(customObject.getContainer()).thenReturn(CONTAINER); - when(customObject.getKey()).thenReturn(KEY); - - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(customObject); - - assertThat(customObjectCompositeIdentifier).isNotNull(); - assertThat(customObjectCompositeIdentifier.getContainer()).isEqualTo(CONTAINER); - assertThat(customObjectCompositeIdentifier.getKey()).isEqualTo(KEY); - } - - @Test - void of_WithEmptyKeyAndContainer_ShouldThrowAnError() { - assertThatThrownBy(() -> CustomObjectCompositeIdentifier.of("", "")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("The container \"\" does not have the correct format. " + private static final String CONTAINER = "container"; + private static final String KEY = "key"; + + @Test + void of_WithCustomObjectDraft_ShouldCreateCustomObjectCompositeIdentifier() { + final CustomObjectDraft customObjectDraft = + CustomObjectDraft.ofUnversionedUpsert(CONTAINER, KEY, null); + + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(customObjectDraft); + + assertThat(customObjectCompositeIdentifier).isNotNull(); + assertThat(customObjectCompositeIdentifier.getContainer()).isEqualTo(CONTAINER); + assertThat(customObjectCompositeIdentifier.getKey()).isEqualTo(KEY); + } + + @Test + void of_WithContainerAndKeyParams_ShouldCreateCustomObjectCompositeIdentifier() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(KEY, CONTAINER); + + assertThat(customObjectCompositeIdentifier).isNotNull(); + assertThat(customObjectCompositeIdentifier.getContainer()).isEqualTo(CONTAINER); + assertThat(customObjectCompositeIdentifier.getKey()).isEqualTo(KEY); + } + + @Test + void of_WithCustomObject_ShouldCreateCustomObjectCompositeIdentifier() { + final CustomObject customObject = mock(CustomObject.class); + when(customObject.getContainer()).thenReturn(CONTAINER); + when(customObject.getKey()).thenReturn(KEY); + + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(customObject); + + assertThat(customObjectCompositeIdentifier).isNotNull(); + assertThat(customObjectCompositeIdentifier.getContainer()).isEqualTo(CONTAINER); + assertThat(customObjectCompositeIdentifier.getKey()).isEqualTo(KEY); + } + + @Test + void of_WithEmptyKeyAndContainer_ShouldThrowAnError() { + assertThatThrownBy(() -> CustomObjectCompositeIdentifier.of("", "")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "The container \"\" does not have the correct format. " + "Key and container need to match [-_~.a-zA-Z0-9]+."); - } - - @Test - void of_WithInvalidIdentifierAsString_ShouldThrowAnError() { - assertThatThrownBy(() -> CustomObjectCompositeIdentifier.of("aContainer-andKey")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("The custom object identifier value: \"aContainer-andKey\" does not have the correct format. " + } + + @Test + void of_WithInvalidIdentifierAsString_ShouldThrowAnError() { + assertThatThrownBy(() -> CustomObjectCompositeIdentifier.of("aContainer-andKey")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "The custom object identifier value: \"aContainer-andKey\" does not have the correct format. " + "The correct format must have a vertical bar \"|\" character between the container and key."); - } - - @Test - void equals_WithSameObj_ShouldReturnTrue() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(KEY, CONTAINER); - - boolean result = customObjectCompositeIdentifier.equals(customObjectCompositeIdentifier); - - assertTrue(result); - } - - @Test - void equals_WithDiffType_ShouldReturnFalse() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(KEY, CONTAINER); - final Object other = new Object(); + } - boolean result = customObjectCompositeIdentifier.equals(other); - - assertFalse(result); - } + @Test + void equals_WithSameObj_ShouldReturnTrue() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(KEY, CONTAINER); - @Test - void equals_WithEqualObjects_ShouldReturnTrue() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(KEY, CONTAINER); - final CustomObjectCompositeIdentifier other - = CustomObjectCompositeIdentifier.of(KEY, CONTAINER); + boolean result = customObjectCompositeIdentifier.equals(customObjectCompositeIdentifier); - boolean result = customObjectCompositeIdentifier.equals(other); + assertTrue(result); + } - assertTrue(result); - } + @Test + void equals_WithDiffType_ShouldReturnFalse() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(KEY, CONTAINER); + final Object other = new Object(); - @Test - void equals_WithDifferentKeys_ShouldReturnFalse() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of("key1", CONTAINER); - final CustomObjectCompositeIdentifier other - = CustomObjectCompositeIdentifier.of("key2", CONTAINER); + boolean result = customObjectCompositeIdentifier.equals(other); + + assertFalse(result); + } - boolean result = customObjectCompositeIdentifier.equals(other); + @Test + void equals_WithEqualObjects_ShouldReturnTrue() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(KEY, CONTAINER); + final CustomObjectCompositeIdentifier other = + CustomObjectCompositeIdentifier.of(KEY, CONTAINER); - assertFalse(result); - } + boolean result = customObjectCompositeIdentifier.equals(other); - @Test - void equals_WithDifferentContainers_ShouldReturnFalse() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(KEY, "container1"); - final CustomObjectCompositeIdentifier other - = CustomObjectCompositeIdentifier.of(KEY, "container2"); + assertTrue(result); + } - boolean result = customObjectCompositeIdentifier.equals(other); + @Test + void equals_WithDifferentKeys_ShouldReturnFalse() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of("key1", CONTAINER); + final CustomObjectCompositeIdentifier other = + CustomObjectCompositeIdentifier.of("key2", CONTAINER); - assertFalse(result); - } + boolean result = customObjectCompositeIdentifier.equals(other); - @Test - void hashCode_withSameInstances_ShouldBeEquals() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(KEY, CONTAINER); - final CustomObjectCompositeIdentifier other = customObjectCompositeIdentifier; + assertFalse(result); + } - final int hash1 = customObjectCompositeIdentifier.hashCode(); - final int hash2 = other.hashCode(); + @Test + void equals_WithDifferentContainers_ShouldReturnFalse() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(KEY, "container1"); + final CustomObjectCompositeIdentifier other = + CustomObjectCompositeIdentifier.of(KEY, "container2"); - assertEquals(hash1, hash2); - } + boolean result = customObjectCompositeIdentifier.equals(other); - @Test - void hashCode_withSameKeyAndSameContainer_ShouldBeEquals() { - // preparation - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(KEY, CONTAINER); - final CustomObjectCompositeIdentifier other - = CustomObjectCompositeIdentifier.of(KEY, CONTAINER); + assertFalse(result); + } - // test - final int hash1 = customObjectCompositeIdentifier.hashCode(); - final int hash2 = other.hashCode(); + @Test + void hashCode_withSameInstances_ShouldBeEquals() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(KEY, CONTAINER); + final CustomObjectCompositeIdentifier other = customObjectCompositeIdentifier; - // assertions - assertEquals(hash1, hash2); - } + final int hash1 = customObjectCompositeIdentifier.hashCode(); + final int hash2 = other.hashCode(); - @Test - void hashCode_withDifferentKeyAndSameContainer_ShouldNotBeEquals() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of("key1", CONTAINER); + assertEquals(hash1, hash2); + } - final CustomObjectCompositeIdentifier other - = CustomObjectCompositeIdentifier.of("key2", CONTAINER); + @Test + void hashCode_withSameKeyAndSameContainer_ShouldBeEquals() { + // preparation + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(KEY, CONTAINER); + final CustomObjectCompositeIdentifier other = + CustomObjectCompositeIdentifier.of(KEY, CONTAINER); - final int hash1 = customObjectCompositeIdentifier.hashCode(); - final int hash2 = other.hashCode(); + // test + final int hash1 = customObjectCompositeIdentifier.hashCode(); + final int hash2 = other.hashCode(); - assertNotEquals(hash1, hash2); - } + // assertions + assertEquals(hash1, hash2); + } - @Test - void hashCode_withSameKeyAndDifferentContainer_ShouldNotBeEquals() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of(KEY, "container1"); - final CustomObjectCompositeIdentifier other - = CustomObjectCompositeIdentifier.of(KEY, "container2"); + @Test + void hashCode_withDifferentKeyAndSameContainer_ShouldNotBeEquals() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of("key1", CONTAINER); - final int hash1 = customObjectCompositeIdentifier.hashCode(); - final int hash2 = other.hashCode(); + final CustomObjectCompositeIdentifier other = + CustomObjectCompositeIdentifier.of("key2", CONTAINER); - assertNotEquals(hash1, hash2); - } + final int hash1 = customObjectCompositeIdentifier.hashCode(); + final int hash2 = other.hashCode(); - @Test - void hashCode_withCompletelyDifferentValues_ShouldNotBeEquals() { - final CustomObjectCompositeIdentifier customObjectCompositeIdentifier - = CustomObjectCompositeIdentifier.of("key1", "container1"); - final CustomObjectCompositeIdentifier other - = CustomObjectCompositeIdentifier.of("key2", "container2"); + assertNotEquals(hash1, hash2); + } - final int hash1 = customObjectCompositeIdentifier.hashCode(); - final int hash2 = other.hashCode(); - - assertNotEquals(hash1, hash2); - } + @Test + void hashCode_withSameKeyAndDifferentContainer_ShouldNotBeEquals() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of(KEY, "container1"); + final CustomObjectCompositeIdentifier other = + CustomObjectCompositeIdentifier.of(KEY, "container2"); + final int hash1 = customObjectCompositeIdentifier.hashCode(); + final int hash2 = other.hashCode(); + + assertNotEquals(hash1, hash2); + } + + @Test + void hashCode_withCompletelyDifferentValues_ShouldNotBeEquals() { + final CustomObjectCompositeIdentifier customObjectCompositeIdentifier = + CustomObjectCompositeIdentifier.of("key1", "container1"); + final CustomObjectCompositeIdentifier other = + CustomObjectCompositeIdentifier.of("key2", "container2"); + + final int hash1 = customObjectCompositeIdentifier.hashCode(); + final int hash2 = other.hashCode(); + + assertNotEquals(hash1, hash2); + } } diff --git a/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectSyncStatisticsTest.java b/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectSyncStatisticsTest.java index 71f7190418..4e1ccee92f 100644 --- a/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectSyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/customobjects/helpers/CustomObjectSyncStatisticsTest.java @@ -1,28 +1,28 @@ package com.commercetools.sync.customobjects.helpers; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomObjectSyncStatisticsTest { - private CustomObjectSyncStatistics customObjectSyncStatistics; + private CustomObjectSyncStatistics customObjectSyncStatistics; - @BeforeEach - void setup() { - customObjectSyncStatistics = new CustomObjectSyncStatistics(); - } + @BeforeEach + void setup() { + customObjectSyncStatistics = new CustomObjectSyncStatistics(); + } - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - customObjectSyncStatistics.incrementCreated(1); - customObjectSyncStatistics.incrementFailed(2); - customObjectSyncStatistics.incrementUpdated(3); - customObjectSyncStatistics.incrementProcessed(6); + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + customObjectSyncStatistics.incrementCreated(1); + customObjectSyncStatistics.incrementFailed(2); + customObjectSyncStatistics.incrementUpdated(3); + customObjectSyncStatistics.incrementProcessed(6); - assertThat(customObjectSyncStatistics.getReportMessage()) - .isEqualTo("Summary: 6 custom objects were processed in total " + assertThat(customObjectSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 6 custom objects were processed in total " + "(1 created, 3 updated and 2 failed to sync)."); - } + } } diff --git a/src/test/java/com/commercetools/sync/customobjects/utils/CustomObjectSyncUtilsTest.java b/src/test/java/com/commercetools/sync/customobjects/utils/CustomObjectSyncUtilsTest.java index 97e34c4078..1d8db1ee1f 100644 --- a/src/test/java/com/commercetools/sync/customobjects/utils/CustomObjectSyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/customobjects/utils/CustomObjectSyncUtilsTest.java @@ -1,194 +1,195 @@ package com.commercetools.sync.customobjects.utils; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; - import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.customobjects.CustomObjectDraft; - import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - class CustomObjectSyncUtilsTest { - private CustomObject oldCustomObject; - private CustomObjectDraft newCustomObjectdraft; - - private void prepareMockObjects(final JsonNode actualObj, final JsonNode mockedObj) { - final String key = "testkey"; - final String container = "testcontainer"; - - newCustomObjectdraft = CustomObjectDraft.ofUnversionedUpsert(container, key, actualObj); - oldCustomObject = mock(CustomObject.class); - when(oldCustomObject.getValue()).thenReturn(mockedObj); - when(oldCustomObject.getContainer()).thenReturn(container); - when(oldCustomObject.getKey()).thenReturn(key); - } - - @Test - void hasIdenticalValue_WithSameBooleanValue_ShouldBeIdentical() throws JsonProcessingException { - JsonNode actualObj = new ObjectMapper().readTree("true"); - JsonNode mockedObj = new ObjectMapper().readTree("true"); - prepareMockObjects(actualObj, mockedObj); - assertThat(actualObj.isBoolean()).isTrue(); - assertThat(mockedObj.isBoolean()).isTrue(); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isTrue(); - } - - @Test - void hasIdenticalValue_WithDifferentBooleanValue_ShouldNotBeIdentical() throws JsonProcessingException { - JsonNode actualObj = new ObjectMapper().readTree("true"); - JsonNode mockedObj = new ObjectMapper().readTree("false"); - prepareMockObjects(actualObj, mockedObj); - assertThat(actualObj.isBoolean()).isTrue(); - assertThat(mockedObj.isBoolean()).isTrue(); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isFalse(); - } - - @Test - void hasIdenticalValue_WithSameNumberValue_ShouldBeIdentical() throws JsonProcessingException { - JsonNode actualObj = new ObjectMapper().readTree("2020"); - JsonNode mockedObj = new ObjectMapper().readTree("2020"); - prepareMockObjects(actualObj, mockedObj); - assertThat(actualObj.isNumber()).isTrue(); - assertThat(mockedObj.isNumber()).isTrue(); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isTrue(); - } - - @Test - void hasIdenticalValue_WithDifferentNumberValue_ShouldNotBeIdentical() throws JsonProcessingException { - JsonNode actualObj = new ObjectMapper().readTree("2020"); - JsonNode mockedObj = new ObjectMapper().readTree("2021"); - prepareMockObjects(actualObj, mockedObj); - assertThat(actualObj.isNumber()).isTrue(); - assertThat(mockedObj.isNumber()).isTrue(); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isFalse(); - } - - @Test - void hasIdenticalValue_WithSameStringValue_ShouldBeIdentical() throws JsonProcessingException { - JsonNode actualObj = new ObjectMapper().readTree("\"CommerceTools\""); - JsonNode mockedObj = new ObjectMapper().readTree("\"CommerceTools\""); - prepareMockObjects(actualObj, mockedObj); - assertThat(actualObj.isTextual()).isTrue(); - assertThat(mockedObj.isTextual()).isTrue(); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isTrue(); - } - - @Test - void hasIdenticalValue_WithDifferentStringValue_ShouldNotBeIdentical() throws JsonProcessingException { - JsonNode actualObj = new ObjectMapper().readTree("\"CommerceToolsPlatform\""); - JsonNode mockedObj = new ObjectMapper().readTree("\"CommerceTools\""); - prepareMockObjects(actualObj, mockedObj); - assertThat(actualObj.isTextual()).isTrue(); - assertThat(mockedObj.isTextual()).isTrue(); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isFalse(); - } - - @Test - void hasIdenticalValue_WithSameFieldAndValueInJsonNode_ShouldBeIdentical() { - - ObjectNode oldValue = JsonNodeFactory.instance.objectNode().put("username", "Peter"); - ObjectNode newValue = JsonNodeFactory.instance.objectNode().put("username", "Peter"); - prepareMockObjects(oldValue, newValue); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isTrue(); - } - - @Test - void hasIdenticalValue_WithSameFieldAndDifferentValueInJsonNode_ShouldNotBeIdentical() { - - ObjectNode oldValue = JsonNodeFactory.instance.objectNode().put("username", "Peter"); - ObjectNode newValue = JsonNodeFactory.instance.objectNode().put("username", "Joe"); - prepareMockObjects(oldValue, newValue); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isFalse(); - } - - @Test - void hasIdenticalValue_WithSameFieldAndValueInDifferentOrderInJsonNode_ShouldBeIdentical() { - - ObjectNode oldValue = JsonNodeFactory.instance.objectNode() - .put("username", "Peter") - .put("userId", "123-456-789"); - - ObjectNode newValue = JsonNodeFactory.instance.objectNode() - .put("userId", "123-456-789") - .put("username", "Peter"); - - prepareMockObjects(oldValue, newValue); - - assertThat(oldValue.toString()).isNotEqualTo(newValue.toString()); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isTrue(); - } - - @Test - void hasIdenticalValue_WithSameNestedJsonNode_WithSameAttributeOrderInNestedJson_ShouldBeIdentical() { - - JsonNode oldNestedJson = JsonNodeFactory.instance.objectNode() - .put("username", "Peter") - .put("userId", "123-456-789"); - JsonNode newNestedJson = JsonNodeFactory.instance.objectNode() - .put("username", "Peter") - .put("userId", "123-456-789"); - - JsonNode oldJsonNode = JsonNodeFactory.instance.objectNode() - .set("nestedJson", oldNestedJson); - - JsonNode newJsonNode = JsonNodeFactory.instance.objectNode() - .set("nestedJson", newNestedJson); - - prepareMockObjects(oldJsonNode, newJsonNode); - - assertThat(oldJsonNode.toString()).isEqualTo(newJsonNode.toString()); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isTrue(); - } - - @Test - void hasIdenticalValue_WithSameNestedJsonNode_WithDifferentAttributeOrderInNestedJson_ShouldBeIdentical() { - - JsonNode oldNestedJson = JsonNodeFactory.instance.objectNode() - .put("username", "Peter") - .put("userId", "123-456-789"); - JsonNode newNestedJson = JsonNodeFactory.instance.objectNode() - .put("userId", "123-456-789") - .put("username", "Peter"); - - JsonNode oldJsonNode = JsonNodeFactory.instance.objectNode() - .set("nestedJson", oldNestedJson); - - JsonNode newJsonNode = JsonNodeFactory.instance.objectNode() - .set("nestedJson", newNestedJson); - - prepareMockObjects(oldJsonNode, newJsonNode); - - assertThat(oldJsonNode.toString()).isNotEqualTo(newJsonNode.toString()); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isTrue(); - } - - @Test - void hasIdenticalValue_WithDifferentNestedJsonNode_ShouldNotBeIdentical() { - - JsonNode oldNestedJson = JsonNodeFactory.instance.objectNode() - .put("username", "Peter") - .put("userId", "123-456-789"); - JsonNode newNestedJson = JsonNodeFactory.instance.objectNode() - .put("userId", "129-382-189") - .put("username", "Peter"); + private CustomObject oldCustomObject; + private CustomObjectDraft newCustomObjectdraft; + + private void prepareMockObjects(final JsonNode actualObj, final JsonNode mockedObj) { + final String key = "testkey"; + final String container = "testcontainer"; + + newCustomObjectdraft = CustomObjectDraft.ofUnversionedUpsert(container, key, actualObj); + oldCustomObject = mock(CustomObject.class); + when(oldCustomObject.getValue()).thenReturn(mockedObj); + when(oldCustomObject.getContainer()).thenReturn(container); + when(oldCustomObject.getKey()).thenReturn(key); + } + + @Test + void hasIdenticalValue_WithSameBooleanValue_ShouldBeIdentical() throws JsonProcessingException { + JsonNode actualObj = new ObjectMapper().readTree("true"); + JsonNode mockedObj = new ObjectMapper().readTree("true"); + prepareMockObjects(actualObj, mockedObj); + assertThat(actualObj.isBoolean()).isTrue(); + assertThat(mockedObj.isBoolean()).isTrue(); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isTrue(); + } + + @Test + void hasIdenticalValue_WithDifferentBooleanValue_ShouldNotBeIdentical() + throws JsonProcessingException { + JsonNode actualObj = new ObjectMapper().readTree("true"); + JsonNode mockedObj = new ObjectMapper().readTree("false"); + prepareMockObjects(actualObj, mockedObj); + assertThat(actualObj.isBoolean()).isTrue(); + assertThat(mockedObj.isBoolean()).isTrue(); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isFalse(); + } + + @Test + void hasIdenticalValue_WithSameNumberValue_ShouldBeIdentical() throws JsonProcessingException { + JsonNode actualObj = new ObjectMapper().readTree("2020"); + JsonNode mockedObj = new ObjectMapper().readTree("2020"); + prepareMockObjects(actualObj, mockedObj); + assertThat(actualObj.isNumber()).isTrue(); + assertThat(mockedObj.isNumber()).isTrue(); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isTrue(); + } + + @Test + void hasIdenticalValue_WithDifferentNumberValue_ShouldNotBeIdentical() + throws JsonProcessingException { + JsonNode actualObj = new ObjectMapper().readTree("2020"); + JsonNode mockedObj = new ObjectMapper().readTree("2021"); + prepareMockObjects(actualObj, mockedObj); + assertThat(actualObj.isNumber()).isTrue(); + assertThat(mockedObj.isNumber()).isTrue(); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isFalse(); + } + + @Test + void hasIdenticalValue_WithSameStringValue_ShouldBeIdentical() throws JsonProcessingException { + JsonNode actualObj = new ObjectMapper().readTree("\"CommerceTools\""); + JsonNode mockedObj = new ObjectMapper().readTree("\"CommerceTools\""); + prepareMockObjects(actualObj, mockedObj); + assertThat(actualObj.isTextual()).isTrue(); + assertThat(mockedObj.isTextual()).isTrue(); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isTrue(); + } + + @Test + void hasIdenticalValue_WithDifferentStringValue_ShouldNotBeIdentical() + throws JsonProcessingException { + JsonNode actualObj = new ObjectMapper().readTree("\"CommerceToolsPlatform\""); + JsonNode mockedObj = new ObjectMapper().readTree("\"CommerceTools\""); + prepareMockObjects(actualObj, mockedObj); + assertThat(actualObj.isTextual()).isTrue(); + assertThat(mockedObj.isTextual()).isTrue(); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isFalse(); + } + + @Test + void hasIdenticalValue_WithSameFieldAndValueInJsonNode_ShouldBeIdentical() { + + ObjectNode oldValue = JsonNodeFactory.instance.objectNode().put("username", "Peter"); + ObjectNode newValue = JsonNodeFactory.instance.objectNode().put("username", "Peter"); + prepareMockObjects(oldValue, newValue); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isTrue(); + } + + @Test + void hasIdenticalValue_WithSameFieldAndDifferentValueInJsonNode_ShouldNotBeIdentical() { + + ObjectNode oldValue = JsonNodeFactory.instance.objectNode().put("username", "Peter"); + ObjectNode newValue = JsonNodeFactory.instance.objectNode().put("username", "Joe"); + prepareMockObjects(oldValue, newValue); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isFalse(); + } + + @Test + void hasIdenticalValue_WithSameFieldAndValueInDifferentOrderInJsonNode_ShouldBeIdentical() { + + ObjectNode oldValue = + JsonNodeFactory.instance.objectNode().put("username", "Peter").put("userId", "123-456-789"); + + ObjectNode newValue = + JsonNodeFactory.instance.objectNode().put("userId", "123-456-789").put("username", "Peter"); + + prepareMockObjects(oldValue, newValue); + + assertThat(oldValue.toString()).isNotEqualTo(newValue.toString()); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isTrue(); + } + + @Test + void + hasIdenticalValue_WithSameNestedJsonNode_WithSameAttributeOrderInNestedJson_ShouldBeIdentical() { + + JsonNode oldNestedJson = + JsonNodeFactory.instance.objectNode().put("username", "Peter").put("userId", "123-456-789"); + JsonNode newNestedJson = + JsonNodeFactory.instance.objectNode().put("username", "Peter").put("userId", "123-456-789"); + + JsonNode oldJsonNode = JsonNodeFactory.instance.objectNode().set("nestedJson", oldNestedJson); + + JsonNode newJsonNode = JsonNodeFactory.instance.objectNode().set("nestedJson", newNestedJson); + + prepareMockObjects(oldJsonNode, newJsonNode); + + assertThat(oldJsonNode.toString()).isEqualTo(newJsonNode.toString()); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isTrue(); + } + + @Test + void + hasIdenticalValue_WithSameNestedJsonNode_WithDifferentAttributeOrderInNestedJson_ShouldBeIdentical() { + + JsonNode oldNestedJson = + JsonNodeFactory.instance.objectNode().put("username", "Peter").put("userId", "123-456-789"); + JsonNode newNestedJson = + JsonNodeFactory.instance.objectNode().put("userId", "123-456-789").put("username", "Peter"); + + JsonNode oldJsonNode = JsonNodeFactory.instance.objectNode().set("nestedJson", oldNestedJson); + + JsonNode newJsonNode = JsonNodeFactory.instance.objectNode().set("nestedJson", newNestedJson); + + prepareMockObjects(oldJsonNode, newJsonNode); + + assertThat(oldJsonNode.toString()).isNotEqualTo(newJsonNode.toString()); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isTrue(); + } + + @Test + void hasIdenticalValue_WithDifferentNestedJsonNode_ShouldNotBeIdentical() { + + JsonNode oldNestedJson = + JsonNodeFactory.instance.objectNode().put("username", "Peter").put("userId", "123-456-789"); + JsonNode newNestedJson = + JsonNodeFactory.instance.objectNode().put("userId", "129-382-189").put("username", "Peter"); - JsonNode oldJsonNode = JsonNodeFactory.instance.objectNode() - .set("nestedJson", oldNestedJson); + JsonNode oldJsonNode = JsonNodeFactory.instance.objectNode().set("nestedJson", oldNestedJson); - JsonNode newJsonNode = JsonNodeFactory.instance.objectNode() - .set("nestedJson", newNestedJson); + JsonNode newJsonNode = JsonNodeFactory.instance.objectNode().set("nestedJson", newNestedJson); - prepareMockObjects(oldJsonNode, newJsonNode); + prepareMockObjects(oldJsonNode, newJsonNode); - assertThat(oldJsonNode.toString()).isNotEqualTo(newJsonNode.toString()); - assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)).isFalse(); - } + assertThat(oldJsonNode.toString()).isNotEqualTo(newJsonNode.toString()); + assertThat(CustomObjectSyncUtils.hasIdenticalValue(oldCustomObject, newCustomObjectdraft)) + .isFalse(); + } } diff --git a/src/test/java/com/commercetools/sync/internals/helpers/PriceCompositeIdTest.java b/src/test/java/com/commercetools/sync/internals/helpers/PriceCompositeIdTest.java index 3a5d954e6c..edd177769e 100644 --- a/src/test/java/com/commercetools/sync/internals/helpers/PriceCompositeIdTest.java +++ b/src/test/java/com/commercetools/sync/internals/helpers/PriceCompositeIdTest.java @@ -1,231 +1,424 @@ package com.commercetools.sync.internals.helpers; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.*; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceFixtures.*; +import static com.neovisionaries.i18n.CountryCode.DE; +import static com.neovisionaries.i18n.CountryCode.NE; +import static com.neovisionaries.i18n.CountryCode.US; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.neovisionaries.i18n.CountryCode; import io.sphere.sdk.products.Price; import io.sphere.sdk.products.PriceDraft; +import java.time.ZonedDateTime; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.time.ZonedDateTime; -import java.util.stream.Stream; +class PriceCompositeIdTest { + private static final String CASE_1 = "with currency only"; + private static final String CASE_2 = "with currency and countryCode"; + private static final String CASE_3 = "with currency and channel"; + private static final String CASE_4 = "with currency, countryCode and channel"; + private static final String CASE_5 = "with currency and customerGroup"; + private static final String CASE_6 = "with currency, countryCode and customerGroup"; + private static final String CASE_7 = "with currency, channel and customerGroup"; + private static final String CASE_8 = "with currency, countryCode, channel and customerGroup"; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.*; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceFixtures.*; -import static com.neovisionaries.i18n.CountryCode.DE; -import static com.neovisionaries.i18n.CountryCode.NE; -import static com.neovisionaries.i18n.CountryCode.US; -import static org.junit.jupiter.api.Assertions.assertEquals; + private static final String CASE_9 = "with currency and validFrom"; + private static final String CASE_10 = "with currency, countryCode and validFrom"; + private static final String CASE_11 = "with currency, channel and validFrom"; + private static final String CASE_12 = "with currency, countryCode, channel and validFrom"; + private static final String CASE_13 = "with currency, customerGroup and validFrom"; + private static final String CASE_14 = "with currency, countryCode, customerGroup and validFrom"; + private static final String CASE_15 = "with currency, channel, customerGroup and validFrom"; + private static final String CASE_16 = + "with currency, countryCode, channel, customerGroup and validFrom"; -class PriceCompositeIdTest { - private static final String CASE_1 = "with currency only"; - private static final String CASE_2 = "with currency and countryCode"; - private static final String CASE_3 = "with currency and channel"; - private static final String CASE_4 = "with currency, countryCode and channel"; - private static final String CASE_5 = "with currency and customerGroup"; - private static final String CASE_6 = "with currency, countryCode and customerGroup"; - private static final String CASE_7 = "with currency, channel and customerGroup"; - private static final String CASE_8 = "with currency, countryCode, channel and customerGroup"; - - private static final String CASE_9 = "with currency and validFrom"; - private static final String CASE_10 = "with currency, countryCode and validFrom"; - private static final String CASE_11 = "with currency, channel and validFrom"; - private static final String CASE_12 = "with currency, countryCode, channel and validFrom"; - private static final String CASE_13 = "with currency, customerGroup and validFrom"; - private static final String CASE_14 = "with currency, countryCode, customerGroup and validFrom"; - private static final String CASE_15 = "with currency, channel, customerGroup and validFrom"; - private static final String CASE_16 = "with currency, countryCode, channel, customerGroup and validFrom"; - - private static final String CASE_17 = "with currency and validUntil"; - private static final String CASE_18 = "with currency, countryCode and validUntil"; - private static final String CASE_19 = "with currency, channel and validUntil"; - private static final String CASE_20 = "with currency, countryCode, channel and validUntil"; - private static final String CASE_21 = "with currency, customerGroup and validUntil"; - private static final String CASE_22 = "with currency, countryCode, customerGroup and validUntil"; - private static final String CASE_23 = "with currency, channel, customerGroup and validUntil"; - private static final String CASE_24 = "with currency, countryCode, channel, customerGroup and validUntil"; - - private static final String CASE_25 = "with currency, validFrom and validUntil"; - private static final String CASE_26 = "with currency, countryCode, validFrom and validUntil"; - private static final String CASE_27 = "with currency, channel, validFrom and validUntil"; - private static final String CASE_28 = "with currency, countryCode, channel, validFrom and validUntil"; - private static final String CASE_29 = "with currency, customerGroup, validFrom and validUntil"; - private static final String CASE_30 = "with currency, countryCode, customerGroup, validFrom and validUntil"; - private static final String CASE_31 = "with currency, channel, customerGroup, validFrom and validUntil"; - private static final String CASE_32 = - "with currency, countryCode, channel, customerGroup, validFrom and validUntil"; - - @ParameterizedTest(name = "[#ofPriceDraft]: {0}") - @MethodSource("ofPriceDraftTestCases") - void ofPriceDraft_ShouldCreateCompositeId(@Nonnull final String testCaseName, - @Nonnull final PriceDraft priceDraft, - @Nonnull final String currencyCode, - @Nullable final CountryCode countryCode, - @Nullable final String customerGroupId, - @Nullable final String channelId, - @Nullable final ZonedDateTime validFrom, - @Nullable final ZonedDateTime validUntil) { - - final PriceCompositeId priceCompositeId = PriceCompositeId.of(priceDraft); - - assertCompositeId(currencyCode, countryCode, customerGroupId, channelId, validFrom, validUntil, - priceCompositeId); - } - - private static Stream ofPriceDraftTestCases() { - return Stream.of( - Arguments.of(CASE_1, DRAFT_111_USD, "USD", null, null, null, null, null), - Arguments.of(CASE_2, DRAFT_US_111_USD, "USD", US, null, null, null, null), - Arguments.of(CASE_3, DRAFT_111_USD_CHANNEL1, "USD", null, null, "channel1", null, null), - Arguments.of(CASE_4, DRAFT_US_111_USD_CHANNEL1, "USD", US, null, "channel1", null, null), - Arguments.of(CASE_5, DRAFT_111_USD_CUST1, "USD", null, "cust1", null, null, null), - Arguments.of(CASE_6, DRAFT_DE_333_USD_CUST1, "USD", DE, "cust1", null, null, null), - Arguments.of(CASE_7, DRAFT_111_USD_CHANNEL1_CUST1, "USD", null, "cust1", "channel1", null, null), - Arguments.of(CASE_8, DRAFT_US_111_USD_CHANNEL1_CUST1, "USD", US, "cust1", "channel1", null, null), - - Arguments.of(CASE_9, DRAFT_111_USD_FROM_01, "USD", null, null, null, byMonth(1), null), - Arguments.of(CASE_10, DRAFT_US_111_USD_FROM_01, "USD", US, null, null, byMonth(1), null), - Arguments.of(CASE_11, DRAFT_111_USD_CHANNEL1_FROM_01, "USD", null, null, "channel1", byMonth(1), null), - Arguments.of(CASE_12, DRAFT_US_111_USD_CHANNEL1_FROM_01, "USD", US, null, "channel1", byMonth(1), null), - Arguments.of(CASE_13, DRAFT_111_USD_CUST1_FROM_01, "USD", null, "cust1", null, byMonth(1), null), - Arguments.of(CASE_14, DRAFT_US_111_USD_CUST1_FROM_01, "USD", US, "cust1", null, byMonth(1), null), - Arguments - .of(CASE_15, DRAFT_111_USD_CHANNEL1_CUST1_FROM_01, "USD", null, "cust1", "channel1", byMonth(1), null), - Arguments - .of(CASE_16, DRAFT_US_111_USD_CHANNEL1_CUST1_FROM_01, "USD", US, "cust1", "channel1", byMonth(1), null), - - Arguments.of(CASE_17, DRAFT_111_USD_UNTIL_01, "USD", null, null, null, null, byMonth(1)), - Arguments.of(CASE_18, DRAFT_US_111_USD_UNTIL_01, "USD", US, null, null, null, byMonth(1)), - Arguments.of(CASE_19, DRAFT_111_USD_CHANNEL1_UNTIL_01, "USD", null, null, "channel1", null, byMonth(1)), - Arguments.of(CASE_20, DRAFT_US_111_USD_CHANNEL1_UNTIL_01, "USD", US, null, "channel1", null, byMonth(1)), - Arguments.of(CASE_21, DRAFT_111_USD_CUST1_UNTIL_01, "USD", null, "cust1", null, null, byMonth(1)), - Arguments.of(CASE_22, DRAFT_US_111_USD_CUST1_UNTIL_01, "USD", US, "cust1", null, null, byMonth(1)), - Arguments - .of(CASE_23, DRAFT_111_USD_CHANNEL1_CUST1_UNTIL_01, "USD", null, "cust1", "channel1", null, byMonth(1)), - Arguments.of(CASE_24, DRAFT_US_111_USD_CHANNEL1_CUST1_UNTIL_01, "USD", US, "cust1", "channel1", null, - byMonth(1)), - - Arguments.of(CASE_25, DRAFT_111_EUR_01_02, "EUR", null, null, null, byMonth(1), byMonth(2)), - Arguments.of(CASE_26, DRAFT_DE_111_EUR_01_02, "EUR", DE, null, null, byMonth(1), byMonth(2)), - Arguments.of(CASE_27, DRAFT_111_EUR_CHANNEL1_01_02, "EUR", null, null, "channel1", byMonth(1), byMonth(2)), - Arguments.of(CASE_28, DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, "EUR", DE, null, "channel1", - byMonth(1), byMonth(2)), - Arguments.of(CASE_29, DRAFT_666_USD_CUST1_01_02, "USD", null, "cust1", null, byMonth(1), byMonth(2)), - Arguments.of(CASE_30, DRAFT_US_666_USD_CUST1_01_02, "USD", US, "cust1", null, byMonth(1), byMonth(2)), - Arguments.of(CASE_31, DRAFT_777_CUST1_CHANNEL1_EUR_05_07, "EUR", null, "cust1", "channel1", byMonth(5), - byMonth(7)), - Arguments.of(CASE_32, DRAFT_NE_777_CUST1_CHANNEL1_EUR_05_07, "EUR", NE, "cust1", "channel1", - byMonth(5), byMonth(7)) - ); - } - - @ParameterizedTest(name = "[#ofPrice]: {0}") - @MethodSource("ofPriceTestCases") - void ofPrice_ShouldCreateCompositeId(@Nonnull final String testCaseName, - @Nonnull final Price price, - @Nonnull final String currencyCode, - @Nullable final CountryCode countryCode, - @Nullable final String customerGroupId, - @Nullable final String channelId, - @Nullable final ZonedDateTime validFrom, - @Nullable final ZonedDateTime validUntil) { - - final PriceCompositeId priceCompositeId = PriceCompositeId.of(price); - - assertCompositeId(currencyCode, countryCode, customerGroupId, channelId, validFrom, validUntil, - priceCompositeId); - } - - - private static Stream ofPriceTestCases() { - return Stream.of( - Arguments.of(CASE_1, USD_111, "USD", null, null, null, null, null), - Arguments.of(CASE_2, US_111_USD, "USD", US, null, null, null, null), - Arguments.of(CASE_3, EUR_345_CHANNEL1, "EUR", null, null, "channel1", null, null), - Arguments.of(CASE_4, DE_345_EUR_CHANNEL1, "EUR", DE, null, "channel1", null, null), - Arguments.of(CASE_5, EUR_345_CUST2, "EUR", null, "cust2", null, null, null), - Arguments.of(CASE_6, DE_345_EUR_CUST2, "EUR", DE, "cust2", null, null, null), - Arguments.of(CASE_7, EUR_345_CHANNEL1_CUST1, "EUR", null, "cust1", "channel1", null, null), - Arguments.of(CASE_8, DE_345_EUR_CHANNEL1_CUST1, "EUR", DE, "cust1", "channel1", null, null), - - Arguments.of(CASE_9, USD_111_FROM_01, "USD", null, null, null, byMonth(1), null), - Arguments.of(CASE_10, US_111_USD_FROM_01, "USD", US, null, null, byMonth(1), null), - Arguments.of(CASE_11, USD_111_CHANNEL1_FROM_01, "USD", null, null, "channel1", byMonth(1), null), - Arguments.of(CASE_12, US_111_USD_CHANNEL1_FROM_01, "USD", US, null, "channel1", byMonth(1), null), - Arguments.of(CASE_13, USD_111_CUST1_FROM_01, "USD", null, "cust1", null, byMonth(1), null), - Arguments.of(CASE_14, US_111_USD_CUST1_FROM_01, "USD", US, "cust1", null, byMonth(1), null), - Arguments.of(CASE_15, USD_111_CHANNEL1_CUST1_FROM_01, "USD", null, "cust1", "channel1", byMonth(1), null), - Arguments.of(CASE_16, US_111_USD_CHANNEL1_CUST1_FROM_01, "USD", US, "cust1", "channel1", byMonth(1), null), - - Arguments.of(CASE_17, USD_111_UNTIL_01, "USD", null, null, null, null, byMonth(1)), - Arguments.of(CASE_18, US_111_USD_UNTIL_01, "USD", US, null, null, null, byMonth(1)), - Arguments.of(CASE_19, USD_111_CHANNEL1_UNTIL_01, "USD", null, null, "channel1", null, byMonth(1)), - Arguments.of(CASE_20, US_111_USD_CHANNEL1_UNTIL_01, "USD", US, null, "channel1", null, byMonth(1)), - Arguments.of(CASE_21, USD_111_CUST1_UNTIL_01, "USD", null, "cust1", null, null, byMonth(1)), - Arguments.of(CASE_22, US_111_USD_CUST1_UNTIL_01, "USD", US, "cust1", null, null, byMonth(1)), - Arguments.of(CASE_23, USD_111_CHANNEL1_CUST1_UNTIL_01, "USD", null, "cust1", "channel1", null, byMonth(1)), - Arguments.of(CASE_24, US_111_USD_CHANNEL1_CUST1_UNTIL_01, "USD", US, "cust1", "channel1", null, byMonth(1)), - - Arguments.of(CASE_25, USD_111_01_02, "USD", null, null, null, byMonth(1), byMonth(2)), - Arguments.of(CASE_26, US_111_USD_01_02, "USD", US, null, null, byMonth(1), byMonth(2)), - Arguments.of(CASE_27, USD_111_CHANNEL1_01_02, "USD", null, null, "channel1", byMonth(1), byMonth(2)), - Arguments.of(CASE_28, DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX, "EUR", DE, null, "channel2", - byMonth(1), byMonth(2)), - Arguments.of(CASE_29, USD_111_CUST1_01_02, "USD", null, "cust1", null, byMonth(1), byMonth(2)), - Arguments.of(CASE_30, US_111_USD_CUST1_01_02, "USD", US, "cust1", null, byMonth(1), byMonth(2)), - Arguments.of(CASE_31, USD_555_CUST2_CHANNEL1_01_02, "USD", null, "cust2", "channel1", byMonth(1), - byMonth(2)), - Arguments.of(CASE_32, US_555_USD_CUST2_CHANNEL1_01_02, "USD", US, "cust2", "channel1", byMonth(1), - byMonth(2)) - ); - } - - - private static void assertCompositeId(@Nonnull final String currencyCode, - @Nullable final CountryCode countryCode, - @Nullable final String customerGroupId, - @Nullable final String channelId, - @Nullable final ZonedDateTime validFrom, - @Nullable final ZonedDateTime validUntil, - @Nonnull final PriceCompositeId priceCompositeId) { - - assertEquals(currencyCode, priceCompositeId.getCurrencyCode()); - assertEquals(countryCode, priceCompositeId.getCountryCode()); - assertEquals(customerGroupId, priceCompositeId.getCustomerGroupId()); - assertEquals(channelId, priceCompositeId.getChannelId()); - assertEquals(validFrom, priceCompositeId.getValidFrom()); - assertEquals(validUntil, priceCompositeId.getValidUntil()); - } - - @ParameterizedTest(name = "[#equals]: {0}") - @MethodSource("equalsTestCases") - @SuppressWarnings("PMD") - void equals(@Nonnull final String testCaseName, - @Nonnull final PriceCompositeId thisPriceCompositeId, - @Nullable final Object otherObject, - final boolean expectedResult) { - - final boolean result = thisPriceCompositeId.equals(otherObject); - - assertEquals(expectedResult, result); - } - - private static Stream equalsTestCases() { - final PriceCompositeId priceCompositeId = PriceCompositeId.of(USD_111); - return Stream.of( - Arguments.of("Null priceCompositeId", priceCompositeId, null, false), - Arguments.of("Not instance of priceCompositeId", priceCompositeId, "foo", false), - Arguments.of("Exact same instances", priceCompositeId, priceCompositeId, true), - Arguments.of("Equal but different instances", priceCompositeId, PriceCompositeId.of(USD_111), true), - Arguments.of("Not equal countryCode", priceCompositeId, PriceCompositeId.of(DE_111_EUR), false), - Arguments.of("Not equal currencyCode", priceCompositeId, PriceCompositeId.of(EUR_345_CHANNEL1), false), - Arguments.of("Not equal channelId", priceCompositeId, PriceCompositeId.of(USD_111_CHANNEL1_01_02), false), - Arguments.of("Not equal customerGroupId", priceCompositeId, PriceCompositeId.of(USD_111_CUST1_01_02), - false), - Arguments.of("Not equal validFrom", priceCompositeId, PriceCompositeId.of(USD_111_FROM_01), false), - Arguments.of("Not equal validUntil", priceCompositeId, PriceCompositeId.of(USD_111_UNTIL_01), false) - ); - } + private static final String CASE_17 = "with currency and validUntil"; + private static final String CASE_18 = "with currency, countryCode and validUntil"; + private static final String CASE_19 = "with currency, channel and validUntil"; + private static final String CASE_20 = "with currency, countryCode, channel and validUntil"; + private static final String CASE_21 = "with currency, customerGroup and validUntil"; + private static final String CASE_22 = "with currency, countryCode, customerGroup and validUntil"; + private static final String CASE_23 = "with currency, channel, customerGroup and validUntil"; + private static final String CASE_24 = + "with currency, countryCode, channel, customerGroup and validUntil"; + + private static final String CASE_25 = "with currency, validFrom and validUntil"; + private static final String CASE_26 = "with currency, countryCode, validFrom and validUntil"; + private static final String CASE_27 = "with currency, channel, validFrom and validUntil"; + private static final String CASE_28 = + "with currency, countryCode, channel, validFrom and validUntil"; + private static final String CASE_29 = "with currency, customerGroup, validFrom and validUntil"; + private static final String CASE_30 = + "with currency, countryCode, customerGroup, validFrom and validUntil"; + private static final String CASE_31 = + "with currency, channel, customerGroup, validFrom and validUntil"; + private static final String CASE_32 = + "with currency, countryCode, channel, customerGroup, validFrom and validUntil"; + + @ParameterizedTest(name = "[#ofPriceDraft]: {0}") + @MethodSource("ofPriceDraftTestCases") + void ofPriceDraft_ShouldCreateCompositeId( + @Nonnull final String testCaseName, + @Nonnull final PriceDraft priceDraft, + @Nonnull final String currencyCode, + @Nullable final CountryCode countryCode, + @Nullable final String customerGroupId, + @Nullable final String channelId, + @Nullable final ZonedDateTime validFrom, + @Nullable final ZonedDateTime validUntil) { + + final PriceCompositeId priceCompositeId = PriceCompositeId.of(priceDraft); + + assertCompositeId( + currencyCode, + countryCode, + customerGroupId, + channelId, + validFrom, + validUntil, + priceCompositeId); + } + + private static Stream ofPriceDraftTestCases() { + return Stream.of( + Arguments.of(CASE_1, DRAFT_111_USD, "USD", null, null, null, null, null), + Arguments.of(CASE_2, DRAFT_US_111_USD, "USD", US, null, null, null, null), + Arguments.of(CASE_3, DRAFT_111_USD_CHANNEL1, "USD", null, null, "channel1", null, null), + Arguments.of(CASE_4, DRAFT_US_111_USD_CHANNEL1, "USD", US, null, "channel1", null, null), + Arguments.of(CASE_5, DRAFT_111_USD_CUST1, "USD", null, "cust1", null, null, null), + Arguments.of(CASE_6, DRAFT_DE_333_USD_CUST1, "USD", DE, "cust1", null, null, null), + Arguments.of( + CASE_7, DRAFT_111_USD_CHANNEL1_CUST1, "USD", null, "cust1", "channel1", null, null), + Arguments.of( + CASE_8, DRAFT_US_111_USD_CHANNEL1_CUST1, "USD", US, "cust1", "channel1", null, null), + Arguments.of(CASE_9, DRAFT_111_USD_FROM_01, "USD", null, null, null, byMonth(1), null), + Arguments.of(CASE_10, DRAFT_US_111_USD_FROM_01, "USD", US, null, null, byMonth(1), null), + Arguments.of( + CASE_11, + DRAFT_111_USD_CHANNEL1_FROM_01, + "USD", + null, + null, + "channel1", + byMonth(1), + null), + Arguments.of( + CASE_12, + DRAFT_US_111_USD_CHANNEL1_FROM_01, + "USD", + US, + null, + "channel1", + byMonth(1), + null), + Arguments.of( + CASE_13, DRAFT_111_USD_CUST1_FROM_01, "USD", null, "cust1", null, byMonth(1), null), + Arguments.of( + CASE_14, DRAFT_US_111_USD_CUST1_FROM_01, "USD", US, "cust1", null, byMonth(1), null), + Arguments.of( + CASE_15, + DRAFT_111_USD_CHANNEL1_CUST1_FROM_01, + "USD", + null, + "cust1", + "channel1", + byMonth(1), + null), + Arguments.of( + CASE_16, + DRAFT_US_111_USD_CHANNEL1_CUST1_FROM_01, + "USD", + US, + "cust1", + "channel1", + byMonth(1), + null), + Arguments.of(CASE_17, DRAFT_111_USD_UNTIL_01, "USD", null, null, null, null, byMonth(1)), + Arguments.of(CASE_18, DRAFT_US_111_USD_UNTIL_01, "USD", US, null, null, null, byMonth(1)), + Arguments.of( + CASE_19, + DRAFT_111_USD_CHANNEL1_UNTIL_01, + "USD", + null, + null, + "channel1", + null, + byMonth(1)), + Arguments.of( + CASE_20, + DRAFT_US_111_USD_CHANNEL1_UNTIL_01, + "USD", + US, + null, + "channel1", + null, + byMonth(1)), + Arguments.of( + CASE_21, DRAFT_111_USD_CUST1_UNTIL_01, "USD", null, "cust1", null, null, byMonth(1)), + Arguments.of( + CASE_22, DRAFT_US_111_USD_CUST1_UNTIL_01, "USD", US, "cust1", null, null, byMonth(1)), + Arguments.of( + CASE_23, + DRAFT_111_USD_CHANNEL1_CUST1_UNTIL_01, + "USD", + null, + "cust1", + "channel1", + null, + byMonth(1)), + Arguments.of( + CASE_24, + DRAFT_US_111_USD_CHANNEL1_CUST1_UNTIL_01, + "USD", + US, + "cust1", + "channel1", + null, + byMonth(1)), + Arguments.of(CASE_25, DRAFT_111_EUR_01_02, "EUR", null, null, null, byMonth(1), byMonth(2)), + Arguments.of( + CASE_26, DRAFT_DE_111_EUR_01_02, "EUR", DE, null, null, byMonth(1), byMonth(2)), + Arguments.of( + CASE_27, + DRAFT_111_EUR_CHANNEL1_01_02, + "EUR", + null, + null, + "channel1", + byMonth(1), + byMonth(2)), + Arguments.of( + CASE_28, + DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, + "EUR", + DE, + null, + "channel1", + byMonth(1), + byMonth(2)), + Arguments.of( + CASE_29, DRAFT_666_USD_CUST1_01_02, "USD", null, "cust1", null, byMonth(1), byMonth(2)), + Arguments.of( + CASE_30, + DRAFT_US_666_USD_CUST1_01_02, + "USD", + US, + "cust1", + null, + byMonth(1), + byMonth(2)), + Arguments.of( + CASE_31, + DRAFT_777_CUST1_CHANNEL1_EUR_05_07, + "EUR", + null, + "cust1", + "channel1", + byMonth(5), + byMonth(7)), + Arguments.of( + CASE_32, + DRAFT_NE_777_CUST1_CHANNEL1_EUR_05_07, + "EUR", + NE, + "cust1", + "channel1", + byMonth(5), + byMonth(7))); + } + + @ParameterizedTest(name = "[#ofPrice]: {0}") + @MethodSource("ofPriceTestCases") + void ofPrice_ShouldCreateCompositeId( + @Nonnull final String testCaseName, + @Nonnull final Price price, + @Nonnull final String currencyCode, + @Nullable final CountryCode countryCode, + @Nullable final String customerGroupId, + @Nullable final String channelId, + @Nullable final ZonedDateTime validFrom, + @Nullable final ZonedDateTime validUntil) { + + final PriceCompositeId priceCompositeId = PriceCompositeId.of(price); + + assertCompositeId( + currencyCode, + countryCode, + customerGroupId, + channelId, + validFrom, + validUntil, + priceCompositeId); + } + + private static Stream ofPriceTestCases() { + return Stream.of( + Arguments.of(CASE_1, USD_111, "USD", null, null, null, null, null), + Arguments.of(CASE_2, US_111_USD, "USD", US, null, null, null, null), + Arguments.of(CASE_3, EUR_345_CHANNEL1, "EUR", null, null, "channel1", null, null), + Arguments.of(CASE_4, DE_345_EUR_CHANNEL1, "EUR", DE, null, "channel1", null, null), + Arguments.of(CASE_5, EUR_345_CUST2, "EUR", null, "cust2", null, null, null), + Arguments.of(CASE_6, DE_345_EUR_CUST2, "EUR", DE, "cust2", null, null, null), + Arguments.of(CASE_7, EUR_345_CHANNEL1_CUST1, "EUR", null, "cust1", "channel1", null, null), + Arguments.of(CASE_8, DE_345_EUR_CHANNEL1_CUST1, "EUR", DE, "cust1", "channel1", null, null), + Arguments.of(CASE_9, USD_111_FROM_01, "USD", null, null, null, byMonth(1), null), + Arguments.of(CASE_10, US_111_USD_FROM_01, "USD", US, null, null, byMonth(1), null), + Arguments.of( + CASE_11, USD_111_CHANNEL1_FROM_01, "USD", null, null, "channel1", byMonth(1), null), + Arguments.of( + CASE_12, US_111_USD_CHANNEL1_FROM_01, "USD", US, null, "channel1", byMonth(1), null), + Arguments.of(CASE_13, USD_111_CUST1_FROM_01, "USD", null, "cust1", null, byMonth(1), null), + Arguments.of(CASE_14, US_111_USD_CUST1_FROM_01, "USD", US, "cust1", null, byMonth(1), null), + Arguments.of( + CASE_15, + USD_111_CHANNEL1_CUST1_FROM_01, + "USD", + null, + "cust1", + "channel1", + byMonth(1), + null), + Arguments.of( + CASE_16, + US_111_USD_CHANNEL1_CUST1_FROM_01, + "USD", + US, + "cust1", + "channel1", + byMonth(1), + null), + Arguments.of(CASE_17, USD_111_UNTIL_01, "USD", null, null, null, null, byMonth(1)), + Arguments.of(CASE_18, US_111_USD_UNTIL_01, "USD", US, null, null, null, byMonth(1)), + Arguments.of( + CASE_19, USD_111_CHANNEL1_UNTIL_01, "USD", null, null, "channel1", null, byMonth(1)), + Arguments.of( + CASE_20, US_111_USD_CHANNEL1_UNTIL_01, "USD", US, null, "channel1", null, byMonth(1)), + Arguments.of(CASE_21, USD_111_CUST1_UNTIL_01, "USD", null, "cust1", null, null, byMonth(1)), + Arguments.of( + CASE_22, US_111_USD_CUST1_UNTIL_01, "USD", US, "cust1", null, null, byMonth(1)), + Arguments.of( + CASE_23, + USD_111_CHANNEL1_CUST1_UNTIL_01, + "USD", + null, + "cust1", + "channel1", + null, + byMonth(1)), + Arguments.of( + CASE_24, + US_111_USD_CHANNEL1_CUST1_UNTIL_01, + "USD", + US, + "cust1", + "channel1", + null, + byMonth(1)), + Arguments.of(CASE_25, USD_111_01_02, "USD", null, null, null, byMonth(1), byMonth(2)), + Arguments.of(CASE_26, US_111_USD_01_02, "USD", US, null, null, byMonth(1), byMonth(2)), + Arguments.of( + CASE_27, USD_111_CHANNEL1_01_02, "USD", null, null, "channel1", byMonth(1), byMonth(2)), + Arguments.of( + CASE_28, + DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX, + "EUR", + DE, + null, + "channel2", + byMonth(1), + byMonth(2)), + Arguments.of( + CASE_29, USD_111_CUST1_01_02, "USD", null, "cust1", null, byMonth(1), byMonth(2)), + Arguments.of( + CASE_30, US_111_USD_CUST1_01_02, "USD", US, "cust1", null, byMonth(1), byMonth(2)), + Arguments.of( + CASE_31, + USD_555_CUST2_CHANNEL1_01_02, + "USD", + null, + "cust2", + "channel1", + byMonth(1), + byMonth(2)), + Arguments.of( + CASE_32, + US_555_USD_CUST2_CHANNEL1_01_02, + "USD", + US, + "cust2", + "channel1", + byMonth(1), + byMonth(2))); + } + + private static void assertCompositeId( + @Nonnull final String currencyCode, + @Nullable final CountryCode countryCode, + @Nullable final String customerGroupId, + @Nullable final String channelId, + @Nullable final ZonedDateTime validFrom, + @Nullable final ZonedDateTime validUntil, + @Nonnull final PriceCompositeId priceCompositeId) { + + assertEquals(currencyCode, priceCompositeId.getCurrencyCode()); + assertEquals(countryCode, priceCompositeId.getCountryCode()); + assertEquals(customerGroupId, priceCompositeId.getCustomerGroupId()); + assertEquals(channelId, priceCompositeId.getChannelId()); + assertEquals(validFrom, priceCompositeId.getValidFrom()); + assertEquals(validUntil, priceCompositeId.getValidUntil()); + } + + @ParameterizedTest(name = "[#equals]: {0}") + @MethodSource("equalsTestCases") + @SuppressWarnings("PMD") + void equals( + @Nonnull final String testCaseName, + @Nonnull final PriceCompositeId thisPriceCompositeId, + @Nullable final Object otherObject, + final boolean expectedResult) { + + final boolean result = thisPriceCompositeId.equals(otherObject); + + assertEquals(expectedResult, result); + } + + private static Stream equalsTestCases() { + final PriceCompositeId priceCompositeId = PriceCompositeId.of(USD_111); + return Stream.of( + Arguments.of("Null priceCompositeId", priceCompositeId, null, false), + Arguments.of("Not instance of priceCompositeId", priceCompositeId, "foo", false), + Arguments.of("Exact same instances", priceCompositeId, priceCompositeId, true), + Arguments.of( + "Equal but different instances", priceCompositeId, PriceCompositeId.of(USD_111), true), + Arguments.of( + "Not equal countryCode", priceCompositeId, PriceCompositeId.of(DE_111_EUR), false), + Arguments.of( + "Not equal currencyCode", + priceCompositeId, + PriceCompositeId.of(EUR_345_CHANNEL1), + false), + Arguments.of( + "Not equal channelId", + priceCompositeId, + PriceCompositeId.of(USD_111_CHANNEL1_01_02), + false), + Arguments.of( + "Not equal customerGroupId", + priceCompositeId, + PriceCompositeId.of(USD_111_CUST1_01_02), + false), + Arguments.of( + "Not equal validFrom", priceCompositeId, PriceCompositeId.of(USD_111_FROM_01), false), + Arguments.of( + "Not equal validUntil", + priceCompositeId, + PriceCompositeId.of(USD_111_UNTIL_01), + false)); + } } diff --git a/src/test/java/com/commercetools/sync/internals/helpers/UpdateActionsSortUtilsTest.java b/src/test/java/com/commercetools/sync/internals/helpers/UpdateActionsSortUtilsTest.java index b78a403b67..cea332dff1 100644 --- a/src/test/java/com/commercetools/sync/internals/helpers/UpdateActionsSortUtilsTest.java +++ b/src/test/java/com/commercetools/sync/internals/helpers/UpdateActionsSortUtilsTest.java @@ -1,159 +1,166 @@ package com.commercetools.sync.internals.helpers; +import static com.commercetools.sync.internals.utils.UpdateActionsSortUtils.sortPriceActions; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_NE_777_EUR_05_07; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.commands.updateactions.AddPrice; import io.sphere.sdk.products.commands.updateactions.ChangePrice; import io.sphere.sdk.products.commands.updateactions.RemovePrice; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import javax.annotation.Nonnull; -import java.util.List; -import java.util.stream.Stream; - -import static com.commercetools.sync.internals.utils.UpdateActionsSortUtils.sortPriceActions; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_NE_777_EUR_05_07; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertEquals; - class UpdateActionsSortUtilsTest { - @ParameterizedTest(name = "[#sortPriceActions]: {0}") - @MethodSource("sortPriceActionsTestCases") - void sortPriceActionsTest(@Nonnull final String testCaseName, - @Nonnull final List> updateActions, - @Nonnull final List> expectedResult) { - - final List> result = sortPriceActions(updateActions); - - assertEquals(expectedResult, result); - } - - /** - * Testing all different conditions stated in Effective Java for comparator implementation: - * - anticommutation - * - exception symmetry - * - transitivity - * - consistency with equals. - * - *

More info here: http://www.javapractices.com/topic/TopicAction.do?Id=10 - */ - private static Stream sortPriceActionsTestCases() { - final String case1 = "empty list"; - final String case2 = "one element"; - - final String case3 = "removePrice, changePrice"; - final String case4 = "changePrice, removePrice"; - - final String case5 = "removePrice, addPrice"; - final String case6 = "addPrice, removePrice"; - - final String case7 = "changePrice, addPrice"; - final String case8 = "addPrice, changePrice"; - - final String case9 = "RemovePrice, changePrice, addPrice"; - final String case10 = "RemovePrice, addPrice, changePrice"; - - final String case11 = "addPrice, changePrice, RemovePrice"; - final String case12 = "addPrice, RemovePrice, changePrice"; - - final String case13 = "changePrice, RemovePrice, addPrice"; - final String case14 = "changePrice, addPrice, RemovePrice"; - - final String case15 = "changePrice, changePrice2"; - final String case16 = "changePrice2, changePrice"; - final String case17 = "removePrice, removePrice2"; - final String case18 = "removePrice2, removePrice"; - final String case19 = "addPrice, addPrice2"; - final String case20 = "addPrice2, addPrice"; - - final String case21 = "removePrice, changePrice2"; - final String case22 = "changePrice2, removePrice"; - - final String case23 = "removePrice, addPrice"; - final String case24 = "addPrice, removePrice"; - - final String case25 = "changePrice2, addPrice"; - final String case26 = "addPrice, changePrice2"; - - final String case27 = "RemovePrice, changePrice2, addPrice"; - final String case28 = "RemovePrice, addPrice, changePrice2"; - - final String case29 = "addPrice, changePrice2, RemovePrice"; - final String case30 = "addPrice, RemovePrice, changePrice2"; - - final String case31 = "changePrice2, RemovePrice, addPrice"; - final String case32 = "changePrice2, addPrice, RemovePrice"; - - - final UpdateAction changePrice = ChangePrice.of("1", null, true); - final UpdateAction changePrice2 = ChangePrice.of("2", null, true); - final UpdateAction addPrice = AddPrice.of(1, DRAFT_NE_777_EUR_05_07); - final UpdateAction addPrice2 = AddPrice.of(2, DRAFT_NE_777_EUR_05_07); - final UpdateAction removePrice = RemovePrice.of("1"); - final UpdateAction removePrice2 = RemovePrice.of("2"); - - return Stream.of( - Arguments.of(case1, emptyList(), emptyList()), - Arguments.of(case2, singletonList(changePrice), singletonList(changePrice)), - - Arguments.of(case3, asList(removePrice, changePrice), asList(removePrice, changePrice)), - Arguments.of(case4, asList(changePrice, removePrice), asList(removePrice, changePrice)), - - Arguments.of(case5, asList(removePrice, addPrice), asList(removePrice, addPrice)), - Arguments.of(case6, asList(addPrice, removePrice), asList(removePrice, addPrice)), - - Arguments.of(case7, asList(changePrice, addPrice), asList(changePrice, addPrice)), - Arguments.of(case8, asList(addPrice, changePrice), asList(changePrice, addPrice)), - - Arguments.of(case9, asList(removePrice, changePrice, addPrice), asList(removePrice, changePrice, addPrice)), - Arguments - .of(case10, asList(removePrice, addPrice, changePrice), asList(removePrice, changePrice, addPrice)), - - Arguments - .of(case11, asList(addPrice, changePrice, removePrice), asList(removePrice, changePrice, addPrice)), - Arguments - .of(case12, asList(addPrice, removePrice, changePrice), asList(removePrice, changePrice, addPrice)), - - Arguments - .of(case13, asList(changePrice, removePrice, addPrice), asList(removePrice, changePrice, addPrice)), - Arguments - .of(case14, asList(changePrice, addPrice, removePrice), asList(removePrice, changePrice, addPrice)), - - Arguments.of(case15, asList(changePrice, changePrice2), asList(changePrice, changePrice2)), - Arguments.of(case16, asList(changePrice2, changePrice), asList(changePrice2, changePrice)), - Arguments.of(case17, asList(removePrice, removePrice2), asList(removePrice, removePrice2)), - Arguments.of(case18, asList(removePrice2, removePrice), asList(removePrice2, removePrice)), - Arguments.of(case19, asList(addPrice, addPrice2), asList(addPrice, addPrice2)), - Arguments.of(case20, asList(addPrice2, addPrice), asList(addPrice2, addPrice)), - - Arguments.of(case21, asList(removePrice, changePrice2), asList(removePrice, changePrice2)), - Arguments.of(case22, asList(changePrice2, removePrice), asList(removePrice, changePrice2)), - - Arguments.of(case23, asList(removePrice, addPrice), asList(removePrice, addPrice)), - Arguments.of(case24, asList(addPrice, removePrice), asList(removePrice, addPrice)), - - Arguments.of(case25, asList(changePrice2, addPrice), asList(changePrice2, addPrice)), - Arguments.of(case26, asList(addPrice, changePrice2), asList(changePrice2, addPrice)), - - Arguments - .of(case27, asList(removePrice, changePrice2, addPrice), asList(removePrice, changePrice2, addPrice)), - Arguments - .of(case28, asList(removePrice, addPrice, changePrice2), asList(removePrice, changePrice2, addPrice)), - - Arguments - .of(case29, asList(addPrice, changePrice2, removePrice), asList(removePrice, changePrice2, addPrice)), - Arguments - .of(case30, asList(addPrice, removePrice, changePrice2), asList(removePrice, changePrice2, addPrice)), - - Arguments - .of(case31, asList(changePrice2, removePrice, addPrice), asList(removePrice, changePrice2, addPrice)), - Arguments - .of(case32, asList(changePrice2, addPrice, removePrice), asList(removePrice, changePrice2, addPrice)) - ); - } + @ParameterizedTest(name = "[#sortPriceActions]: {0}") + @MethodSource("sortPriceActionsTestCases") + void sortPriceActionsTest( + @Nonnull final String testCaseName, + @Nonnull final List> updateActions, + @Nonnull final List> expectedResult) { + + final List> result = sortPriceActions(updateActions); + + assertEquals(expectedResult, result); + } + + /** + * Testing all different conditions stated in Effective Java for comparator implementation: - + * anticommutation - exception symmetry - transitivity - consistency with equals. + * + *

More info here: http://www.javapractices.com/topic/TopicAction.do?Id=10 + */ + private static Stream sortPriceActionsTestCases() { + final String case1 = "empty list"; + final String case2 = "one element"; + + final String case3 = "removePrice, changePrice"; + final String case4 = "changePrice, removePrice"; + + final String case5 = "removePrice, addPrice"; + final String case6 = "addPrice, removePrice"; + + final String case7 = "changePrice, addPrice"; + final String case8 = "addPrice, changePrice"; + + final String case9 = "RemovePrice, changePrice, addPrice"; + final String case10 = "RemovePrice, addPrice, changePrice"; + + final String case11 = "addPrice, changePrice, RemovePrice"; + final String case12 = "addPrice, RemovePrice, changePrice"; + + final String case13 = "changePrice, RemovePrice, addPrice"; + final String case14 = "changePrice, addPrice, RemovePrice"; + + final String case15 = "changePrice, changePrice2"; + final String case16 = "changePrice2, changePrice"; + final String case17 = "removePrice, removePrice2"; + final String case18 = "removePrice2, removePrice"; + final String case19 = "addPrice, addPrice2"; + final String case20 = "addPrice2, addPrice"; + + final String case21 = "removePrice, changePrice2"; + final String case22 = "changePrice2, removePrice"; + + final String case23 = "removePrice, addPrice"; + final String case24 = "addPrice, removePrice"; + + final String case25 = "changePrice2, addPrice"; + final String case26 = "addPrice, changePrice2"; + + final String case27 = "RemovePrice, changePrice2, addPrice"; + final String case28 = "RemovePrice, addPrice, changePrice2"; + + final String case29 = "addPrice, changePrice2, RemovePrice"; + final String case30 = "addPrice, RemovePrice, changePrice2"; + + final String case31 = "changePrice2, RemovePrice, addPrice"; + final String case32 = "changePrice2, addPrice, RemovePrice"; + + final UpdateAction changePrice = ChangePrice.of("1", null, true); + final UpdateAction changePrice2 = ChangePrice.of("2", null, true); + final UpdateAction addPrice = AddPrice.of(1, DRAFT_NE_777_EUR_05_07); + final UpdateAction addPrice2 = AddPrice.of(2, DRAFT_NE_777_EUR_05_07); + final UpdateAction removePrice = RemovePrice.of("1"); + final UpdateAction removePrice2 = RemovePrice.of("2"); + + return Stream.of( + Arguments.of(case1, emptyList(), emptyList()), + Arguments.of(case2, singletonList(changePrice), singletonList(changePrice)), + Arguments.of(case3, asList(removePrice, changePrice), asList(removePrice, changePrice)), + Arguments.of(case4, asList(changePrice, removePrice), asList(removePrice, changePrice)), + Arguments.of(case5, asList(removePrice, addPrice), asList(removePrice, addPrice)), + Arguments.of(case6, asList(addPrice, removePrice), asList(removePrice, addPrice)), + Arguments.of(case7, asList(changePrice, addPrice), asList(changePrice, addPrice)), + Arguments.of(case8, asList(addPrice, changePrice), asList(changePrice, addPrice)), + Arguments.of( + case9, + asList(removePrice, changePrice, addPrice), + asList(removePrice, changePrice, addPrice)), + Arguments.of( + case10, + asList(removePrice, addPrice, changePrice), + asList(removePrice, changePrice, addPrice)), + Arguments.of( + case11, + asList(addPrice, changePrice, removePrice), + asList(removePrice, changePrice, addPrice)), + Arguments.of( + case12, + asList(addPrice, removePrice, changePrice), + asList(removePrice, changePrice, addPrice)), + Arguments.of( + case13, + asList(changePrice, removePrice, addPrice), + asList(removePrice, changePrice, addPrice)), + Arguments.of( + case14, + asList(changePrice, addPrice, removePrice), + asList(removePrice, changePrice, addPrice)), + Arguments.of(case15, asList(changePrice, changePrice2), asList(changePrice, changePrice2)), + Arguments.of(case16, asList(changePrice2, changePrice), asList(changePrice2, changePrice)), + Arguments.of(case17, asList(removePrice, removePrice2), asList(removePrice, removePrice2)), + Arguments.of(case18, asList(removePrice2, removePrice), asList(removePrice2, removePrice)), + Arguments.of(case19, asList(addPrice, addPrice2), asList(addPrice, addPrice2)), + Arguments.of(case20, asList(addPrice2, addPrice), asList(addPrice2, addPrice)), + Arguments.of(case21, asList(removePrice, changePrice2), asList(removePrice, changePrice2)), + Arguments.of(case22, asList(changePrice2, removePrice), asList(removePrice, changePrice2)), + Arguments.of(case23, asList(removePrice, addPrice), asList(removePrice, addPrice)), + Arguments.of(case24, asList(addPrice, removePrice), asList(removePrice, addPrice)), + Arguments.of(case25, asList(changePrice2, addPrice), asList(changePrice2, addPrice)), + Arguments.of(case26, asList(addPrice, changePrice2), asList(changePrice2, addPrice)), + Arguments.of( + case27, + asList(removePrice, changePrice2, addPrice), + asList(removePrice, changePrice2, addPrice)), + Arguments.of( + case28, + asList(removePrice, addPrice, changePrice2), + asList(removePrice, changePrice2, addPrice)), + Arguments.of( + case29, + asList(addPrice, changePrice2, removePrice), + asList(removePrice, changePrice2, addPrice)), + Arguments.of( + case30, + asList(addPrice, removePrice, changePrice2), + asList(removePrice, changePrice2, addPrice)), + Arguments.of( + case31, + asList(changePrice2, removePrice, addPrice), + asList(removePrice, changePrice2, addPrice)), + Arguments.of( + case32, + asList(changePrice2, addPrice, removePrice), + asList(removePrice, changePrice2, addPrice))); + } } diff --git a/src/test/java/com/commercetools/sync/internals/utils/UnorderedCollectionSyncUtilsTest.java b/src/test/java/com/commercetools/sync/internals/utils/UnorderedCollectionSyncUtilsTest.java index 659146a0df..442a34148d 100644 --- a/src/test/java/com/commercetools/sync/internals/utils/UnorderedCollectionSyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/internals/utils/UnorderedCollectionSyncUtilsTest.java @@ -1,197 +1,233 @@ package com.commercetools.sync.internals.utils; +import static com.commercetools.sync.internals.utils.UnorderedCollectionSyncUtils.buildRemoveUpdateActions; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductVariant; import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.products.ProductVariantDraftBuilder; import io.sphere.sdk.products.commands.updateactions.RemoveVariant; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Function; - -import static com.commercetools.sync.internals.utils.UnorderedCollectionSyncUtils.buildRemoveUpdateActions; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class UnorderedCollectionSyncUtilsTest { - @Test - void buildRemoveUpdateActions_withNullNewDraftsAndEmptyOldCollection_ShouldNotBuildActions() { - // preparation - final List oldVariants = new ArrayList<>(); - final List newDrafts = null; - - // test - final List> removeUpdateActions = buildRemoveUpdateActions(oldVariants, newDrafts, - ProductVariant::getKey, ProductVariantDraft::getKey, + @Test + void buildRemoveUpdateActions_withNullNewDraftsAndEmptyOldCollection_ShouldNotBuildActions() { + // preparation + final List oldVariants = new ArrayList<>(); + final List newDrafts = null; + + // test + final List> removeUpdateActions = + buildRemoveUpdateActions( + oldVariants, + newDrafts, + ProductVariant::getKey, + ProductVariantDraft::getKey, p -> RemoveVariant.ofVariantId(p.getId(), true)); - // assertion - assertThat(removeUpdateActions).isEmpty(); - } - - @Test - void buildRemoveUpdateActions_withNullNewDrafts_ShouldBuildRemoveActionsForEveryDraft() { - // preparation - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getId()).thenReturn(1); - when(productVariant.getKey()).thenReturn("1"); - final List oldVariants = singletonList(productVariant); - final List newDrafts = null; - - // test - final List> removeUpdateActions = buildRemoveUpdateActions(oldVariants, newDrafts, - ProductVariant::getKey, ProductVariantDraft::getKey, + // assertion + assertThat(removeUpdateActions).isEmpty(); + } + + @Test + void buildRemoveUpdateActions_withNullNewDrafts_ShouldBuildRemoveActionsForEveryDraft() { + // preparation + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getId()).thenReturn(1); + when(productVariant.getKey()).thenReturn("1"); + final List oldVariants = singletonList(productVariant); + final List newDrafts = null; + + // test + final List> removeUpdateActions = + buildRemoveUpdateActions( + oldVariants, + newDrafts, + ProductVariant::getKey, + ProductVariantDraft::getKey, p -> RemoveVariant.ofVariantId(p.getId(), true)); - // assertion - assertThat(removeUpdateActions) - .containsExactly(RemoveVariant.ofVariantId(productVariant.getId(), true)); - } - - @Test - void buildRemoveUpdateActions_withEmptyNewDrafts_ShouldBuildRemoveActionsForEveryDraft() { - // preparation - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getId()).thenReturn(1); - when(productVariant.getKey()).thenReturn("1"); - final List oldVariants = singletonList(productVariant); - final List newDrafts = new ArrayList<>(); - - // test - final List> removeUpdateActions = buildRemoveUpdateActions(oldVariants, - newDrafts, ProductVariant::getKey, ProductVariantDraft::getKey, + // assertion + assertThat(removeUpdateActions) + .containsExactly(RemoveVariant.ofVariantId(productVariant.getId(), true)); + } + + @Test + void buildRemoveUpdateActions_withEmptyNewDrafts_ShouldBuildRemoveActionsForEveryDraft() { + // preparation + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getId()).thenReturn(1); + when(productVariant.getKey()).thenReturn("1"); + final List oldVariants = singletonList(productVariant); + final List newDrafts = new ArrayList<>(); + + // test + final List> removeUpdateActions = + buildRemoveUpdateActions( + oldVariants, + newDrafts, + ProductVariant::getKey, + ProductVariantDraft::getKey, p -> RemoveVariant.ofVariantId(p.getId(), true)); - // assertion - assertThat(removeUpdateActions) - .containsExactly(RemoveVariant.ofVariantId(productVariant.getId(), true)); - } - - @Test - void buildRemoveUpdateActions_withSomeMatchingDrafts_ShouldBuildRemoveActionsForEveryMissingDraft() { - // preparation - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getId()).thenReturn(1); - when(productVariant.getKey()).thenReturn("1"); - - final ProductVariant productVariant2 = mock(ProductVariant.class); - when(productVariant2.getId()).thenReturn(2); - when(productVariant2.getKey()).thenReturn("2"); - - final List oldVariants = asList(productVariant, productVariant2); - final List newDrafts = - singletonList(ProductVariantDraftBuilder.of(productVariant).build()); - - // test - final List> removeUpdateActions = buildRemoveUpdateActions(oldVariants, - newDrafts, ProductVariant::getKey, ProductVariantDraft::getKey, + // assertion + assertThat(removeUpdateActions) + .containsExactly(RemoveVariant.ofVariantId(productVariant.getId(), true)); + } + + @Test + void + buildRemoveUpdateActions_withSomeMatchingDrafts_ShouldBuildRemoveActionsForEveryMissingDraft() { + // preparation + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getId()).thenReturn(1); + when(productVariant.getKey()).thenReturn("1"); + + final ProductVariant productVariant2 = mock(ProductVariant.class); + when(productVariant2.getId()).thenReturn(2); + when(productVariant2.getKey()).thenReturn("2"); + + final List oldVariants = asList(productVariant, productVariant2); + final List newDrafts = + singletonList(ProductVariantDraftBuilder.of(productVariant).build()); + + // test + final List> removeUpdateActions = + buildRemoveUpdateActions( + oldVariants, + newDrafts, + ProductVariant::getKey, + ProductVariantDraft::getKey, p -> RemoveVariant.ofVariantId(p.getId(), true)); - // assertion - assertThat(removeUpdateActions) - .containsExactly(RemoveVariant.ofVariantId(productVariant2.getId(), true)); - } - - @Test - void buildRemoveUpdateActions_withDraftsWithNullKeys_ShouldNotBuildRemoveActionsForDraftsWithNullKeys() { - // preparation - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getId()).thenReturn(1); - - final ProductVariant productVariant2 = mock(ProductVariant.class); - when(productVariant2.getId()).thenReturn(2); - - final List oldVariants = asList(productVariant, productVariant2); - final List newDrafts = - singletonList(ProductVariantDraftBuilder.of(productVariant).build()); - - // test - final List> removeUpdateActions = buildRemoveUpdateActions(oldVariants, - newDrafts, ProductVariant::getKey, ProductVariantDraft::getKey, + // assertion + assertThat(removeUpdateActions) + .containsExactly(RemoveVariant.ofVariantId(productVariant2.getId(), true)); + } + + @Test + void + buildRemoveUpdateActions_withDraftsWithNullKeys_ShouldNotBuildRemoveActionsForDraftsWithNullKeys() { + // preparation + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getId()).thenReturn(1); + + final ProductVariant productVariant2 = mock(ProductVariant.class); + when(productVariant2.getId()).thenReturn(2); + + final List oldVariants = asList(productVariant, productVariant2); + final List newDrafts = + singletonList(ProductVariantDraftBuilder.of(productVariant).build()); + + // test + final List> removeUpdateActions = + buildRemoveUpdateActions( + oldVariants, + newDrafts, + ProductVariant::getKey, + ProductVariantDraft::getKey, p -> RemoveVariant.ofVariantId(p.getId(), true)); - // assertion - assertThat(removeUpdateActions).isEmpty(); - } - - @Test - void buildRemoveUpdateActions_withNullDrafts_ShouldNotBuildRemoveActions() { - // preparation - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getId()).thenReturn(1); - - final ProductVariant productVariant2 = mock(ProductVariant.class); - when(productVariant2.getId()).thenReturn(2); - - final List oldVariants = asList(productVariant, productVariant2); - final List newDrafts = singletonList(null); - - // test - final List> removeUpdateActions = buildRemoveUpdateActions(oldVariants, - newDrafts, ProductVariant::getKey, ProductVariantDraft::getKey, + // assertion + assertThat(removeUpdateActions).isEmpty(); + } + + @Test + void buildRemoveUpdateActions_withNullDrafts_ShouldNotBuildRemoveActions() { + // preparation + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getId()).thenReturn(1); + + final ProductVariant productVariant2 = mock(ProductVariant.class); + when(productVariant2.getId()).thenReturn(2); + + final List oldVariants = asList(productVariant, productVariant2); + final List newDrafts = singletonList(null); + + // test + final List> removeUpdateActions = + buildRemoveUpdateActions( + oldVariants, + newDrafts, + ProductVariant::getKey, + ProductVariantDraft::getKey, p -> RemoveVariant.ofVariantId(p.getId(), true)); - // assertion - assertThat(removeUpdateActions).isEmpty(); - } - - @Test - void buildRemoveUpdateActions_withNullReturningActionMapper_ShouldBuildActionsWithNoNullElement() { - // preparation - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getId()).thenReturn(1); - - final ProductVariant productVariant2 = mock(ProductVariant.class); - when(productVariant2.getId()).thenReturn(2); - when(productVariant2.getKey()).thenReturn("2"); - - final ProductVariant productVariant3 = mock(ProductVariant.class); - when(productVariant2.getId()).thenReturn(3); - when(productVariant2.getKey()).thenReturn("3"); - - final List oldVariants = asList(productVariant, productVariant2, productVariant3); - final List newDrafts = - singletonList(ProductVariantDraftBuilder.of(productVariant).build()); - - // test - final Function> onlyRemoveV2Mapper = variant -> { - if (Objects.equals(variant.getKey(), productVariant2.getKey())) { - return RemoveVariant.ofVariantId(productVariant2.getId(), true); - } - return null; + // assertion + assertThat(removeUpdateActions).isEmpty(); + } + + @Test + void + buildRemoveUpdateActions_withNullReturningActionMapper_ShouldBuildActionsWithNoNullElement() { + // preparation + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getId()).thenReturn(1); + + final ProductVariant productVariant2 = mock(ProductVariant.class); + when(productVariant2.getId()).thenReturn(2); + when(productVariant2.getKey()).thenReturn("2"); + + final ProductVariant productVariant3 = mock(ProductVariant.class); + when(productVariant2.getId()).thenReturn(3); + when(productVariant2.getKey()).thenReturn("3"); + + final List oldVariants = + asList(productVariant, productVariant2, productVariant3); + final List newDrafts = + singletonList(ProductVariantDraftBuilder.of(productVariant).build()); + + // test + final Function> onlyRemoveV2Mapper = + variant -> { + if (Objects.equals(variant.getKey(), productVariant2.getKey())) { + return RemoveVariant.ofVariantId(productVariant2.getId(), true); + } + return null; }; - final List> removeUpdateActions = buildRemoveUpdateActions(oldVariants, - newDrafts, ProductVariant::getKey, ProductVariantDraft::getKey, onlyRemoveV2Mapper); - - // assertion - assertThat(removeUpdateActions).doesNotContainNull(); - } - - @Test - void buildRemoveUpdateActions_withIdenticalNewDraftsAndOldMap_ShouldNotBuildRemoveActions() { - // preparation - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getId()).thenReturn(1); - when(productVariant.getKey()).thenReturn("1"); - final List oldVariants = singletonList(productVariant); - final List newDrafts = - singletonList(ProductVariantDraftBuilder.of(productVariant).build()); - - // test - final List> removeUpdateActions = buildRemoveUpdateActions(oldVariants, - newDrafts, ProductVariant::getKey, ProductVariantDraft::getKey, + final List> removeUpdateActions = + buildRemoveUpdateActions( + oldVariants, + newDrafts, + ProductVariant::getKey, + ProductVariantDraft::getKey, + onlyRemoveV2Mapper); + + // assertion + assertThat(removeUpdateActions).doesNotContainNull(); + } + + @Test + void buildRemoveUpdateActions_withIdenticalNewDraftsAndOldMap_ShouldNotBuildRemoveActions() { + // preparation + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getId()).thenReturn(1); + when(productVariant.getKey()).thenReturn("1"); + final List oldVariants = singletonList(productVariant); + final List newDrafts = + singletonList(ProductVariantDraftBuilder.of(productVariant).build()); + + // test + final List> removeUpdateActions = + buildRemoveUpdateActions( + oldVariants, + newDrafts, + ProductVariant::getKey, + ProductVariantDraft::getKey, p -> RemoveVariant.ofVariantId(p.getId(), true)); - // assertion - assertThat(removeUpdateActions).isEmpty(); - } - + // assertion + assertThat(removeUpdateActions).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/inventories/InventorySyncMockUtils.java b/src/test/java/com/commercetools/sync/inventories/InventorySyncMockUtils.java index 952a00cd10..416c5c36be 100644 --- a/src/test/java/com/commercetools/sync/inventories/InventorySyncMockUtils.java +++ b/src/test/java/com/commercetools/sync/inventories/InventorySyncMockUtils.java @@ -1,5 +1,12 @@ package com.commercetools.sync.inventories; +import static java.util.Collections.singleton; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.services.ChannelService; import com.commercetools.sync.services.InventoryService; import io.sphere.sdk.channels.Channel; @@ -8,122 +15,118 @@ import io.sphere.sdk.inventory.InventoryEntryDraft; import io.sphere.sdk.models.Reference; import io.sphere.sdk.types.CustomFields; - -import javax.annotation.Nonnull; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static java.util.Collections.singleton; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; public class InventorySyncMockUtils { - /** - * Returns mock {@link Channel} instance. Returned instance represents channel of passed {@code id}, {@code key} - * and of role {@link ChannelRole#INVENTORY_SUPPLY}. - * - * @param id result of calling {@link Channel#getId()} - * @param key result of calling {@link Channel#getKey()} - * @return mock instance of {@link Channel} - */ - public static Channel getMockSupplyChannel(final String id, final String key) { - final Channel channel = mock(Channel.class); - when(channel.getId()).thenReturn(id); - when(channel.getKey()).thenReturn(key); - when(channel.getRoles()).thenReturn(singleton(ChannelRole.INVENTORY_SUPPLY)); - when(channel.toReference()).thenReturn(Channel.referenceOfId(id)); - return channel; - } - - /** - * Returns mock {@link InventoryEntry} instance. Executing getters on returned instance will return values passed - * in parameters. - * - * @param sku result of calling {@link InventoryEntry#getSku()} - * @param quantityOnStock result of calling {@link InventoryEntry#getQuantityOnStock()} - * @param restockableInDays result of calling {@link InventoryEntry#getRestockableInDays()} - * @param expectedDelivery result of calling {@link InventoryEntry#getExpectedDelivery()} - * @param supplyChannel result of calling {@link InventoryEntry#getSupplyChannel()} - * @param customFields result of calling {@link InventoryEntry#getCustom()} - * @return mock instance of {@link InventoryEntry} - */ - public static InventoryEntry getMockInventoryEntry(final String sku, - final Long quantityOnStock, - final Integer restockableInDays, - final ZonedDateTime expectedDelivery, - final Reference supplyChannel, - final CustomFields customFields) { - final InventoryEntry inventoryEntry = mock(InventoryEntry.class); - when(inventoryEntry.getSku()).thenReturn(sku); - when(inventoryEntry.getQuantityOnStock()).thenReturn(quantityOnStock); - when(inventoryEntry.getRestockableInDays()).thenReturn(restockableInDays); - when(inventoryEntry.getExpectedDelivery()).thenReturn(expectedDelivery); - when(inventoryEntry.getSupplyChannel()).thenReturn(supplyChannel); - when(inventoryEntry.getCustom()).thenReturn(customFields); - return inventoryEntry; - } - + /** + * Returns mock {@link Channel} instance. Returned instance represents channel of passed {@code + * id}, {@code key} and of role {@link ChannelRole#INVENTORY_SUPPLY}. + * + * @param id result of calling {@link Channel#getId()} + * @param key result of calling {@link Channel#getKey()} + * @return mock instance of {@link Channel} + */ + public static Channel getMockSupplyChannel(final String id, final String key) { + final Channel channel = mock(Channel.class); + when(channel.getId()).thenReturn(id); + when(channel.getKey()).thenReturn(key); + when(channel.getRoles()).thenReturn(singleton(ChannelRole.INVENTORY_SUPPLY)); + when(channel.toReference()).thenReturn(Channel.referenceOfId(id)); + return channel; + } + /** + * Returns mock {@link InventoryEntry} instance. Executing getters on returned instance will + * return values passed in parameters. + * + * @param sku result of calling {@link InventoryEntry#getSku()} + * @param quantityOnStock result of calling {@link InventoryEntry#getQuantityOnStock()} + * @param restockableInDays result of calling {@link InventoryEntry#getRestockableInDays()} + * @param expectedDelivery result of calling {@link InventoryEntry#getExpectedDelivery()} + * @param supplyChannel result of calling {@link InventoryEntry#getSupplyChannel()} + * @param customFields result of calling {@link InventoryEntry#getCustom()} + * @return mock instance of {@link InventoryEntry} + */ + public static InventoryEntry getMockInventoryEntry( + final String sku, + final Long quantityOnStock, + final Integer restockableInDays, + final ZonedDateTime expectedDelivery, + final Reference supplyChannel, + final CustomFields customFields) { + final InventoryEntry inventoryEntry = mock(InventoryEntry.class); + when(inventoryEntry.getSku()).thenReturn(sku); + when(inventoryEntry.getQuantityOnStock()).thenReturn(quantityOnStock); + when(inventoryEntry.getRestockableInDays()).thenReturn(restockableInDays); + when(inventoryEntry.getExpectedDelivery()).thenReturn(expectedDelivery); + when(inventoryEntry.getSupplyChannel()).thenReturn(supplyChannel); + when(inventoryEntry.getCustom()).thenReturn(customFields); + return inventoryEntry; + } - /** - * Returns mock instance of {@link InventoryService}. Executing any method with any parameter on this instance - * returns values passed in parameters, wrapped in {@link CompletionStage}. - * - * @param inventoryEntries result of calling {@link InventoryService#fetchInventoryEntriesBySkus(Set)} - * @param createdInventoryEntry result of calling {@link InventoryService#createInventoryEntry(InventoryEntryDraft)} - * @param updatedInventoryEntry result of calling - * {@link InventoryService#updateInventoryEntry(InventoryEntry, List)} - * @return mock instance of {@link InventoryService} - */ - static InventoryService getMockInventoryService(final Set inventoryEntries, - final InventoryEntry createdInventoryEntry, - final InventoryEntry updatedInventoryEntry) { - final InventoryService inventoryService = mock(InventoryService.class); - when(inventoryService.fetchInventoryEntriesBySkus(any())).thenReturn(completedFuture(inventoryEntries)); - when(inventoryService.createInventoryEntry(any())) - .thenReturn(completedFuture(Optional.ofNullable(createdInventoryEntry))); - when(inventoryService.updateInventoryEntry(any(), any())).thenReturn(completedFuture(updatedInventoryEntry)); - return inventoryService; - } + /** + * Returns mock instance of {@link InventoryService}. Executing any method with any parameter on + * this instance returns values passed in parameters, wrapped in {@link CompletionStage}. + * + * @param inventoryEntries result of calling {@link + * InventoryService#fetchInventoryEntriesBySkus(Set)} + * @param createdInventoryEntry result of calling {@link + * InventoryService#createInventoryEntry(InventoryEntryDraft)} + * @param updatedInventoryEntry result of calling {@link + * InventoryService#updateInventoryEntry(InventoryEntry, List)} + * @return mock instance of {@link InventoryService} + */ + static InventoryService getMockInventoryService( + final Set inventoryEntries, + final InventoryEntry createdInventoryEntry, + final InventoryEntry updatedInventoryEntry) { + final InventoryService inventoryService = mock(InventoryService.class); + when(inventoryService.fetchInventoryEntriesBySkus(any())) + .thenReturn(completedFuture(inventoryEntries)); + when(inventoryService.createInventoryEntry(any())) + .thenReturn(completedFuture(Optional.ofNullable(createdInventoryEntry))); + when(inventoryService.updateInventoryEntry(any(), any())) + .thenReturn(completedFuture(updatedInventoryEntry)); + return inventoryService; + } - /** - * Returns mock instance of {@link InventoryService} with the specified parameters as mock results of calling the - * {@link ChannelService#createAndCacheChannel(String)} and - * {@link ChannelService#fetchCachedChannelId(String)} (String)} of the mock channel service. - * - * @param createdSupplyChannel result of future resulting from calling - * {@link ChannelService#createAndCacheChannel(String)} - * - * @return mock instance of {@link InventoryService}. - */ - public static ChannelService getMockChannelService(@Nonnull final Channel createdSupplyChannel) { - final String createdSupplyChannelId = createdSupplyChannel.getId(); + /** + * Returns mock instance of {@link InventoryService} with the specified parameters as mock results + * of calling the {@link ChannelService#createAndCacheChannel(String)} and {@link + * ChannelService#fetchCachedChannelId(String)} (String)} of the mock channel service. + * + * @param createdSupplyChannel result of future resulting from calling {@link + * ChannelService#createAndCacheChannel(String)} + * @return mock instance of {@link InventoryService}. + */ + public static ChannelService getMockChannelService(@Nonnull final Channel createdSupplyChannel) { + final String createdSupplyChannelId = createdSupplyChannel.getId(); - final ChannelService channelService = mock(ChannelService.class); - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(completedFuture(Optional.of(createdSupplyChannelId))); - when(channelService.createAndCacheChannel(any())) - .thenReturn(completedFuture(Optional.of(createdSupplyChannel))); - return channelService; - } + final ChannelService channelService = mock(ChannelService.class); + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(completedFuture(Optional.of(createdSupplyChannelId))); + when(channelService.createAndCacheChannel(any())) + .thenReturn(completedFuture(Optional.of(createdSupplyChannel))); + return channelService; + } - /** - * Returns {@link CompletionStage} completed exceptionally. - * - * @param type of result that is supposed to be inside {@link CompletionStage} - * @return {@link CompletionStage} instance that is completed exceptionally with {@link RuntimeException} - */ - static CompletionStage getCompletionStageWithException() { - final CompletableFuture exceptionalStage = new CompletableFuture<>(); - exceptionalStage.completeExceptionally(new RuntimeException()); - return exceptionalStage; - } + /** + * Returns {@link CompletionStage} completed exceptionally. + * + * @param type of result that is supposed to be inside {@link CompletionStage} + * @return {@link CompletionStage} instance that is completed exceptionally with {@link + * RuntimeException} + */ + static CompletionStage getCompletionStageWithException() { + final CompletableFuture exceptionalStage = new CompletableFuture<>(); + exceptionalStage.completeExceptionally(new RuntimeException()); + return exceptionalStage; + } } diff --git a/src/test/java/com/commercetools/sync/inventories/InventorySyncOptionsTest.java b/src/test/java/com/commercetools/sync/inventories/InventorySyncOptionsTest.java index 1cf0438648..4ec81ead6a 100644 --- a/src/test/java/com/commercetools/sync/inventories/InventorySyncOptionsTest.java +++ b/src/test/java/com/commercetools/sync/inventories/InventorySyncOptionsTest.java @@ -1,38 +1,44 @@ package com.commercetools.sync.inventories; -import io.sphere.sdk.client.SphereClient; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import io.sphere.sdk.client.SphereClient; +import org.junit.jupiter.api.Test; + class InventorySyncOptionsTest { - @Test - void build_WithOnlyRequiredFieldsSet_ShouldReturnProperOptionsInstance() { - final InventorySyncOptions options = InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); - assertThat(options).isNotNull(); - assertThat(options.getBatchSize()).isEqualTo(InventorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(options.shouldEnsureChannels()).isEqualTo(InventorySyncOptionsBuilder.ENSURE_CHANNELS_DEFAULT); - } + @Test + void build_WithOnlyRequiredFieldsSet_ShouldReturnProperOptionsInstance() { + final InventorySyncOptions options = + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); + assertThat(options).isNotNull(); + assertThat(options.getBatchSize()).isEqualTo(InventorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); + assertThat(options.shouldEnsureChannels()) + .isEqualTo(InventorySyncOptionsBuilder.ENSURE_CHANNELS_DEFAULT); + } - @Test - void build_WithAllFieldsSet_ShouldReturnProperOptionsInstance() { - final InventorySyncOptions options = InventorySyncOptionsBuilder.of(mock(SphereClient.class)) - .batchSize(10) - .ensureChannels(true) - .build(); - assertThat(options).isNotNull(); - assertThat(options.getBatchSize()).isEqualTo(10); - assertThat(options.shouldEnsureChannels()).isTrue(); - } + @Test + void build_WithAllFieldsSet_ShouldReturnProperOptionsInstance() { + final InventorySyncOptions options = + InventorySyncOptionsBuilder.of(mock(SphereClient.class)) + .batchSize(10) + .ensureChannels(true) + .build(); + assertThat(options).isNotNull(); + assertThat(options.getBatchSize()).isEqualTo(10); + assertThat(options.shouldEnsureChannels()).isTrue(); + } - @Test - void setBatchSize_WithZeroOrNegativePassed_ShouldNotSetBatchSize() { - final InventorySyncOptionsBuilder builder = InventorySyncOptionsBuilder.of(mock(SphereClient.class)); - final InventorySyncOptions optionsWithZero = builder.batchSize(0).build(); - final InventorySyncOptions optionsWithNegative = builder.batchSize(-1).build(); - assertThat(optionsWithZero.getBatchSize()).isEqualTo(InventorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(optionsWithNegative.getBatchSize()).isEqualTo(InventorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } + @Test + void setBatchSize_WithZeroOrNegativePassed_ShouldNotSetBatchSize() { + final InventorySyncOptionsBuilder builder = + InventorySyncOptionsBuilder.of(mock(SphereClient.class)); + final InventorySyncOptions optionsWithZero = builder.batchSize(0).build(); + final InventorySyncOptions optionsWithNegative = builder.batchSize(-1).build(); + assertThat(optionsWithZero.getBatchSize()) + .isEqualTo(InventorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); + assertThat(optionsWithNegative.getBatchSize()) + .isEqualTo(InventorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } } diff --git a/src/test/java/com/commercetools/sync/inventories/InventorySyncTest.java b/src/test/java/com/commercetools/sync/inventories/InventorySyncTest.java index 2b8b7266f2..4e1baa0745 100644 --- a/src/test/java/com/commercetools/sync/inventories/InventorySyncTest.java +++ b/src/test/java/com/commercetools/sync/inventories/InventorySyncTest.java @@ -1,5 +1,31 @@ package com.commercetools.sync.inventories; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getCompletionStageWithException; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockChannelService; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockInventoryEntry; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockInventoryService; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.inventories.helpers.InventorySyncStatistics; @@ -16,9 +42,6 @@ import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.models.SphereException; import io.sphere.sdk.types.CustomFieldsDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -29,529 +52,539 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getCompletionStageWithException; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockChannelService; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockInventoryEntry; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockInventoryService; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.CompletableFuture.supplyAsync; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class InventorySyncTest { - private static final String SKU_1 = "1000"; - private static final String SKU_2 = "2000"; - private static final String SKU_3 = "3000"; - - private static final String KEY_1 = "channel-key_1"; - private static final String KEY_2 = "channel-key_2"; - private static final String KEY_3 = "channel-key_3"; - - private static final String REF_1 = "111"; - private static final String REF_2 = "222"; - private static final String REF_3 = "333"; - - private static final Long QUANTITY_1 = 10L; - private static final Long QUANTITY_2 = 30L; - - private static final Integer RESTOCKABLE_1 = 10; - private static final Integer RESTOCKABLE_2 = 10; - - private static final ZonedDateTime DATE_1 = ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); - private static final ZonedDateTime DATE_2 = ZonedDateTime.of(2017, 5, 1, 20, 0, 0, 0, ZoneId.of("UTC")); - - private List drafts; - private Set existingInventories; - private List errorCallBackMessages; - private List errorCallBackExceptions; - - /** - * Initialises test data. - */ - @BeforeEach - void setup() { - final Reference reference1 = Channel.referenceOfId(REF_1); - final Reference reference2 = Channel.referenceOfId(REF_2); - - existingInventories = asSet( - getMockInventoryEntry(SKU_1, QUANTITY_1, RESTOCKABLE_1, DATE_1, null, null), - getMockInventoryEntry(SKU_1, QUANTITY_1, RESTOCKABLE_1, DATE_1, reference2, null), - getMockInventoryEntry(SKU_2, QUANTITY_1, RESTOCKABLE_1, DATE_1, null, null), - getMockInventoryEntry(SKU_2, QUANTITY_1, RESTOCKABLE_1, DATE_1, reference1, null), - getMockInventoryEntry(SKU_2, QUANTITY_1, RESTOCKABLE_1, DATE_1, reference2, null) - ); - - drafts = asList( - InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, null) - .build(), - InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_2)) - .build(), - InventoryEntryDraftBuilder - .of(SKU_2, QUANTITY_2, DATE_2, RESTOCKABLE_2, null) - .build(), - InventoryEntryDraftBuilder - .of(SKU_2, QUANTITY_2, DATE_2, RESTOCKABLE_2, ResourceIdentifier.ofKey(KEY_1)) + private static final String SKU_1 = "1000"; + private static final String SKU_2 = "2000"; + private static final String SKU_3 = "3000"; + + private static final String KEY_1 = "channel-key_1"; + private static final String KEY_2 = "channel-key_2"; + private static final String KEY_3 = "channel-key_3"; + + private static final String REF_1 = "111"; + private static final String REF_2 = "222"; + private static final String REF_3 = "333"; + + private static final Long QUANTITY_1 = 10L; + private static final Long QUANTITY_2 = 30L; + + private static final Integer RESTOCKABLE_1 = 10; + private static final Integer RESTOCKABLE_2 = 10; + + private static final ZonedDateTime DATE_1 = + ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); + private static final ZonedDateTime DATE_2 = + ZonedDateTime.of(2017, 5, 1, 20, 0, 0, 0, ZoneId.of("UTC")); + + private List drafts; + private Set existingInventories; + private List errorCallBackMessages; + private List errorCallBackExceptions; + + /** Initialises test data. */ + @BeforeEach + void setup() { + final Reference reference1 = Channel.referenceOfId(REF_1); + final Reference reference2 = Channel.referenceOfId(REF_2); + + existingInventories = + asSet( + getMockInventoryEntry(SKU_1, QUANTITY_1, RESTOCKABLE_1, DATE_1, null, null), + getMockInventoryEntry(SKU_1, QUANTITY_1, RESTOCKABLE_1, DATE_1, reference2, null), + getMockInventoryEntry(SKU_2, QUANTITY_1, RESTOCKABLE_1, DATE_1, null, null), + getMockInventoryEntry(SKU_2, QUANTITY_1, RESTOCKABLE_1, DATE_1, reference1, null), + getMockInventoryEntry(SKU_2, QUANTITY_1, RESTOCKABLE_1, DATE_1, reference2, null)); + + drafts = + asList( + InventoryEntryDraftBuilder.of(SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, null).build(), + InventoryEntryDraftBuilder.of( + SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_2)) .build(), - InventoryEntryDraftBuilder - .of(SKU_2, QUANTITY_2, DATE_2, RESTOCKABLE_2, ResourceIdentifier.ofKey(KEY_2)) + InventoryEntryDraftBuilder.of(SKU_2, QUANTITY_2, DATE_2, RESTOCKABLE_2, null).build(), + InventoryEntryDraftBuilder.of( + SKU_2, QUANTITY_2, DATE_2, RESTOCKABLE_2, ResourceIdentifier.ofKey(KEY_1)) .build(), - InventoryEntryDraftBuilder - .of(SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, null) + InventoryEntryDraftBuilder.of( + SKU_2, QUANTITY_2, DATE_2, RESTOCKABLE_2, ResourceIdentifier.ofKey(KEY_2)) .build(), - InventoryEntryDraftBuilder - .of(SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_2)) - .build() - ); - - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - } - - @Test - void getStatistics_ShouldReturnProperStatistics() { - // preparation - final InventorySync inventorySync = getInventorySync(30, false); - inventorySync - .sync(drafts) - .toCompletableFuture() - .join(); - - // test - final InventorySyncStatistics stats = inventorySync.getStatistics(); - - // assertion - assertThat(stats).hasValues(7, 2, 3, 0); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - } - - @Test - void sync_WithEmptyList_ShouldNotSync() { - final InventorySync inventorySync = getInventorySync(30, false); - final InventorySyncStatistics stats = inventorySync.sync(emptyList()) - .toCompletableFuture() - .join(); - - assertThat(stats).hasValues(0, 0, 0, 0); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - } - - @Test - void sync_WithEnsuredChannels_ShouldCreateEntriesWithUnknownChannels() { - final InventoryEntryDraft draftWithNewChannel = InventoryEntryDraftBuilder - .of(SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) - .build(); - final InventorySync inventorySync = getInventorySync(30, true); - final InventorySyncStatistics stats = inventorySync.sync(singletonList(draftWithNewChannel)) - .toCompletableFuture() - .join(); - - assertThat(stats).hasValues(1, 1, 0, 0); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - } - - @Test - void sync_WithNoNewCreatedInventory_ShouldIncrementFailedStatic() { - final InventoryEntryDraft draftWithNewChannel = InventoryEntryDraftBuilder - .of(SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) + InventoryEntryDraftBuilder.of(SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, null).build(), + InventoryEntryDraftBuilder.of( + SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_2)) + .build()); + + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + } + + @Test + void getStatistics_ShouldReturnProperStatistics() { + // preparation + final InventorySync inventorySync = getInventorySync(30, false); + inventorySync.sync(drafts).toCompletableFuture().join(); + + // test + final InventorySyncStatistics stats = inventorySync.getStatistics(); + + // assertion + assertThat(stats).hasValues(7, 2, 3, 0); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + } + + @Test + void sync_WithEmptyList_ShouldNotSync() { + final InventorySync inventorySync = getInventorySync(30, false); + final InventorySyncStatistics stats = + inventorySync.sync(emptyList()).toCompletableFuture().join(); + + assertThat(stats).hasValues(0, 0, 0, 0); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + } + + @Test + void sync_WithEnsuredChannels_ShouldCreateEntriesWithUnknownChannels() { + final InventoryEntryDraft draftWithNewChannel = + InventoryEntryDraftBuilder.of( + SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) .build(); - final InventorySyncOptions options = getInventorySyncOptions(30, true); - final InventoryService inventoryService = getMockInventoryService(existingInventories, - null, mock(InventoryEntry.class)); - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_2, KEY_2)); - - InventorySync inventorySync = new InventorySync(options, inventoryService, channelService, - mock(TypeService.class)); - final InventorySyncStatistics stats = inventorySync.sync(singletonList(draftWithNewChannel)) - .toCompletableFuture() - .join(); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(errorCallBackExceptions).hasSize(0); - assertThat(stats).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithNotEnsuredChannels_ShouldNotSyncEntriesWithUnknownChannels() { - final InventoryEntryDraft draftWithNewChannel = InventoryEntryDraftBuilder - .of(SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) + final InventorySync inventorySync = getInventorySync(30, true); + final InventorySyncStatistics stats = + inventorySync.sync(singletonList(draftWithNewChannel)).toCompletableFuture().join(); + + assertThat(stats).hasValues(1, 1, 0, 0); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + } + + @Test + void sync_WithNoNewCreatedInventory_ShouldIncrementFailedStatic() { + final InventoryEntryDraft draftWithNewChannel = + InventoryEntryDraftBuilder.of( + SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) .build(); - - final InventorySyncOptions options = getInventorySyncOptions(30, false); - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - final ChannelService channelService = mock(ChannelService.class); - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(completedFuture(Optional.empty())); - - final InventorySync inventorySync = new InventorySync(options, inventoryService, channelService, - mock(TypeService.class)); - - final InventorySyncStatistics stats = inventorySync.sync(singletonList(draftWithNewChannel)) - .toCompletableFuture() - .join(); - - assertThat(stats).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format("Failed to process the InventoryEntryDraft" - + " with SKU:'%s'. Reason: %s: Failed to resolve supply channel resource identifier on" - + " InventoryEntryDraft with SKU:'%s'. Reason: Channel with key '%s' does not exist.", SKU_3, - ReferenceResolutionException.class.getCanonicalName(), SKU_3, KEY_3)); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); - assertThat(errorCallBackExceptions.get(0).getCause()).isExactlyInstanceOf(ReferenceResolutionException.class); - } - - @Test - void sync_WithDraftsWithNullSku_ShouldNotSync() { - final InventoryEntryDraft draftWithNullSku = InventoryEntryDraft.of(null, 12); - final InventorySync inventorySync = getInventorySync(30, false); - final InventorySyncStatistics stats = inventorySync.sync(singletonList(draftWithNullSku)) - .toCompletableFuture() - .join(); - - assertThat(stats).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo("InventoryEntryDraft doesn't have a SKU." - + " Please make sure all inventory entry drafts have SKUs."); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); - } - - @Test - void sync_WithDraftsWithEmptySku_ShouldNotSync() { - final InventoryEntryDraft draftWithEmptySku = InventoryEntryDraft.of("", 12); - final InventorySync inventorySync = getInventorySync(30, false); - final InventorySyncStatistics stats = inventorySync.sync(singletonList(draftWithEmptySku)) - .toCompletableFuture() - .join(); - - assertThat(stats).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo("InventoryEntryDraft doesn't have a SKU. " - + "Please make sure all inventory entry drafts have SKUs."); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); - } - - @Test - void sync_WithExceptionWhenFetchingExistingInventoriesBatch_ShouldProcessThatBatch() { - final InventorySyncOptions options = getInventorySyncOptions(1, false); - - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); - when(inventoryService.fetchInventoryEntriesBySkus(singleton(SKU_1))) - .thenReturn(getCompletionStageWithException()); - - final InventorySync inventorySync = new InventorySync(options, inventoryService, channelService, - mock(TypeService.class)); - - final InventorySyncStatistics stats = inventorySync.sync(drafts) - .toCompletableFuture() - .join(); - - assertThat(stats).hasValues(7, 4, 1, 2); - assertThat(errorCallBackMessages).hasSize(2); - assertThat(errorCallBackExceptions).hasSize(2); - } - - @Test - void sync_WithExceptionWhenCreatingOrUpdatingEntries_ShouldNotSync() { - final InventorySyncOptions options = getInventorySyncOptions(3, false); - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - when(inventoryService.createInventoryEntry(any())).thenReturn(getCompletionStageWithException()); - when(inventoryService.updateInventoryEntry(any(), any())).thenReturn(getCompletionStageWithException()); - - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_2, KEY_2)); - - final InventorySync inventorySync = new InventorySync(options, inventoryService, channelService, - mock(TypeService.class)); - final InventorySyncStatistics stats = inventorySync.sync(drafts) - .toCompletableFuture() - .join(); - - assertThat(stats).hasValues(7, 0, 0, 5); - assertThat(errorCallBackMessages).hasSize(5); - assertThat(errorCallBackExceptions).hasSize(5); - } - - @Test - void sync_WithExceptionWhenUpdatingEntries_ShouldNotSync() { - final InventorySyncOptions options = getInventorySyncOptions(3, false); - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - when(inventoryService.updateInventoryEntry(any(), any())).thenReturn(getCompletionStageWithException()); - - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_2, KEY_2)); - - final InventorySync inventorySync = new InventorySync(options, inventoryService, channelService, - mock(TypeService.class)); - - final InventoryEntryDraft inventoryEntryDraft = InventoryEntryDraftBuilder - .of(SKU_2, QUANTITY_2, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_2)) + final InventorySyncOptions options = getInventorySyncOptions(30, true); + final InventoryService inventoryService = + getMockInventoryService(existingInventories, null, mock(InventoryEntry.class)); + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_2, KEY_2)); + + InventorySync inventorySync = + new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + final InventorySyncStatistics stats = + inventorySync.sync(singletonList(draftWithNewChannel)).toCompletableFuture().join(); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(errorCallBackExceptions).hasSize(0); + assertThat(stats).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithNotEnsuredChannels_ShouldNotSyncEntriesWithUnknownChannels() { + final InventoryEntryDraft draftWithNewChannel = + InventoryEntryDraftBuilder.of( + SKU_3, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) .build(); - final InventorySyncStatistics stats = inventorySync.sync(Collections.singletonList(inventoryEntryDraft)) - .toCompletableFuture() - .join(); - - assertThat(stats).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackExceptions).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo( - format("Failed to update inventory entry of SKU '%s' and supply channel id '%s'.", SKU_2, REF_2)); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(RuntimeException.class); - } - - @Test - void sync_WithExistingInventoryEntryButWithEmptyCustomTypeReference_ShouldFailSync() { - final InventorySyncOptions options = getInventorySyncOptions(3, false); - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - - final ChannelService channelService = mock(ChannelService.class); - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(completedFuture(Optional.of(REF_2))); - final InventorySync inventorySync = new InventorySync(options, inventoryService, channelService, - mock(TypeService.class)); - - final List newDrafts = new ArrayList<>(); - final InventoryEntryDraft draftWithNullCustomTypeId = - InventoryEntryDraft.of(SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, null) - .withCustom(CustomFieldsDraft.ofTypeKeyAndJson("", new HashMap<>())); - newDrafts.add(draftWithNullCustomTypeId); - - final InventorySyncStatistics syncStatistics = inventorySync.sync(newDrafts).toCompletableFuture().join(); - - assertThat(syncStatistics).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).isNotEmpty(); - assertThat(errorCallBackMessages.get(0)).contains(format("Failed to process the" - + " InventoryEntryDraft with SKU:'%s'. Reason: %s: Failed to resolve custom type resource identifier on " - + "InventoryEntryDraft with SKU:'1000'. Reason: %s", SKU_1, - ReferenceResolutionException.class.getCanonicalName(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - assertThat(errorCallBackExceptions).isNotEmpty(); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); - assertThat(errorCallBackExceptions.get(0).getCause()).isExactlyInstanceOf(ReferenceResolutionException.class); - } - - @Test - void sync_WithNewSupplyChannelAndEnsure_ShouldSync() { - final InventorySyncOptions options = getInventorySyncOptions(3, true); - - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final InventoryEntryDraft newInventoryDraft = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) + final InventorySyncOptions options = getInventorySyncOptions(30, false); + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + final ChannelService channelService = mock(ChannelService.class); + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(completedFuture(Optional.empty())); + + final InventorySync inventorySync = + new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + + final InventorySyncStatistics stats = + inventorySync.sync(singletonList(draftWithNewChannel)).toCompletableFuture().join(); + + assertThat(stats).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo( + format( + "Failed to process the InventoryEntryDraft" + + " with SKU:'%s'. Reason: %s: Failed to resolve supply channel resource identifier on" + + " InventoryEntryDraft with SKU:'%s'. Reason: Channel with key '%s' does not exist.", + SKU_3, ReferenceResolutionException.class.getCanonicalName(), SKU_3, KEY_3)); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); + assertThat(errorCallBackExceptions.get(0).getCause()) + .isExactlyInstanceOf(ReferenceResolutionException.class); + } + + @Test + void sync_WithDraftsWithNullSku_ShouldNotSync() { + final InventoryEntryDraft draftWithNullSku = InventoryEntryDraft.of(null, 12); + final InventorySync inventorySync = getInventorySync(30, false); + final InventorySyncStatistics stats = + inventorySync.sync(singletonList(draftWithNullSku)).toCompletableFuture().join(); + + assertThat(stats).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo( + "InventoryEntryDraft doesn't have a SKU." + + " Please make sure all inventory entry drafts have SKUs."); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); + } + + @Test + void sync_WithDraftsWithEmptySku_ShouldNotSync() { + final InventoryEntryDraft draftWithEmptySku = InventoryEntryDraft.of("", 12); + final InventorySync inventorySync = getInventorySync(30, false); + final InventorySyncStatistics stats = + inventorySync.sync(singletonList(draftWithEmptySku)).toCompletableFuture().join(); + + assertThat(stats).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo( + "InventoryEntryDraft doesn't have a SKU. " + + "Please make sure all inventory entry drafts have SKUs."); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); + } + + @Test + void sync_WithExceptionWhenFetchingExistingInventoriesBatch_ShouldProcessThatBatch() { + final InventorySyncOptions options = getInventorySyncOptions(1, false); + + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); + when(inventoryService.fetchInventoryEntriesBySkus(singleton(SKU_1))) + .thenReturn(getCompletionStageWithException()); + + final InventorySync inventorySync = + new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + + final InventorySyncStatistics stats = inventorySync.sync(drafts).toCompletableFuture().join(); + + assertThat(stats).hasValues(7, 4, 1, 2); + assertThat(errorCallBackMessages).hasSize(2); + assertThat(errorCallBackExceptions).hasSize(2); + } + + @Test + void sync_WithExceptionWhenCreatingOrUpdatingEntries_ShouldNotSync() { + final InventorySyncOptions options = getInventorySyncOptions(3, false); + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + when(inventoryService.createInventoryEntry(any())) + .thenReturn(getCompletionStageWithException()); + when(inventoryService.updateInventoryEntry(any(), any())) + .thenReturn(getCompletionStageWithException()); + + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_2, KEY_2)); + + final InventorySync inventorySync = + new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + final InventorySyncStatistics stats = inventorySync.sync(drafts).toCompletableFuture().join(); + + assertThat(stats).hasValues(7, 0, 0, 5); + assertThat(errorCallBackMessages).hasSize(5); + assertThat(errorCallBackExceptions).hasSize(5); + } + + @Test + void sync_WithExceptionWhenUpdatingEntries_ShouldNotSync() { + final InventorySyncOptions options = getInventorySyncOptions(3, false); + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + when(inventoryService.updateInventoryEntry(any(), any())) + .thenReturn(getCompletionStageWithException()); + + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_2, KEY_2)); + + final InventorySync inventorySync = + new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + + final InventoryEntryDraft inventoryEntryDraft = + InventoryEntryDraftBuilder.of( + SKU_2, QUANTITY_2, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_2)) .build(); - final InventorySync inventorySync = new InventorySync(options, inventoryService, channelService, - mock(TypeService.class)); - - final InventorySyncStatistics stats = inventorySync.sync(singletonList(newInventoryDraft)) - .toCompletableFuture() - .join(); - assertThat(stats).hasValues(1, 1, 0, 0); - assertThat(errorCallBackMessages).isEmpty(); - assertThat(errorCallBackExceptions).isEmpty(); - } - - @Test - void sync_WithExceptionWhenCreatingNewSupplyChannel_ShouldTriggerErrorCallbackAndIncrementFailed() { - final InventorySyncOptions options = getInventorySyncOptions(3, true); - - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - when(channelService.createAndCacheChannel(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final InventoryEntryDraft newInventoryDraft = InventoryEntryDraftBuilder - .of(SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) - .build(); - final InventorySync inventorySync = new InventorySync(options, inventoryService, channelService, - mock(TypeService.class)); - - final InventorySyncStatistics stats = inventorySync.sync(singletonList(newInventoryDraft)) - .toCompletableFuture() - .join(); - assertThat(stats).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).isNotEmpty(); - assertThat(errorCallBackMessages.get(0)).contains(format("Failed to resolve supply channel resource identifier" - + " on InventoryEntryDraft with SKU:'%s'. Reason: Failed to create supply channel with key: '%s'", SKU_1, - "channel-key_3")); - assertThat(errorCallBackExceptions).isNotEmpty(); - assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); - assertThat(errorCallBackExceptions.get(0).getCause()).isExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(errorCallBackExceptions.get(0).getCause().getCause()).isExactlyInstanceOf(CompletionException.class); - } - - @Test - void sync_WithNullInInputList_ShouldIncrementFailedStatistics() { - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); - final InventorySyncOptions options = getInventorySyncOptions(3, false); - - final InventorySync inventorySync = new InventorySync(options, inventoryService, channelService, - mock(TypeService.class)); - - final InventorySyncStatistics stats = inventorySync.sync(singletonList(null)) + final InventorySyncStatistics stats = + inventorySync + .sync(Collections.singletonList(inventoryEntryDraft)) .toCompletableFuture() .join(); - assertThat(stats).hasValues(1, 0, 0, 1); - assertThat(errorCallBackMessages).isNotEmpty(); - assertThat(errorCallBackMessages.get(0)).isEqualTo("InventoryEntryDraft is null."); - assertThat(errorCallBackExceptions).isNotEmpty(); - assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); - } - - @Test - void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { - // preparation - final InventoryEntryDraft inventoryEntryDraft = InventoryEntryDraftBuilder.of(SKU_1, 1L) - .build(); - final InventorySyncOptions optionsSpy = spy(getInventorySyncOptions(1, false)); - - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); - - final InventorySync inventorySync = new InventorySync(optionsSpy, inventoryService, channelService, - mock(TypeService.class)); - - // test - inventorySync.sync(singletonList(inventoryEntryDraft)).toCompletableFuture().join(); - - // assertion - verify(optionsSpy).applyBeforeUpdateCallback(any(), any(), any()); - verify(optionsSpy, never()).applyBeforeCreateCallback(any()); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { - // preparation - final InventoryEntryDraft inventoryEntryDraft = InventoryEntryDraftBuilder.of("newSKU", 1L) - .build(); - final InventorySyncOptions optionsSpy = spy(getInventorySyncOptions(1, false)); - - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); - - final InventorySync inventorySync = new InventorySync(optionsSpy, inventoryService, channelService, - mock(TypeService.class)); - - // test - inventorySync.sync(singletonList(inventoryEntryDraft)) - .toCompletableFuture().join(); - - // assertion - verify(optionsSpy).applyBeforeCreateCallback(any()); - verify(optionsSpy, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - private InventorySync getInventorySync(int batchSize, boolean ensureChannels) { - final InventorySyncOptions options = getInventorySyncOptions(batchSize, ensureChannels); - final InventoryService inventoryService = getMockInventoryService(existingInventories, - mock(InventoryEntry.class), mock(InventoryEntry.class)); - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_2, KEY_2)); - return new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); - } - - private InventorySyncOptions getInventorySyncOptions(int batchSize, boolean ensureChannels) { - return InventorySyncOptionsBuilder.of(mock(SphereClient.class)) - .batchSize(batchSize) - .ensureChannels(ensureChannels) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception.getCause()); - }) - .build(); - } - - @Test - void sync_WithFailOnCachingKeysToIds_ShouldTriggerErrorCallbackAndReturnProperStats() { - // preparation - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final InventorySyncOptions inventorySyncOptions = InventorySyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); + assertThat(stats).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackExceptions).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo( + format( + "Failed to update inventory entry of SKU '%s' and supply channel id '%s'.", + SKU_2, REF_2)); + assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(RuntimeException.class); + } + + @Test + void sync_WithExistingInventoryEntryButWithEmptyCustomTypeReference_ShouldFailSync() { + final InventorySyncOptions options = getInventorySyncOptions(3, false); + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + + final ChannelService channelService = mock(ChannelService.class); + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(completedFuture(Optional.of(REF_2))); + final InventorySync inventorySync = + new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + + final List newDrafts = new ArrayList<>(); + final InventoryEntryDraft draftWithNullCustomTypeId = + InventoryEntryDraft.of(SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, null) + .withCustom(CustomFieldsDraft.ofTypeKeyAndJson("", new HashMap<>())); + newDrafts.add(draftWithNullCustomTypeId); + + final InventorySyncStatistics syncStatistics = + inventorySync.sync(newDrafts).toCompletableFuture().join(); + + assertThat(syncStatistics).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).isNotEmpty(); + assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to process the" + + " InventoryEntryDraft with SKU:'%s'. Reason: %s: Failed to resolve custom type resource identifier on " + + "InventoryEntryDraft with SKU:'1000'. Reason: %s", + SKU_1, + ReferenceResolutionException.class.getCanonicalName(), + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + assertThat(errorCallBackExceptions).isNotEmpty(); + assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); + assertThat(errorCallBackExceptions.get(0).getCause()) + .isExactlyInstanceOf(ReferenceResolutionException.class); + } + + @Test + void sync_WithNewSupplyChannelAndEnsure_ShouldSync() { + final InventorySyncOptions options = getInventorySyncOptions(3, true); + + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final InventoryEntryDraft newInventoryDraft = + InventoryEntryDraftBuilder.of( + SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) + .build(); + final InventorySync inventorySync = + new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + + final InventorySyncStatistics stats = + inventorySync.sync(singletonList(newInventoryDraft)).toCompletableFuture().join(); + assertThat(stats).hasValues(1, 1, 0, 0); + assertThat(errorCallBackMessages).isEmpty(); + assertThat(errorCallBackExceptions).isEmpty(); + } + + @Test + void + sync_WithExceptionWhenCreatingNewSupplyChannel_ShouldTriggerErrorCallbackAndIncrementFailed() { + final InventorySyncOptions options = getInventorySyncOptions(3, true); + + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + when(channelService.createAndCacheChannel(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final InventoryEntryDraft newInventoryDraft = + InventoryEntryDraftBuilder.of( + SKU_1, QUANTITY_1, DATE_1, RESTOCKABLE_1, ResourceIdentifier.ofKey(KEY_3)) + .build(); + final InventorySync inventorySync = + new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + + final InventorySyncStatistics stats = + inventorySync.sync(singletonList(newInventoryDraft)).toCompletableFuture().join(); + + assertThat(stats).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).isNotEmpty(); + assertThat(errorCallBackMessages.get(0)) + .contains( + format( + "Failed to resolve supply channel resource identifier" + + " on InventoryEntryDraft with SKU:'%s'. Reason: Failed to create supply channel with key: '%s'", + SKU_1, "channel-key_3")); + assertThat(errorCallBackExceptions).isNotEmpty(); + assertThat(errorCallBackExceptions.get(0)).isExactlyInstanceOf(CompletionException.class); + assertThat(errorCallBackExceptions.get(0).getCause()) + .isExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(errorCallBackExceptions.get(0).getCause().getCause()) + .isExactlyInstanceOf(CompletionException.class); + } + + @Test + void sync_WithNullInInputList_ShouldIncrementFailedStatistics() { + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); + final InventorySyncOptions options = getInventorySyncOptions(3, false); + + final InventorySync inventorySync = + new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + + final InventorySyncStatistics stats = + inventorySync.sync(singletonList(null)).toCompletableFuture().join(); + + assertThat(stats).hasValues(1, 0, 0, 1); + assertThat(errorCallBackMessages).isNotEmpty(); + assertThat(errorCallBackMessages.get(0)).isEqualTo("InventoryEntryDraft is null."); + assertThat(errorCallBackExceptions).isNotEmpty(); + assertThat(errorCallBackExceptions.get(0)).isEqualTo(null); + } + + @Test + void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { + // preparation + final InventoryEntryDraft inventoryEntryDraft = + InventoryEntryDraftBuilder.of(SKU_1, 1L).build(); + final InventorySyncOptions optionsSpy = spy(getInventorySyncOptions(1, false)); + + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); + + final InventorySync inventorySync = + new InventorySync(optionsSpy, inventoryService, channelService, mock(TypeService.class)); + + // test + inventorySync.sync(singletonList(inventoryEntryDraft)).toCompletableFuture().join(); + + // assertion + verify(optionsSpy).applyBeforeUpdateCallback(any(), any(), any()); + verify(optionsSpy, never()).applyBeforeCreateCallback(any()); + } + + @Test + void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { + // preparation + final InventoryEntryDraft inventoryEntryDraft = + InventoryEntryDraftBuilder.of("newSKU", 1L).build(); + final InventorySyncOptions optionsSpy = spy(getInventorySyncOptions(1, false)); + + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_3, KEY_3)); + + final InventorySync inventorySync = + new InventorySync(optionsSpy, inventoryService, channelService, mock(TypeService.class)); + + // test + inventorySync.sync(singletonList(inventoryEntryDraft)).toCompletableFuture().join(); + + // assertion + verify(optionsSpy).applyBeforeCreateCallback(any()); + verify(optionsSpy, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + private InventorySync getInventorySync(int batchSize, boolean ensureChannels) { + final InventorySyncOptions options = getInventorySyncOptions(batchSize, ensureChannels); + final InventoryService inventoryService = + getMockInventoryService( + existingInventories, mock(InventoryEntry.class), mock(InventoryEntry.class)); + final ChannelService channelService = getMockChannelService(getMockSupplyChannel(REF_2, KEY_2)); + return new InventorySync(options, inventoryService, channelService, mock(TypeService.class)); + } + + private InventorySyncOptions getInventorySyncOptions(int batchSize, boolean ensureChannels) { + return InventorySyncOptionsBuilder.of(mock(SphereClient.class)) + .batchSize(batchSize) + .ensureChannels(ensureChannels) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception.getCause()); }) + .build(); + } + + @Test + void sync_WithFailOnCachingKeysToIds_ShouldTriggerErrorCallbackAndReturnProperStats() { + // preparation + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final InventorySyncOptions inventorySyncOptions = + InventorySyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final TypeService typeService = spy(new TypeServiceImpl(inventorySyncOptions)); - when(typeService.cacheKeysToIds(anySet())) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - - final InventorySync inventorySync = new InventorySync(inventorySyncOptions, - mock(InventoryService.class), mock(ChannelService.class), typeService); - - final InventoryEntryDraft newInventoryDraftWithCustomType = mock(InventoryEntryDraft.class); - when(newInventoryDraftWithCustomType.getSku()).thenReturn("sku"); - when(newInventoryDraftWithCustomType.getCustom()).thenReturn( - CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())); - - //test - final InventorySyncStatistics inventorySyncStatistics = inventorySync + final TypeService typeService = spy(new TypeServiceImpl(inventorySyncOptions)); + when(typeService.cacheKeysToIds(anySet())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final InventorySync inventorySync = + new InventorySync( + inventorySyncOptions, + mock(InventoryService.class), + mock(ChannelService.class), + typeService); + + final InventoryEntryDraft newInventoryDraftWithCustomType = mock(InventoryEntryDraft.class); + when(newInventoryDraftWithCustomType.getSku()).thenReturn("sku"); + when(newInventoryDraftWithCustomType.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())); + + // test + final InventorySyncStatistics inventorySyncStatistics = + inventorySync .sync(singletonList(newInventoryDraftWithCustomType)) .toCompletableFuture() .join(); + // assertions + AssertionsForStatistics.assertThat(inventorySyncStatistics).hasValues(1, 0, 0, 1); - // assertions - AssertionsForStatistics.assertThat(inventorySyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to build a cache of keys to ids.")); + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to build a cache of keys to ids.")); - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); }); - } + } } diff --git a/src/test/java/com/commercetools/sync/inventories/helpers/InventoryBatchValidatorTest.java b/src/test/java/com/commercetools/sync/inventories/helpers/InventoryBatchValidatorTest.java index c40dabd661..cdb0919fc3 100644 --- a/src/test/java/com/commercetools/sync/inventories/helpers/InventoryBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/inventories/helpers/InventoryBatchValidatorTest.java @@ -1,123 +1,127 @@ package com.commercetools.sync.inventories.helpers; +import static com.commercetools.sync.inventories.helpers.InventoryBatchValidator.INVENTORY_DRAFT_IS_NULL; +import static com.commercetools.sync.inventories.helpers.InventoryBatchValidator.INVENTORY_DRAFT_SKU_NOT_SET; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.inventories.InventorySyncOptions; import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.inventory.InventoryEntryDraft; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.types.CustomFieldsDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; - -import static com.commercetools.sync.inventories.helpers.InventoryBatchValidator.INVENTORY_DRAFT_IS_NULL; -import static com.commercetools.sync.inventories.helpers.InventoryBatchValidator.INVENTORY_DRAFT_SKU_NOT_SET; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class InventoryBatchValidatorTest { - private InventorySyncOptions syncOptions; - private InventorySyncStatistics syncStatistics; - private List errorCallBackMessages; + private InventorySyncOptions syncOptions; + private InventorySyncStatistics syncStatistics; + private List errorCallBackMessages; - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = InventorySyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorCallBackMessages.add(exception.getMessage())) + syncOptions = + InventorySyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallBackMessages.add(exception.getMessage())) .build(); - syncStatistics = mock(InventorySyncStatistics.class); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.emptyList()); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullInventoryDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.singletonList(null)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(INVENTORY_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithInventoryDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final InventoryEntryDraft inventoryDraft = mock(InventoryEntryDraft.class); - final Set validDrafts = getValidDrafts(Collections.singletonList(inventoryDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(INVENTORY_DRAFT_SKU_NOT_SET); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithInventoryDraftWithEmptySku_ShouldHaveValidationErrorAndEmptyResult() { - final InventoryEntryDraft inventoryDraft = mock(InventoryEntryDraft.class); - when(inventoryDraft.getSku()).thenReturn(EMPTY); - final Set validDrafts = getValidDrafts(Collections.singletonList(inventoryDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(INVENTORY_DRAFT_SKU_NOT_SET); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { - final InventoryEntryDraft validInventoryDraft = mock(InventoryEntryDraft.class); - when(validInventoryDraft.getSku()).thenReturn("validDraftSku"); - when(validInventoryDraft.getSupplyChannel()).thenReturn(ResourceIdentifier.ofKey("validSupplyChannelKey")); - when(validInventoryDraft.getCustom()) - .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", Collections.emptyMap())); - - final InventoryEntryDraft validMainInventoryDraft = mock(InventoryEntryDraft.class); - when(validMainInventoryDraft.getSku()).thenReturn("validDraftSku1"); - - final InventoryEntryDraft invalidInventoryDraft = mock(InventoryEntryDraft.class); - when(invalidInventoryDraft.getSupplyChannel()).thenReturn(ResourceIdentifier.ofKey("key")); - - final InventoryBatchValidator inventoryBatchValidator = - new InventoryBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, InventoryBatchValidator.ReferencedKeys> pair - = inventoryBatchValidator.validateAndCollectReferencedKeys( + syncStatistics = mock(InventorySyncStatistics.class); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.emptyList()); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullInventoryDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.singletonList(null)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(INVENTORY_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithInventoryDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final InventoryEntryDraft inventoryDraft = mock(InventoryEntryDraft.class); + final Set validDrafts = + getValidDrafts(Collections.singletonList(inventoryDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(INVENTORY_DRAFT_SKU_NOT_SET); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithInventoryDraftWithEmptySku_ShouldHaveValidationErrorAndEmptyResult() { + final InventoryEntryDraft inventoryDraft = mock(InventoryEntryDraft.class); + when(inventoryDraft.getSku()).thenReturn(EMPTY); + final Set validDrafts = + getValidDrafts(Collections.singletonList(inventoryDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(INVENTORY_DRAFT_SKU_NOT_SET); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { + final InventoryEntryDraft validInventoryDraft = mock(InventoryEntryDraft.class); + when(validInventoryDraft.getSku()).thenReturn("validDraftSku"); + when(validInventoryDraft.getSupplyChannel()) + .thenReturn(ResourceIdentifier.ofKey("validSupplyChannelKey")); + when(validInventoryDraft.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", Collections.emptyMap())); + + final InventoryEntryDraft validMainInventoryDraft = mock(InventoryEntryDraft.class); + when(validMainInventoryDraft.getSku()).thenReturn("validDraftSku1"); + + final InventoryEntryDraft invalidInventoryDraft = mock(InventoryEntryDraft.class); + when(invalidInventoryDraft.getSupplyChannel()).thenReturn(ResourceIdentifier.ofKey("key")); + + final InventoryBatchValidator inventoryBatchValidator = + new InventoryBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, InventoryBatchValidator.ReferencedKeys> pair = + inventoryBatchValidator.validateAndCollectReferencedKeys( Arrays.asList(validInventoryDraft, invalidInventoryDraft, validMainInventoryDraft)); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(INVENTORY_DRAFT_SKU_NOT_SET); - assertThat(pair.getLeft()) - .containsExactlyInAnyOrder(validInventoryDraft, validMainInventoryDraft); - assertThat(pair.getRight().getChannelKeys()) - .containsExactlyInAnyOrder("validSupplyChannelKey"); - assertThat(pair.getRight().getTypeKeys()) - .containsExactlyInAnyOrder("typeKey"); - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List inventoryDrafts) { - final InventoryBatchValidator inventoryBatchValidator = - new InventoryBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, InventoryBatchValidator.ReferencedKeys> pair = - inventoryBatchValidator.validateAndCollectReferencedKeys(inventoryDrafts); - return pair.getLeft(); - } + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(INVENTORY_DRAFT_SKU_NOT_SET); + assertThat(pair.getLeft()) + .containsExactlyInAnyOrder(validInventoryDraft, validMainInventoryDraft); + assertThat(pair.getRight().getChannelKeys()).containsExactlyInAnyOrder("validSupplyChannelKey"); + assertThat(pair.getRight().getTypeKeys()).containsExactlyInAnyOrder("typeKey"); + } + + @Nonnull + private Set getValidDrafts( + @Nonnull final List inventoryDrafts) { + final InventoryBatchValidator inventoryBatchValidator = + new InventoryBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, InventoryBatchValidator.ReferencedKeys> pair = + inventoryBatchValidator.validateAndCollectReferencedKeys(inventoryDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/inventories/helpers/InventoryEntryIdentifierTest.java b/src/test/java/com/commercetools/sync/inventories/helpers/InventoryEntryIdentifierTest.java index adb8c0eeb2..df15a2996c 100644 --- a/src/test/java/com/commercetools/sync/inventories/helpers/InventoryEntryIdentifierTest.java +++ b/src/test/java/com/commercetools/sync/inventories/helpers/InventoryEntryIdentifierTest.java @@ -1,181 +1,193 @@ package com.commercetools.sync.inventories.helpers; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockInventoryEntry; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + import io.sphere.sdk.channels.Channel; import io.sphere.sdk.inventory.InventoryEntry; import io.sphere.sdk.inventory.InventoryEntryDraft; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.Map; - -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockInventoryEntry; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class InventoryEntryIdentifierTest { - private static final String SKU = "123"; - private static final String SKU_2 = "321"; - private static final String CHANNEL_ID = "channel-id"; - private static final String CHANNEL_ID_2 = "channel-id-2"; - - @Test - void of_WithDraftWithoutSupplyChannel_ShouldBuildInventoryEntryIdentifier() { - final InventoryEntryDraft draft = InventoryEntryDraft.of(SKU, 1L); - final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(draft); - assertThat(inventoryEntryIdentifier).isNotNull(); - assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); - assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isNull(); - } - - @Test - void of_WithDraftWithSupplyChannel_ShouldBuildInventoryEntryIdentifier() { - final InventoryEntryDraft draft = InventoryEntryDraft - .of(SKU, 1L, null, null, Channel.referenceOfId(CHANNEL_ID)); - final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(draft); - assertThat(inventoryEntryIdentifier).isNotNull(); - assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); - assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isEqualTo(CHANNEL_ID); - } - - @Test - void of_WithEntryWithoutSupplyChannel_ShouldBuildInventoryEntryIdentifier() { - final InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, null, null); - final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - assertThat(inventoryEntryIdentifier).isNotNull(); - assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); - assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isNull(); - } - - @Test - void of_WithEntryWithSupplyChannel_ShouldBuildInventoryEntryIdentifier() { - final InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, - Channel.referenceOfId(CHANNEL_ID), null); - final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - assertThat(inventoryEntryIdentifier).isNotNull(); - assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); - assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isEqualTo(CHANNEL_ID); - } - - @Test - void of_WithSkuAndNoSupplyChannel_ShouldBuildInventoryEntryIdentifier() { - final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(SKU, null); - assertThat(inventoryEntryIdentifier).isNotNull(); - assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); - assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isNull(); - } - - @Test - void of_WithSkuAndSupplyChannel_ShouldBuildInventoryEntryIdentifier() { - final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(SKU, CHANNEL_ID); - assertThat(inventoryEntryIdentifier).isNotNull(); - assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); - assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isEqualTo(CHANNEL_ID); - } - - @Test - void equals_WithSameIdentifier_ShouldBeTrue() { - // preparation - final InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, - Channel.referenceOfId(CHANNEL_ID), null); - final InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - - // test - assertThat(entryIdentifier).isEqualTo(entryIdentifier); - } - - @Test - void equals_WithEqualSkuAndChannelKey_ShouldBeTrue() { - // preparation - final InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, - Channel.referenceOfId(CHANNEL_ID), null); - final InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - final InventoryEntryIdentifier draftIdentifier = InventoryEntryIdentifier - .of(InventoryEntryDraft.of(SKU, 1L, null, null, Channel.referenceOfId(CHANNEL_ID))); - - // test - assertThat(entryIdentifier).isEqualTo(draftIdentifier); - } - - @Test - void equals_WithDifferentEntryAndDraft_ShouldBeFalse() { - // Different SKUs, same channels - InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, - Channel.referenceOfId(CHANNEL_ID), null); - InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - InventoryEntryIdentifier draftIdentifier = InventoryEntryIdentifier - .of(InventoryEntryDraft.of(SKU_2, 1L, null, null, Channel.referenceOfId(CHANNEL_ID))); - - assertThat(entryIdentifier).isNotEqualTo(draftIdentifier); - - // Same SKUs, different channels - inventoryEntry = getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); - entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - draftIdentifier = InventoryEntryIdentifier - .of(InventoryEntryDraft.of(SKU, 1L, null, null, Channel.referenceOfId(CHANNEL_ID_2))); - - assertThat(entryIdentifier).isNotEqualTo(draftIdentifier); - - // different SKUs, different channels - inventoryEntry = getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); - entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - draftIdentifier = InventoryEntryIdentifier - .of(InventoryEntryDraft.of(SKU_2, 1L, null, null, Channel.referenceOfId(CHANNEL_ID_2))); - - assertThat(entryIdentifier).isNotEqualTo(draftIdentifier); - } - - @Test - void equals_WithNoIdentifier_ShouldBeFalse() { - final InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, - Channel.referenceOfId(CHANNEL_ID), null); - final InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - - assertThat(entryIdentifier).isNotEqualTo(inventoryEntry); - } - - @Test - void inventoryEntryIdentifiersCreatedFromSimilarDraftAndEntry_ShouldHaveSameHashCodes() { - InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, - Channel.referenceOfId(CHANNEL_ID), null); - InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - InventoryEntryIdentifier draftIdentifier = InventoryEntryIdentifier - .of(InventoryEntryDraft.of(SKU, 1L, null, null, Channel.referenceOfId(CHANNEL_ID))); - - assertThat(entryIdentifier.hashCode()).isEqualTo(draftIdentifier.hashCode()); - - // No supply channel - inventoryEntry = getMockInventoryEntry(SKU, null, null, null, null, null); - entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - draftIdentifier = InventoryEntryIdentifier - .of(InventoryEntryDraft.of(SKU, 1L, null, null, null)); - - assertThat(entryIdentifier.hashCode()).isEqualTo(draftIdentifier.hashCode()); - } - - @Test - void inventoryEntryIdentifier_ShouldWorkAsHashMapKey() { - final InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, - Channel.referenceOfId(CHANNEL_ID), null); - final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - final Map map = new HashMap<>(); - map.put(inventoryEntryIdentifier, inventoryEntry); - - assertThat(map.containsKey(inventoryEntryIdentifier)).isTrue(); - assertThat(map.get(inventoryEntryIdentifier)).isEqualTo(inventoryEntry); - } - - @Test - void toString_WithBothSkuAndChannelKey_ShouldReturnCorrectString() { - // preparation - final InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, - Channel.referenceOfId(CHANNEL_ID), null); - final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); - - // test - final String result = inventoryEntryIdentifier.toString(); - - // assertion - assertThat(result).isEqualTo(format("{sku='%s', channelKey='%s'}", SKU, CHANNEL_ID)); - } + private static final String SKU = "123"; + private static final String SKU_2 = "321"; + private static final String CHANNEL_ID = "channel-id"; + private static final String CHANNEL_ID_2 = "channel-id-2"; + + @Test + void of_WithDraftWithoutSupplyChannel_ShouldBuildInventoryEntryIdentifier() { + final InventoryEntryDraft draft = InventoryEntryDraft.of(SKU, 1L); + final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(draft); + assertThat(inventoryEntryIdentifier).isNotNull(); + assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); + assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isNull(); + } + + @Test + void of_WithDraftWithSupplyChannel_ShouldBuildInventoryEntryIdentifier() { + final InventoryEntryDraft draft = + InventoryEntryDraft.of(SKU, 1L, null, null, Channel.referenceOfId(CHANNEL_ID)); + final InventoryEntryIdentifier inventoryEntryIdentifier = InventoryEntryIdentifier.of(draft); + assertThat(inventoryEntryIdentifier).isNotNull(); + assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); + assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isEqualTo(CHANNEL_ID); + } + + @Test + void of_WithEntryWithoutSupplyChannel_ShouldBuildInventoryEntryIdentifier() { + final InventoryEntry inventoryEntry = getMockInventoryEntry(SKU, null, null, null, null, null); + final InventoryEntryIdentifier inventoryEntryIdentifier = + InventoryEntryIdentifier.of(inventoryEntry); + assertThat(inventoryEntryIdentifier).isNotNull(); + assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); + assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isNull(); + } + + @Test + void of_WithEntryWithSupplyChannel_ShouldBuildInventoryEntryIdentifier() { + final InventoryEntry inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + final InventoryEntryIdentifier inventoryEntryIdentifier = + InventoryEntryIdentifier.of(inventoryEntry); + assertThat(inventoryEntryIdentifier).isNotNull(); + assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); + assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isEqualTo(CHANNEL_ID); + } + + @Test + void of_WithSkuAndNoSupplyChannel_ShouldBuildInventoryEntryIdentifier() { + final InventoryEntryIdentifier inventoryEntryIdentifier = + InventoryEntryIdentifier.of(SKU, null); + assertThat(inventoryEntryIdentifier).isNotNull(); + assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); + assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isNull(); + } + + @Test + void of_WithSkuAndSupplyChannel_ShouldBuildInventoryEntryIdentifier() { + final InventoryEntryIdentifier inventoryEntryIdentifier = + InventoryEntryIdentifier.of(SKU, CHANNEL_ID); + assertThat(inventoryEntryIdentifier).isNotNull(); + assertThat(inventoryEntryIdentifier.getInventoryEntrySku()).isEqualTo(SKU); + assertThat(inventoryEntryIdentifier.getInventoryEntryChannelKey()).isEqualTo(CHANNEL_ID); + } + + @Test + void equals_WithSameIdentifier_ShouldBeTrue() { + // preparation + final InventoryEntry inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + final InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); + + // test + assertThat(entryIdentifier).isEqualTo(entryIdentifier); + } + + @Test + void equals_WithEqualSkuAndChannelKey_ShouldBeTrue() { + // preparation + final InventoryEntry inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + final InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); + final InventoryEntryIdentifier draftIdentifier = + InventoryEntryIdentifier.of( + InventoryEntryDraft.of(SKU, 1L, null, null, Channel.referenceOfId(CHANNEL_ID))); + + // test + assertThat(entryIdentifier).isEqualTo(draftIdentifier); + } + + @Test + void equals_WithDifferentEntryAndDraft_ShouldBeFalse() { + // Different SKUs, same channels + InventoryEntry inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); + InventoryEntryIdentifier draftIdentifier = + InventoryEntryIdentifier.of( + InventoryEntryDraft.of(SKU_2, 1L, null, null, Channel.referenceOfId(CHANNEL_ID))); + + assertThat(entryIdentifier).isNotEqualTo(draftIdentifier); + + // Same SKUs, different channels + inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); + draftIdentifier = + InventoryEntryIdentifier.of( + InventoryEntryDraft.of(SKU, 1L, null, null, Channel.referenceOfId(CHANNEL_ID_2))); + + assertThat(entryIdentifier).isNotEqualTo(draftIdentifier); + + // different SKUs, different channels + inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); + draftIdentifier = + InventoryEntryIdentifier.of( + InventoryEntryDraft.of(SKU_2, 1L, null, null, Channel.referenceOfId(CHANNEL_ID_2))); + + assertThat(entryIdentifier).isNotEqualTo(draftIdentifier); + } + + @Test + void equals_WithNoIdentifier_ShouldBeFalse() { + final InventoryEntry inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + final InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); + + assertThat(entryIdentifier).isNotEqualTo(inventoryEntry); + } + + @Test + void inventoryEntryIdentifiersCreatedFromSimilarDraftAndEntry_ShouldHaveSameHashCodes() { + InventoryEntry inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + InventoryEntryIdentifier entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); + InventoryEntryIdentifier draftIdentifier = + InventoryEntryIdentifier.of( + InventoryEntryDraft.of(SKU, 1L, null, null, Channel.referenceOfId(CHANNEL_ID))); + + assertThat(entryIdentifier.hashCode()).isEqualTo(draftIdentifier.hashCode()); + + // No supply channel + inventoryEntry = getMockInventoryEntry(SKU, null, null, null, null, null); + entryIdentifier = InventoryEntryIdentifier.of(inventoryEntry); + draftIdentifier = + InventoryEntryIdentifier.of(InventoryEntryDraft.of(SKU, 1L, null, null, null)); + + assertThat(entryIdentifier.hashCode()).isEqualTo(draftIdentifier.hashCode()); + } + + @Test + void inventoryEntryIdentifier_ShouldWorkAsHashMapKey() { + final InventoryEntry inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + final InventoryEntryIdentifier inventoryEntryIdentifier = + InventoryEntryIdentifier.of(inventoryEntry); + final Map map = new HashMap<>(); + map.put(inventoryEntryIdentifier, inventoryEntry); + + assertThat(map.containsKey(inventoryEntryIdentifier)).isTrue(); + assertThat(map.get(inventoryEntryIdentifier)).isEqualTo(inventoryEntry); + } + + @Test + void toString_WithBothSkuAndChannelKey_ShouldReturnCorrectString() { + // preparation + final InventoryEntry inventoryEntry = + getMockInventoryEntry(SKU, null, null, null, Channel.referenceOfId(CHANNEL_ID), null); + final InventoryEntryIdentifier inventoryEntryIdentifier = + InventoryEntryIdentifier.of(inventoryEntry); + + // test + final String result = inventoryEntryIdentifier.toString(); + + // assertion + assertThat(result).isEqualTo(format("{sku='%s', channelKey='%s'}", SKU, CHANNEL_ID)); + } } diff --git a/src/test/java/com/commercetools/sync/inventories/helpers/InventoryReferenceResolverTest.java b/src/test/java/com/commercetools/sync/inventories/helpers/InventoryReferenceResolverTest.java index 964fc7da82..6ae86004e8 100644 --- a/src/test/java/com/commercetools/sync/inventories/helpers/InventoryReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/inventories/helpers/InventoryReferenceResolverTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.inventories.helpers; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.inventories.InventorySyncMockUtils; @@ -16,9 +25,6 @@ import io.sphere.sdk.models.SphereException; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.HashMap; @@ -26,194 +32,227 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; - -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class InventoryReferenceResolverTest { - private TypeService typeService; - private ChannelService channelService; - private InventorySyncOptions syncOptions; - - private static final String SKU = "1000"; - private static final String CHANNEL_KEY = "channel-key_1"; - private static final String UUID_KEY = UUID.randomUUID().toString(); - private static final String CUSTOM_TYPE_KEY = "customType-key_1"; - private static final String CHANNEL_ID = "1"; - private static final Long QUANTITY = 10L; - private static final Integer RESTOCKABLE_IN_DAYS = 10; - private static final ZonedDateTime DATE_1 = ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - typeService = getMockTypeService(); - channelService = InventorySyncMockUtils.getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)); - syncOptions = InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); - } - - @Test - void resolveSupplyChannelReference_WithNonExistingChannelAndNotEnsureChannel_ShouldNotResolveChannelReference() { - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final InventoryEntryDraft draft = InventoryEntryDraftBuilder - .of(SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofKey(CHANNEL_KEY)) + private TypeService typeService; + private ChannelService channelService; + private InventorySyncOptions syncOptions; + + private static final String SKU = "1000"; + private static final String CHANNEL_KEY = "channel-key_1"; + private static final String UUID_KEY = UUID.randomUUID().toString(); + private static final String CUSTOM_TYPE_KEY = "customType-key_1"; + private static final String CHANNEL_ID = "1"; + private static final Long QUANTITY = 10L; + private static final Integer RESTOCKABLE_IN_DAYS = 10; + private static final ZonedDateTime DATE_1 = + ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + typeService = getMockTypeService(); + channelService = + InventorySyncMockUtils.getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)); + syncOptions = InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build(); + } + + @Test + void + resolveSupplyChannelReference_WithNonExistingChannelAndNotEnsureChannel_ShouldNotResolveChannelReference() { + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final InventoryEntryDraft draft = + InventoryEntryDraftBuilder.of( + SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofKey(CHANNEL_KEY)) .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE_KEY, new HashMap<>())) .build(); - final InventoryReferenceResolver referenceResolver = - new InventoryReferenceResolver(syncOptions, typeService, channelService); - - referenceResolver.resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) - .exceptionally(exception -> { - assertThat(exception).isExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(exception.getCause()) - .isExactlyInstanceOf(CompletionException.class); - assertThat(exception.getCause().getCause()) - .isExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(exception.getCause().getCause().getMessage()) - .isEqualTo("Channel with key 'channel-key_1' does not exist."); - return null; - }).toCompletableFuture().join(); - } - - @Test - void - resolveSupplyChannelReference_WithNonExistingChannelAndEnsureChannel_ShouldResolveSupplyChannelReference() { - final InventorySyncOptions optionsWithEnsureChannels = InventorySyncOptionsBuilder.of(mock(SphereClient.class)) - .ensureChannels(true) - .build(); - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final InventoryEntryDraft draft = InventoryEntryDraftBuilder - .of(SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofKey(CHANNEL_KEY)) + final InventoryReferenceResolver referenceResolver = + new InventoryReferenceResolver(syncOptions, typeService, channelService); + + referenceResolver + .resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) + .exceptionally( + exception -> { + assertThat(exception).isExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(exception.getCause()).isExactlyInstanceOf(CompletionException.class); + assertThat(exception.getCause().getCause()) + .isExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(exception.getCause().getCause().getMessage()) + .isEqualTo("Channel with key 'channel-key_1' does not exist."); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void + resolveSupplyChannelReference_WithNonExistingChannelAndEnsureChannel_ShouldResolveSupplyChannelReference() { + final InventorySyncOptions optionsWithEnsureChannels = + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).ensureChannels(true).build(); + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final InventoryEntryDraft draft = + InventoryEntryDraftBuilder.of( + SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofKey(CHANNEL_KEY)) .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE_KEY, new HashMap<>())) .build(); - final InventoryReferenceResolver referenceResolver = - new InventoryReferenceResolver(optionsWithEnsureChannels, typeService, channelService); - - referenceResolver.resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) - .thenApply(InventoryEntryDraftBuilder::build) - .thenAccept(resolvedDraft -> { - assertThat(resolvedDraft.getSupplyChannel()).isNotNull(); - assertThat(resolvedDraft.getSupplyChannel().getId()).isEqualTo(CHANNEL_ID); - }).toCompletableFuture().join(); - } - - @Test - void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { - final InventoryEntryDraftBuilder draftBuilder = InventoryEntryDraftBuilder - .of(SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofId(UUID_KEY)) + final InventoryReferenceResolver referenceResolver = + new InventoryReferenceResolver(optionsWithEnsureChannels, typeService, channelService); + + referenceResolver + .resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) + .thenApply(InventoryEntryDraftBuilder::build) + .thenAccept( + resolvedDraft -> { + assertThat(resolvedDraft.getSupplyChannel()).isNotNull(); + assertThat(resolvedDraft.getSupplyChannel().getId()).isEqualTo(CHANNEL_ID); + }) + .toCompletableFuture() + .join(); + } + + @Test + void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { + final InventoryEntryDraftBuilder draftBuilder = + InventoryEntryDraftBuilder.of( + SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofId(UUID_KEY)) .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE_KEY, new HashMap<>())); - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFutureUtils.failed(new SphereException("bad request"))); - - final InventoryReferenceResolver referenceResolver = - new InventoryReferenceResolver(syncOptions, typeService, channelService); - - referenceResolver.resolveCustomTypeReference(draftBuilder) - .exceptionally(exception -> { - assertThat(exception).isExactlyInstanceOf(CompletionException.class); - assertThat(exception.getCause()).isExactlyInstanceOf(SphereException.class); - assertThat(exception.getCause().getMessage()).contains("bad request"); - return null; - }).toCompletableFuture().join(); - } - - @Test - void resolveCustomTypeReference_WithNonExistentCustomType_ShouldNotResolveCustomTypeReference() { - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final InventoryEntryDraftBuilder draftBuilder = InventoryEntryDraftBuilder - .of(SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofKey(CHANNEL_KEY)) + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFutureUtils.failed(new SphereException("bad request"))); + + final InventoryReferenceResolver referenceResolver = + new InventoryReferenceResolver(syncOptions, typeService, channelService); + + referenceResolver + .resolveCustomTypeReference(draftBuilder) + .exceptionally( + exception -> { + assertThat(exception).isExactlyInstanceOf(CompletionException.class); + assertThat(exception.getCause()).isExactlyInstanceOf(SphereException.class); + assertThat(exception.getCause().getMessage()).contains("bad request"); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void resolveCustomTypeReference_WithNonExistentCustomType_ShouldNotResolveCustomTypeReference() { + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final InventoryEntryDraftBuilder draftBuilder = + InventoryEntryDraftBuilder.of( + SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofKey(CHANNEL_KEY)) .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE_KEY, new HashMap<>())); - final InventoryReferenceResolver referenceResolver = - new InventoryReferenceResolver(syncOptions, typeService, channelService); - - final String expectedExceptionMessage = format(InventoryReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE, SKU); - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, CUSTOM_TYPE_KEY)); - - referenceResolver.resolveCustomTypeReference(draftBuilder) - .exceptionally(exception -> { - assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(exception.getCause().getMessage()) - .isEqualTo(expectedMessageWithCause); - return null; - }).toCompletableFuture().join(); - } - - @Test - void resolveSupplyChannelReference_WithEmptyIdOnSupplyChannelReference_ShouldNotResolveChannelReference() { - final InventoryEntryDraft draft = InventoryEntryDraftBuilder - .of(SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofKey("")) + final InventoryReferenceResolver referenceResolver = + new InventoryReferenceResolver(syncOptions, typeService, channelService); + + final String expectedExceptionMessage = + format(InventoryReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE, SKU); + final String expectedMessageWithCause = + format( + "%s Reason: %s", + expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, CUSTOM_TYPE_KEY)); + + referenceResolver + .resolveCustomTypeReference(draftBuilder) + .exceptionally( + exception -> { + assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(exception.getCause().getMessage()).isEqualTo(expectedMessageWithCause); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void + resolveSupplyChannelReference_WithEmptyIdOnSupplyChannelReference_ShouldNotResolveChannelReference() { + final InventoryEntryDraft draft = + InventoryEntryDraftBuilder.of( + SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofKey("")) .custom(CustomFieldsDraft.ofTypeKeyAndJson(CUSTOM_TYPE_KEY, new HashMap<>())) .build(); - final InventoryReferenceResolver referenceResolver = - new InventoryReferenceResolver(syncOptions, typeService, channelService); - - referenceResolver.resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) - .exceptionally(exception -> { - assertThat(exception).isExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(exception.getMessage()) - .isEqualTo(format("Failed to resolve supply channel resource identifier on " - + "InventoryEntryDraft with SKU:'1000'. Reason: %s", - BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - return null; - }).toCompletableFuture().join(); - } - - @Test - void resolveSupplyChannelReference_WithNullIdOnChannelReference_ShouldNotResolveSupplyChannelReference() { - final InventoryEntryDraft draft = mock(InventoryEntryDraft.class); - final Reference supplyChannelReference = Channel.referenceOfId(null); - when(draft.getSupplyChannel()).thenReturn(supplyChannelReference); - - final InventoryReferenceResolver referenceResolver = - new InventoryReferenceResolver(syncOptions, typeService, channelService); - - referenceResolver.resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) - .exceptionally(exception -> { - assertThat(exception).isExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(exception.getMessage()) - .isEqualTo(format("Failed to resolve supply channel resource identifier on " - + "InventoryEntryDraft with SKU:'null'. Reason: %s", - BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - return null; - }).toCompletableFuture().join(); - } - - @Test - void resolveSupplyChannelReference_WithResolvedSupplyChannelReference_ShouldNotResolveChannelReference() { - final InventoryEntryDraft draft = InventoryEntryDraftBuilder - .of(SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofId(CHANNEL_ID)) + final InventoryReferenceResolver referenceResolver = + new InventoryReferenceResolver(syncOptions, typeService, channelService); + + referenceResolver + .resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) + .exceptionally( + exception -> { + assertThat(exception).isExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(exception.getMessage()) + .isEqualTo( + format( + "Failed to resolve supply channel resource identifier on " + + "InventoryEntryDraft with SKU:'1000'. Reason: %s", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void + resolveSupplyChannelReference_WithNullIdOnChannelReference_ShouldNotResolveSupplyChannelReference() { + final InventoryEntryDraft draft = mock(InventoryEntryDraft.class); + final Reference supplyChannelReference = Channel.referenceOfId(null); + when(draft.getSupplyChannel()).thenReturn(supplyChannelReference); + + final InventoryReferenceResolver referenceResolver = + new InventoryReferenceResolver(syncOptions, typeService, channelService); + + referenceResolver + .resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) + .exceptionally( + exception -> { + assertThat(exception).isExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(exception.getMessage()) + .isEqualTo( + format( + "Failed to resolve supply channel resource identifier on " + + "InventoryEntryDraft with SKU:'null'. Reason: %s", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void + resolveSupplyChannelReference_WithResolvedSupplyChannelReference_ShouldNotResolveChannelReference() { + final InventoryEntryDraft draft = + InventoryEntryDraftBuilder.of( + SKU, QUANTITY, DATE_1, RESTOCKABLE_IN_DAYS, ResourceIdentifier.ofId(CHANNEL_ID)) .build(); - final InventoryReferenceResolver referenceResolver = - new InventoryReferenceResolver(syncOptions, typeService, channelService); - - referenceResolver.resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) - .thenApply(InventoryEntryDraftBuilder::build) - .thenAccept(resolvedDraft -> { - assertThat(resolvedDraft.getSupplyChannel()).isNotNull(); - assertThat(resolvedDraft.getSupplyChannel().getId()).isEqualTo(CHANNEL_ID); - }).toCompletableFuture().join(); - - } + final InventoryReferenceResolver referenceResolver = + new InventoryReferenceResolver(syncOptions, typeService, channelService); + + referenceResolver + .resolveSupplyChannelReference(InventoryEntryDraftBuilder.of(draft)) + .thenApply(InventoryEntryDraftBuilder::build) + .thenAccept( + resolvedDraft -> { + assertThat(resolvedDraft.getSupplyChannel()).isNotNull(); + assertThat(resolvedDraft.getSupplyChannel().getId()).isEqualTo(CHANNEL_ID); + }) + .toCompletableFuture() + .join(); + } } diff --git a/src/test/java/com/commercetools/sync/inventories/helpers/InventorySyncStatisticsTest.java b/src/test/java/com/commercetools/sync/inventories/helpers/InventorySyncStatisticsTest.java index fd6d95b21b..16988c28e5 100644 --- a/src/test/java/com/commercetools/sync/inventories/helpers/InventorySyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/inventories/helpers/InventorySyncStatisticsTest.java @@ -1,28 +1,29 @@ package com.commercetools.sync.inventories.helpers; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - class InventorySyncStatisticsTest { - private InventorySyncStatistics inventorySyncStatistics; + private InventorySyncStatistics inventorySyncStatistics; - @BeforeEach - void setup() { - inventorySyncStatistics = new InventorySyncStatistics(); - } + @BeforeEach + void setup() { + inventorySyncStatistics = new InventorySyncStatistics(); + } - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - inventorySyncStatistics.incrementCreated(1); - inventorySyncStatistics.incrementFailed(1); - inventorySyncStatistics.incrementUpdated(1); - inventorySyncStatistics.incrementProcessed(3); + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + inventorySyncStatistics.incrementCreated(1); + inventorySyncStatistics.incrementFailed(1); + inventorySyncStatistics.incrementUpdated(1); + inventorySyncStatistics.incrementProcessed(3); - assertThat(inventorySyncStatistics.getReportMessage()) - .isEqualTo("Summary: 3 inventory entries were processed in total " + assertThat(inventorySyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 3 inventory entries were processed in total " + "(1 created, 1 updated and 1 failed to sync)."); - } + } } diff --git a/src/test/java/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtilsTest.java index 26ddb7006e..dcf78a3f93 100644 --- a/src/test/java/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtilsTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.inventories.utils; +import static com.commercetools.sync.inventories.utils.InventoryReferenceResolutionUtils.buildInventoryQuery; +import static com.commercetools.sync.inventories.utils.InventoryReferenceResolutionUtils.mapToInventoryEntryDrafts; +import static com.commercetools.sync.products.ProductSyncMockUtils.getChannelMock; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.channels.Channel; import io.sphere.sdk.expansion.ExpansionPath; import io.sphere.sdk.inventory.InventoryEntry; @@ -8,112 +15,105 @@ import io.sphere.sdk.models.Reference; import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; - -import static com.commercetools.sync.inventories.utils.InventoryReferenceResolutionUtils.buildInventoryQuery; -import static com.commercetools.sync.inventories.utils.InventoryReferenceResolutionUtils.mapToInventoryEntryDrafts; -import static com.commercetools.sync.products.ProductSyncMockUtils.getChannelMock; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class InventoryReferenceResolutionUtilsTest { - @Test - void mapToInventoryEntryDrafts_WithAllExpandedReferences_ShouldReturnResourceIdentifiersWithKeys() { - //preparation - final String customTypeId = UUID.randomUUID().toString(); - final String customTypeKey = "customTypeKey"; - final Type mockCustomType = mock(Type.class); - when(mockCustomType.getId()).thenReturn(customTypeId); - when(mockCustomType.getKey()).thenReturn(customTypeKey); - - final List mockInventoryEntries = new ArrayList<>(); - final String channelKey = "channelKey"; - - for (int i = 0; i < 2; i++) { - final InventoryEntry mockInventoryEntry = mock(InventoryEntry.class); - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = Reference.ofResourceTypeIdAndObj(UUID.randomUUID().toString(), - mockCustomType); - when(mockCustomFields.getType()).thenReturn(typeReference); - when(mockInventoryEntry.getCustom()).thenReturn(mockCustomFields); - - final Channel channel = getChannelMock(channelKey); - final Reference channelReference = Reference - .ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel.getId(), channel); - when(mockInventoryEntry.getSupplyChannel()).thenReturn(channelReference); - - mockInventoryEntries.add(mockInventoryEntry); - } - - //test - final List referenceReplacedDrafts = - mapToInventoryEntryDrafts(mockInventoryEntries); - - //assertion - for (InventoryEntryDraft referenceReplacedDraft : referenceReplacedDrafts) { - assertThat(referenceReplacedDraft.getCustom().getType().getKey()).isEqualTo(customTypeKey); - assertThat(referenceReplacedDraft.getSupplyChannel().getKey()).isEqualTo(channelKey); - } + @Test + void + mapToInventoryEntryDrafts_WithAllExpandedReferences_ShouldReturnResourceIdentifiersWithKeys() { + // preparation + final String customTypeId = UUID.randomUUID().toString(); + final String customTypeKey = "customTypeKey"; + final Type mockCustomType = mock(Type.class); + when(mockCustomType.getId()).thenReturn(customTypeId); + when(mockCustomType.getKey()).thenReturn(customTypeKey); + + final List mockInventoryEntries = new ArrayList<>(); + final String channelKey = "channelKey"; + + for (int i = 0; i < 2; i++) { + final InventoryEntry mockInventoryEntry = mock(InventoryEntry.class); + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = + Reference.ofResourceTypeIdAndObj(UUID.randomUUID().toString(), mockCustomType); + when(mockCustomFields.getType()).thenReturn(typeReference); + when(mockInventoryEntry.getCustom()).thenReturn(mockCustomFields); + + final Channel channel = getChannelMock(channelKey); + final Reference channelReference = + Reference.ofResourceTypeIdAndIdAndObj( + Channel.referenceTypeId(), channel.getId(), channel); + when(mockInventoryEntry.getSupplyChannel()).thenReturn(channelReference); + + mockInventoryEntries.add(mockInventoryEntry); } - @Test - void mapToInventoryEntryDrafts_WithNonExpandedReferences_ShouldReturnResourceIdentifiersWithoutKeys() { - //preparation - final String customTypeId = UUID.randomUUID().toString(); - final List mockInventoryEntries = new ArrayList<>(); - final String channelId = UUID.randomUUID().toString(); + // test + final List referenceReplacedDrafts = + mapToInventoryEntryDrafts(mockInventoryEntries); - for (int i = 0; i < 2; i++) { - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = Type.referenceOfId(customTypeId); - when(mockCustomFields.getType()).thenReturn(typeReference); + // assertion + for (InventoryEntryDraft referenceReplacedDraft : referenceReplacedDrafts) { + assertThat(referenceReplacedDraft.getCustom().getType().getKey()).isEqualTo(customTypeKey); + assertThat(referenceReplacedDraft.getSupplyChannel().getKey()).isEqualTo(channelKey); + } + } - final InventoryEntry mockInventoryEntry = mock(InventoryEntry.class); - when(mockInventoryEntry.getCustom()).thenReturn(mockCustomFields); + @Test + void + mapToInventoryEntryDrafts_WithNonExpandedReferences_ShouldReturnResourceIdentifiersWithoutKeys() { + // preparation + final String customTypeId = UUID.randomUUID().toString(); + final List mockInventoryEntries = new ArrayList<>(); + final String channelId = UUID.randomUUID().toString(); - final Reference channelReference = Channel.referenceOfId(channelId); - when(mockInventoryEntry.getSupplyChannel()).thenReturn(channelReference); + for (int i = 0; i < 2; i++) { + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = Type.referenceOfId(customTypeId); + when(mockCustomFields.getType()).thenReturn(typeReference); - mockInventoryEntries.add(mockInventoryEntry); - } + final InventoryEntry mockInventoryEntry = mock(InventoryEntry.class); + when(mockInventoryEntry.getCustom()).thenReturn(mockCustomFields); - //test - final List referenceReplacedDrafts = - mapToInventoryEntryDrafts(mockInventoryEntries); + final Reference channelReference = Channel.referenceOfId(channelId); + when(mockInventoryEntry.getSupplyChannel()).thenReturn(channelReference); - //assertion - for (InventoryEntryDraft referenceReplacedDraft : referenceReplacedDrafts) { - assertThat(referenceReplacedDraft.getCustom().getType().getId()).isEqualTo(customTypeId); - assertThat(referenceReplacedDraft.getSupplyChannel().getId()).isEqualTo(channelId); - } + mockInventoryEntries.add(mockInventoryEntry); } - @Test - void mapToInventoryEntryDrafts_WithNullReferences_ShouldNotReturnResourceIdentifiers() { - //test - final List referenceReplacedDrafts = - mapToInventoryEntryDrafts(Collections.singletonList(mock(InventoryEntry.class))); - - //assertion - for (InventoryEntryDraft referenceReplacedDraft : referenceReplacedDrafts) { - assertThat(referenceReplacedDraft.getCustom()).isNull(); - assertThat(referenceReplacedDraft.getSupplyChannel()).isNull(); - } - } + // test + final List referenceReplacedDrafts = + mapToInventoryEntryDrafts(mockInventoryEntries); - @Test - void buildInventoryQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final InventoryEntryQuery inventoryEntryQuery = buildInventoryQuery(); - assertThat(inventoryEntryQuery.expansionPaths()).containsExactly( - ExpansionPath.of("custom.type"), - ExpansionPath.of("supplyChannel")); + // assertion + for (InventoryEntryDraft referenceReplacedDraft : referenceReplacedDrafts) { + assertThat(referenceReplacedDraft.getCustom().getType().getId()).isEqualTo(customTypeId); + assertThat(referenceReplacedDraft.getSupplyChannel().getId()).isEqualTo(channelId); } - + } + + @Test + void mapToInventoryEntryDrafts_WithNullReferences_ShouldNotReturnResourceIdentifiers() { + // test + final List referenceReplacedDrafts = + mapToInventoryEntryDrafts(Collections.singletonList(mock(InventoryEntry.class))); + + // assertion + for (InventoryEntryDraft referenceReplacedDraft : referenceReplacedDrafts) { + assertThat(referenceReplacedDraft.getCustom()).isNull(); + assertThat(referenceReplacedDraft.getSupplyChannel()).isNull(); + } + } + + @Test + void buildInventoryQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final InventoryEntryQuery inventoryEntryQuery = buildInventoryQuery(); + assertThat(inventoryEntryQuery.expansionPaths()) + .containsExactly(ExpansionPath.of("custom.type"), ExpansionPath.of("supplyChannel")); + } } diff --git a/src/test/java/com/commercetools/sync/inventories/utils/InventorySyncUtilsTest.java b/src/test/java/com/commercetools/sync/inventories/utils/InventorySyncUtilsTest.java index b38e5a5fc4..edfb9bd83e 100644 --- a/src/test/java/com/commercetools/sync/inventories/utils/InventorySyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/inventories/utils/InventorySyncUtilsTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.inventories.utils; +import static com.commercetools.sync.commons.MockUtils.getMockCustomFields; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockInventoryEntry; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.inventories.InventorySyncOptionsBuilder; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import io.sphere.sdk.channels.Channel; @@ -19,153 +25,166 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.CustomFieldsDraftBuilder; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; - -import static com.commercetools.sync.commons.MockUtils.getMockCustomFields; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockInventoryEntry; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class InventorySyncUtilsTest { - private static final String CUSTOM_TYPE_ID = "testId"; - private static final String CUSTOM_FIELD_1_NAME = "testField"; - private static final String CUSTOM_FIELD_2_NAME = "differentField"; - private static final String CUSTOM_FIELD_1_VALUE = "testValue"; - private static final String CUSTOM_FIELD_2_VALUE = "differentValue"; - - private static final ZonedDateTime DATE_1 = ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); - private static final ZonedDateTime DATE_2 = ZonedDateTime.of(2017, 5, 1, 20, 0, 0, 0, ZoneId.of("UTC")); - - private InventoryEntry inventoryEntry; - private InventoryEntry inventoryEntryWithCustomField1; - private InventoryEntryDraft similarDraft; - private InventoryEntryDraft variousDraft; - - /** - * Initialises test data. - */ - @BeforeEach - void setup() { - final Channel channel = getMockSupplyChannel("111", "key1"); - final Reference reference = Channel.referenceOfId("111").filled(channel); - final CustomFields customFields = getMockCustomFields(CUSTOM_TYPE_ID, CUSTOM_FIELD_1_NAME, + private static final String CUSTOM_TYPE_ID = "testId"; + private static final String CUSTOM_FIELD_1_NAME = "testField"; + private static final String CUSTOM_FIELD_2_NAME = "differentField"; + private static final String CUSTOM_FIELD_1_VALUE = "testValue"; + private static final String CUSTOM_FIELD_2_VALUE = "differentValue"; + + private static final ZonedDateTime DATE_1 = + ZonedDateTime.of(2017, 4, 1, 10, 0, 0, 0, ZoneId.of("UTC")); + private static final ZonedDateTime DATE_2 = + ZonedDateTime.of(2017, 5, 1, 20, 0, 0, 0, ZoneId.of("UTC")); + + private InventoryEntry inventoryEntry; + private InventoryEntry inventoryEntryWithCustomField1; + private InventoryEntryDraft similarDraft; + private InventoryEntryDraft variousDraft; + + /** Initialises test data. */ + @BeforeEach + void setup() { + final Channel channel = getMockSupplyChannel("111", "key1"); + final Reference reference = Channel.referenceOfId("111").filled(channel); + final CustomFields customFields = + getMockCustomFields( + CUSTOM_TYPE_ID, + CUSTOM_FIELD_1_NAME, JsonNodeFactory.instance.textNode(CUSTOM_FIELD_1_VALUE)); - inventoryEntry = getMockInventoryEntry("123", 10L, 10, DATE_1, reference, null); - inventoryEntryWithCustomField1 = getMockInventoryEntry("123", 10L, 10, DATE_1, reference, customFields); - - - similarDraft = InventoryEntryDraftBuilder.of("123", 10L, DATE_1, 10, ResourceIdentifier.ofId("111")) - .build(); - variousDraft = InventoryEntryDraftBuilder.of("321", 20L, DATE_2, 20, ResourceIdentifier.ofId("222")) - .build(); - } - - @Test - void buildActions_WithSimilarEntries_ShouldReturnEmptyList() { - List> actions = InventorySyncUtils - .buildActions(inventoryEntry, similarDraft, InventorySyncOptionsBuilder.of(mock(SphereClient.class)) - .build()); - - assertThat(actions).isEmpty(); - } - - @Test - void buildActions_WithVariousEntries_ShouldReturnActions() { - List> actions = InventorySyncUtils - .buildActions(inventoryEntry, variousDraft, InventorySyncOptionsBuilder.of(mock(SphereClient.class)) - .build()); - - assertThat(actions).hasSize(4); - assertThat(actions.get(0)).isNotNull(); - assertThat(actions.get(1)).isNotNull(); - assertThat(actions.get(2)).isNotNull(); - assertThat(actions.get(3)).isNotNull(); - assertThat(actions.get(0)).isInstanceOf(ChangeQuantity.class); - assertThat(actions.get(1)).isInstanceOf(SetRestockableInDays.class); - assertThat(actions.get(2)).isInstanceOf(SetExpectedDelivery.class); - assertThat(actions.get(3)).isInstanceOfAny(SetSupplyChannel.class); - } - - @Test - void buildActions_WithSimilarEntriesAndSameCustomFields_ShouldReturnEmptyList() { - final InventoryEntryDraft draft = InventoryEntryDraftBuilder.of(similarDraft) - .custom(getDraftOfCustomField(CUSTOM_FIELD_1_NAME, - CUSTOM_FIELD_1_VALUE)) - .build(); - final List> actions = InventorySyncUtils - .buildActions(inventoryEntryWithCustomField1, - draft, InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); - - assertThat(actions).isEmpty(); - } - - @Test - void buildActions_WithSimilarEntriesAndNewCustomTypeSet_ShouldReturnActions() { - final InventoryEntryDraft draft = InventoryEntryDraftBuilder.of(similarDraft) - .custom(getDraftOfCustomField(CUSTOM_FIELD_2_NAME, - CUSTOM_FIELD_2_VALUE)).build(); - final List> actions = InventorySyncUtils.buildActions(inventoryEntry, draft, + inventoryEntry = getMockInventoryEntry("123", 10L, 10, DATE_1, reference, null); + inventoryEntryWithCustomField1 = + getMockInventoryEntry("123", 10L, 10, DATE_1, reference, customFields); + + similarDraft = + InventoryEntryDraftBuilder.of("123", 10L, DATE_1, 10, ResourceIdentifier.ofId("111")) + .build(); + variousDraft = + InventoryEntryDraftBuilder.of("321", 20L, DATE_2, 20, ResourceIdentifier.ofId("222")) + .build(); + } + + @Test + void buildActions_WithSimilarEntries_ShouldReturnEmptyList() { + List> actions = + InventorySyncUtils.buildActions( + inventoryEntry, + similarDraft, + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions).isEmpty(); + } + + @Test + void buildActions_WithVariousEntries_ShouldReturnActions() { + List> actions = + InventorySyncUtils.buildActions( + inventoryEntry, + variousDraft, + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions).hasSize(4); + assertThat(actions.get(0)).isNotNull(); + assertThat(actions.get(1)).isNotNull(); + assertThat(actions.get(2)).isNotNull(); + assertThat(actions.get(3)).isNotNull(); + assertThat(actions.get(0)).isInstanceOf(ChangeQuantity.class); + assertThat(actions.get(1)).isInstanceOf(SetRestockableInDays.class); + assertThat(actions.get(2)).isInstanceOf(SetExpectedDelivery.class); + assertThat(actions.get(3)).isInstanceOfAny(SetSupplyChannel.class); + } + + @Test + void buildActions_WithSimilarEntriesAndSameCustomFields_ShouldReturnEmptyList() { + final InventoryEntryDraft draft = + InventoryEntryDraftBuilder.of(similarDraft) + .custom(getDraftOfCustomField(CUSTOM_FIELD_1_NAME, CUSTOM_FIELD_1_VALUE)) + .build(); + final List> actions = + InventorySyncUtils.buildActions( + inventoryEntryWithCustomField1, + draft, + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions).isEmpty(); + } + + @Test + void buildActions_WithSimilarEntriesAndNewCustomTypeSet_ShouldReturnActions() { + final InventoryEntryDraft draft = + InventoryEntryDraftBuilder.of(similarDraft) + .custom(getDraftOfCustomField(CUSTOM_FIELD_2_NAME, CUSTOM_FIELD_2_VALUE)) + .build(); + final List> actions = + InventorySyncUtils.buildActions( + inventoryEntry, + draft, InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); - assertThat(actions).hasSize(1); - assertThat(actions.get(0)).isNotNull(); - assertThat(actions.get(0)).isInstanceOf(SetCustomType.class); - } - - @Test - void buildActions_WithSimilarEntriesAndRemovedExistingCustomType_ShouldReturnActions() { - final List> actions = InventorySyncUtils - .buildActions(inventoryEntryWithCustomField1, - similarDraft, InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); - - assertThat(actions).hasSize(1); - assertThat(actions.get(0)).isNotNull(); - assertThat(actions.get(0)).isInstanceOf(SetCustomType.class); - } - - @Test - void buildActions_WithSimilarEntriesButDifferentCustomFields_ShouldReturnActions() { - final InventoryEntryDraft draft = InventoryEntryDraftBuilder.of(similarDraft) - .custom(getDraftOfCustomField(CUSTOM_FIELD_2_NAME, - CUSTOM_FIELD_2_VALUE)) - .build(); - final List> actions = - InventorySyncUtils.buildActions(inventoryEntryWithCustomField1, draft, - InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); - - assertThat(actions).hasSize(2); - assertThat(actions.get(0)).isNotNull(); - assertThat(actions.get(1)).isNotNull(); - assertThat(actions.get(0)).isInstanceOf(SetCustomField.class); - assertThat(actions.get(1)).isInstanceOf(SetCustomField.class); - } - - @Test - void buildActions_WithSimilarEntriesButDifferentCustomFieldValues_ShouldReturnActions() { - final InventoryEntryDraft draft = InventoryEntryDraftBuilder.of(similarDraft) - .custom(getDraftOfCustomField(CUSTOM_FIELD_1_NAME, - CUSTOM_FIELD_2_VALUE)) - .build(); - final List> actions = InventorySyncUtils - .buildActions(inventoryEntryWithCustomField1, draft, InventorySyncOptionsBuilder - .of(mock(SphereClient.class)) - .build()); - - assertThat(actions).hasSize(1); - assertThat(actions.get(0)).isNotNull(); - assertThat(actions.get(0)).isInstanceOf(SetCustomField.class); - } - - private CustomFieldsDraft getDraftOfCustomField(final String fieldName, final String fieldValue) { - return CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) - .addObject(fieldName, fieldValue) - .build(); - } + assertThat(actions).hasSize(1); + assertThat(actions.get(0)).isNotNull(); + assertThat(actions.get(0)).isInstanceOf(SetCustomType.class); + } + + @Test + void buildActions_WithSimilarEntriesAndRemovedExistingCustomType_ShouldReturnActions() { + final List> actions = + InventorySyncUtils.buildActions( + inventoryEntryWithCustomField1, + similarDraft, + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions).hasSize(1); + assertThat(actions.get(0)).isNotNull(); + assertThat(actions.get(0)).isInstanceOf(SetCustomType.class); + } + + @Test + void buildActions_WithSimilarEntriesButDifferentCustomFields_ShouldReturnActions() { + final InventoryEntryDraft draft = + InventoryEntryDraftBuilder.of(similarDraft) + .custom(getDraftOfCustomField(CUSTOM_FIELD_2_NAME, CUSTOM_FIELD_2_VALUE)) + .build(); + final List> actions = + InventorySyncUtils.buildActions( + inventoryEntryWithCustomField1, + draft, + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions).hasSize(2); + assertThat(actions.get(0)).isNotNull(); + assertThat(actions.get(1)).isNotNull(); + assertThat(actions.get(0)).isInstanceOf(SetCustomField.class); + assertThat(actions.get(1)).isInstanceOf(SetCustomField.class); + } + + @Test + void buildActions_WithSimilarEntriesButDifferentCustomFieldValues_ShouldReturnActions() { + final InventoryEntryDraft draft = + InventoryEntryDraftBuilder.of(similarDraft) + .custom(getDraftOfCustomField(CUSTOM_FIELD_1_NAME, CUSTOM_FIELD_2_VALUE)) + .build(); + final List> actions = + InventorySyncUtils.buildActions( + inventoryEntryWithCustomField1, + draft, + InventorySyncOptionsBuilder.of(mock(SphereClient.class)).build()); + + assertThat(actions).hasSize(1); + assertThat(actions.get(0)).isNotNull(); + assertThat(actions.get(0)).isInstanceOf(SetCustomField.class); + } + + private CustomFieldsDraft getDraftOfCustomField(final String fieldName, final String fieldValue) { + return CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) + .addObject(fieldName, fieldValue) + .build(); + } } diff --git a/src/test/java/com/commercetools/sync/inventories/utils/InventoryUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/inventories/utils/InventoryUpdateActionUtilsTest.java index 5fa2cd46d1..017348df47 100644 --- a/src/test/java/com/commercetools/sync/inventories/utils/InventoryUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/inventories/utils/InventoryUpdateActionUtilsTest.java @@ -1,5 +1,13 @@ package com.commercetools.sync.inventories.utils; +import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildChangeQuantityAction; +import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetExpectedDeliveryAction; +import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetRestockableInDaysAction; +import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetSupplyChannelAction; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.channels.Channel; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.inventory.InventoryEntry; @@ -11,170 +19,168 @@ import io.sphere.sdk.inventory.commands.updateactions.SetSupplyChannel; import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Optional; import java.util.function.BiFunction; - -import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildChangeQuantityAction; -import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetExpectedDeliveryAction; -import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetRestockableInDaysAction; -import static com.commercetools.sync.inventories.utils.InventoryUpdateActionUtils.buildSetSupplyChannelAction; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; class InventoryUpdateActionUtilsTest { - private static InventoryEntry old; - private static InventoryEntryDraft newSame; - private static InventoryEntryDraft newDifferent; - private static InventoryEntryDraft newWithNullValues; - - /** - * Initialises test data. - */ - @BeforeAll - static void setup() { - final ZonedDateTime date1 = ZonedDateTime.of(2017, 5, 1, 10, 0, 0, 0, ZoneId.of("UTC")); - final ZonedDateTime date2 = ZonedDateTime.of(2017, 4, 1, 12, 0, 0, 0, ZoneId.of("UTC")); - - final Reference supplyChannel1 = Channel.referenceOfId("456"); - final Reference supplyChannel2 = Channel.referenceOfId("789"); - - old = mock(InventoryEntry.class); - when(old.getSku()).thenReturn("123"); - when(old.getQuantityOnStock()).thenReturn(10L); - when(old.getRestockableInDays()).thenReturn(10); - when(old.getExpectedDelivery()).thenReturn(date1); - when(old.getSupplyChannel()).thenReturn(supplyChannel1); - - newSame = InventoryEntryDraftBuilder - .of("123", 10L, date1, 10, ResourceIdentifier.ofId(supplyChannel1.getId())) - .build(); - newDifferent = InventoryEntryDraftBuilder - .of("123", 20L, date2, 20, ResourceIdentifier.ofId(supplyChannel2.getId())) + private static InventoryEntry old; + private static InventoryEntryDraft newSame; + private static InventoryEntryDraft newDifferent; + private static InventoryEntryDraft newWithNullValues; + + /** Initialises test data. */ + @BeforeAll + static void setup() { + final ZonedDateTime date1 = ZonedDateTime.of(2017, 5, 1, 10, 0, 0, 0, ZoneId.of("UTC")); + final ZonedDateTime date2 = ZonedDateTime.of(2017, 4, 1, 12, 0, 0, 0, ZoneId.of("UTC")); + + final Reference supplyChannel1 = Channel.referenceOfId("456"); + final Reference supplyChannel2 = Channel.referenceOfId("789"); + + old = mock(InventoryEntry.class); + when(old.getSku()).thenReturn("123"); + when(old.getQuantityOnStock()).thenReturn(10L); + when(old.getRestockableInDays()).thenReturn(10); + when(old.getExpectedDelivery()).thenReturn(date1); + when(old.getSupplyChannel()).thenReturn(supplyChannel1); + + newSame = + InventoryEntryDraftBuilder.of( + "123", 10L, date1, 10, ResourceIdentifier.ofId(supplyChannel1.getId())) .build(); - newWithNullValues = InventoryEntryDraftBuilder - .of("123", 20L, null, null, null) + newDifferent = + InventoryEntryDraftBuilder.of( + "123", 20L, date2, 20, ResourceIdentifier.ofId(supplyChannel2.getId())) .build(); - } - - @Test - void buildChangeQuantityAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeQuantityAction(old, newDifferent); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isExactlyInstanceOf(ChangeQuantity.class); - assertThat(((ChangeQuantity) result.get()).getQuantity()).isEqualTo(newDifferent.getQuantityOnStock()); - } - - @Test - void buildChangeQuantityAction_WithNewNullValue_ShouldReturnAction() { - final InventoryEntryDraft draft = mock(InventoryEntryDraft.class); - when(draft.getQuantityOnStock()).thenReturn(null); - final Optional> result = buildChangeQuantityAction(old, draft); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isExactlyInstanceOf(ChangeQuantity.class); - assertThat(((ChangeQuantity) result.get()).getQuantity()).isEqualTo(0L); - } - - @Test - void buildChangeQuantityAction_WithNewNullValueAndOldZeroValue_ShouldReturnEmptyOptional() { - final InventoryEntryDraft draft = mock(InventoryEntryDraft.class); - when(draft.getQuantityOnStock()).thenReturn(null); - final InventoryEntry entry = mock(InventoryEntry.class); - when(draft.getQuantityOnStock()).thenReturn(0L); - final Optional> result = buildChangeQuantityAction(entry, draft); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isFalse(); - } - - @Test - void buildChangeQuantityAction_WithSameValues_ShouldReturnEmptyOptional() { - assertNoUpdatesForSameValues(InventoryUpdateActionUtils::buildChangeQuantityAction); - } - - @Test - void buildSetRestockableInDaysAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetRestockableInDaysAction(old, newDifferent); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isExactlyInstanceOf(SetRestockableInDays.class); - assertThat(((SetRestockableInDays) result.get()).getRestockableInDays()) - .isEqualTo(newDifferent.getRestockableInDays()); - } - - @Test - void buildSetRestockableInDaysAction_WithNewNullValue_ShouldReturnAction() { - final Optional> result = buildSetRestockableInDaysAction(old, newWithNullValues); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isExactlyInstanceOf(SetRestockableInDays.class); - assertThat(((SetRestockableInDays) result.get()).getRestockableInDays()).isNull(); - } - - @Test - void buildSetRestockableInDaysAction_WithSameValues_ShouldReturnEmptyOptional() { - assertNoUpdatesForSameValues(InventoryUpdateActionUtils::buildSetRestockableInDaysAction); - } - - @Test - void buildSetExpectedDeliveryAction_WithDifferentValue_ShouldReturnActions() { - final Optional> result = buildSetExpectedDeliveryAction(old, newDifferent); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isExactlyInstanceOf(SetExpectedDelivery.class); - assertThat(((SetExpectedDelivery) result.get()).getExpectedDelivery()) - .isEqualTo(newDifferent.getExpectedDelivery()); - } - - @Test - void buildSetExpectedDeliveryAction_WithNewNullValue_ShouldReturnAction() { - final Optional> result = buildSetExpectedDeliveryAction(old, newWithNullValues); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isExactlyInstanceOf(SetExpectedDelivery.class); - assertThat(((SetExpectedDelivery) result.get()).getExpectedDelivery()).isNull(); - } - - @Test - void buildSetExpectedDeliveryAction_WithSameValues_ShouldReturnEmptyOptional() { - assertNoUpdatesForSameValues(InventoryUpdateActionUtils::buildSetExpectedDeliveryAction); - } - - @Test - void buildSetSupplyChannelAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetSupplyChannelAction(old, newDifferent); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isExactlyInstanceOf(SetSupplyChannel.class); - assertThat(((SetSupplyChannel) result.get()).getSupplyChannel()) - .isEqualTo(newDifferent.getSupplyChannel()); - } - - @Test - void buildSetSupplyChannelAction_WithNewNullValue_ShouldReturnAction() { - final Optional> result = buildSetSupplyChannelAction(old, newWithNullValues); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isTrue(); - assertThat(result.get()).isExactlyInstanceOf(SetSupplyChannel.class); - assertThat(((SetSupplyChannel) result.get()).getSupplyChannel()).isNull(); - } - - @Test - void buildSetSupplyChannelAction_WithSameValues_ShouldReturnEmptyOptional() { - assertNoUpdatesForSameValues(InventoryUpdateActionUtils::buildSetSupplyChannelAction); - } - - private void assertNoUpdatesForSameValues(final BiFunction>> buildFunction) { - final Optional> result = buildFunction.apply(old, newSame); - assertThat(result).isNotNull(); - assertThat(result.isPresent()).isFalse(); - } + newWithNullValues = InventoryEntryDraftBuilder.of("123", 20L, null, null, null).build(); + } + + @Test + void buildChangeQuantityAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildChangeQuantityAction(old, newDifferent); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isTrue(); + assertThat(result.get()).isExactlyInstanceOf(ChangeQuantity.class); + assertThat(((ChangeQuantity) result.get()).getQuantity()) + .isEqualTo(newDifferent.getQuantityOnStock()); + } + + @Test + void buildChangeQuantityAction_WithNewNullValue_ShouldReturnAction() { + final InventoryEntryDraft draft = mock(InventoryEntryDraft.class); + when(draft.getQuantityOnStock()).thenReturn(null); + final Optional> result = buildChangeQuantityAction(old, draft); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isTrue(); + assertThat(result.get()).isExactlyInstanceOf(ChangeQuantity.class); + assertThat(((ChangeQuantity) result.get()).getQuantity()).isEqualTo(0L); + } + + @Test + void buildChangeQuantityAction_WithNewNullValueAndOldZeroValue_ShouldReturnEmptyOptional() { + final InventoryEntryDraft draft = mock(InventoryEntryDraft.class); + when(draft.getQuantityOnStock()).thenReturn(null); + final InventoryEntry entry = mock(InventoryEntry.class); + when(draft.getQuantityOnStock()).thenReturn(0L); + final Optional> result = buildChangeQuantityAction(entry, draft); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isFalse(); + } + + @Test + void buildChangeQuantityAction_WithSameValues_ShouldReturnEmptyOptional() { + assertNoUpdatesForSameValues(InventoryUpdateActionUtils::buildChangeQuantityAction); + } + + @Test + void buildSetRestockableInDaysAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetRestockableInDaysAction(old, newDifferent); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isTrue(); + assertThat(result.get()).isExactlyInstanceOf(SetRestockableInDays.class); + assertThat(((SetRestockableInDays) result.get()).getRestockableInDays()) + .isEqualTo(newDifferent.getRestockableInDays()); + } + + @Test + void buildSetRestockableInDaysAction_WithNewNullValue_ShouldReturnAction() { + final Optional> result = + buildSetRestockableInDaysAction(old, newWithNullValues); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isTrue(); + assertThat(result.get()).isExactlyInstanceOf(SetRestockableInDays.class); + assertThat(((SetRestockableInDays) result.get()).getRestockableInDays()).isNull(); + } + + @Test + void buildSetRestockableInDaysAction_WithSameValues_ShouldReturnEmptyOptional() { + assertNoUpdatesForSameValues(InventoryUpdateActionUtils::buildSetRestockableInDaysAction); + } + + @Test + void buildSetExpectedDeliveryAction_WithDifferentValue_ShouldReturnActions() { + final Optional> result = + buildSetExpectedDeliveryAction(old, newDifferent); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isTrue(); + assertThat(result.get()).isExactlyInstanceOf(SetExpectedDelivery.class); + assertThat(((SetExpectedDelivery) result.get()).getExpectedDelivery()) + .isEqualTo(newDifferent.getExpectedDelivery()); + } + + @Test + void buildSetExpectedDeliveryAction_WithNewNullValue_ShouldReturnAction() { + final Optional> result = + buildSetExpectedDeliveryAction(old, newWithNullValues); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isTrue(); + assertThat(result.get()).isExactlyInstanceOf(SetExpectedDelivery.class); + assertThat(((SetExpectedDelivery) result.get()).getExpectedDelivery()).isNull(); + } + + @Test + void buildSetExpectedDeliveryAction_WithSameValues_ShouldReturnEmptyOptional() { + assertNoUpdatesForSameValues(InventoryUpdateActionUtils::buildSetExpectedDeliveryAction); + } + + @Test + void buildSetSupplyChannelAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetSupplyChannelAction(old, newDifferent); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isTrue(); + assertThat(result.get()).isExactlyInstanceOf(SetSupplyChannel.class); + assertThat(((SetSupplyChannel) result.get()).getSupplyChannel()) + .isEqualTo(newDifferent.getSupplyChannel()); + } + + @Test + void buildSetSupplyChannelAction_WithNewNullValue_ShouldReturnAction() { + final Optional> result = + buildSetSupplyChannelAction(old, newWithNullValues); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isTrue(); + assertThat(result.get()).isExactlyInstanceOf(SetSupplyChannel.class); + assertThat(((SetSupplyChannel) result.get()).getSupplyChannel()).isNull(); + } + + @Test + void buildSetSupplyChannelAction_WithSameValues_ShouldReturnEmptyOptional() { + assertNoUpdatesForSameValues(InventoryUpdateActionUtils::buildSetSupplyChannelAction); + } + + private void assertNoUpdatesForSameValues( + final BiFunction>> + buildFunction) { + final Optional> result = buildFunction.apply(old, newSame); + assertThat(result).isNotNull(); + assertThat(result.isPresent()).isFalse(); + } } diff --git a/src/test/java/com/commercetools/sync/products/ProductSyncMockUtils.java b/src/test/java/com/commercetools/sync/products/ProductSyncMockUtils.java index 31804f774d..5e60f08258 100644 --- a/src/test/java/com/commercetools/sync/products/ProductSyncMockUtils.java +++ b/src/test/java/com/commercetools/sync/products/ProductSyncMockUtils.java @@ -1,5 +1,16 @@ package com.commercetools.sync.products; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.services.CategoryService; import com.commercetools.sync.services.CustomObjectService; import com.commercetools.sync.services.CustomerGroupService; @@ -33,9 +44,6 @@ import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.Type; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Arrays; @@ -49,459 +57,493 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; - -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static java.util.Optional.ofNullable; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class ProductSyncMockUtils { - public static final String PRODUCT_KEY_1_RESOURCE_PATH = "product-key-1.json"; - public static final String PRODUCT_KEY_SPECIAL_CHARS_RESOURCE_PATH = "product-key-with-special-character.json"; - public static final String PRODUCT_KEY_1_CHANGED_RESOURCE_PATH = "product-key-1-changed.json"; - public static final String PRODUCT_KEY_1_CHANGED_ATTRIBUTES_RESOURCE_PATH = "product-key-1-changed-attributes.json"; - public static final String PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH = "product-key-1-with-prices.json"; - public static final String PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH = - "product-key-1-changed-with-prices.json"; - public static final String PRODUCT_KEY_2_RESOURCE_PATH = "product-key-2.json"; - public static final String PRODUCT_WITH_VARS_RESOURCE_PATH = "product-with-variants.json"; - public static final String PRODUCT_NO_VARS_RESOURCE_PATH = "product-with-no-variants.json"; - public static final String PRODUCT_TYPE_RESOURCE_PATH = "product-type.json"; - public static final String PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH = "product-type-with-references.json"; - public static final String PRODUCT_TYPE_NO_KEY_RESOURCE_PATH = "product-type-no-key.json"; - public static final String CATEGORY_KEY_1_RESOURCE_PATH = "category-key-1.json"; - public static final String SIMPLE_PRODUCT_WITH_MASTER_VARIANT_RESOURCE_PATH = - "simple-product-with-master-variant.json"; - public static final String SIMPLE_PRODUCT_WITH_MULTIPLE_VARIANTS_RESOURCE_PATH = - "simple-product-with-multiple-variants.json"; - - - /** - * Unfortunately, - * Category Order Hints in CTP platform is quite picky: it requires number values as a string - * and only without trailing zeros and only in fixed point format. - * - * @see - * http://dev.commercetools.com/http-api-projects-products.html#category-order-hints - */ - private static final DecimalFormat ORDER_HINT_FORMAT; - - static { - ORDER_HINT_FORMAT = new DecimalFormat(); - ORDER_HINT_FORMAT.setMaximumFractionDigits(Integer.MAX_VALUE); - ORDER_HINT_FORMAT.setMaximumIntegerDigits(1); - DecimalFormatSymbols formatSymbols = new DecimalFormatSymbols(Locale.ENGLISH); - formatSymbols.setDecimalSeparator('.'); - formatSymbols.setGroupingSeparator('.'); - ORDER_HINT_FORMAT.setDecimalFormatSymbols(formatSymbols); - } - - /** - * Builds a {@link ProductDraftBuilder} based on the staged projection of the product JSON resource located at the - * {@code jsonResourcePath} and based on the supplied {@code productType}. - * TODO: GITHUB ISSUE#152 - * - * @param jsonResourcePath the path of the JSON resource to build the product draft from. - * @param productTypeReference the reference of the product type that the product draft belongs to. - * @return a {@link ProductDraftBuilder} instance containing the data from the current projection of the specified - * JSON resource and the product type. - */ - public static ProductDraftBuilder createProductDraftBuilder(@Nonnull final String jsonResourcePath, - @Nonnull final ResourceIdentifiable - productTypeReference) { - final Product productFromJson = readObjectFromResource(jsonResourcePath, Product.class); - final ProductData productData = productFromJson.getMasterData().getStaged(); - - @SuppressWarnings("ConstantConditions") final List allVariants = productData - .getAllVariants().stream() + public static final String PRODUCT_KEY_1_RESOURCE_PATH = "product-key-1.json"; + public static final String PRODUCT_KEY_SPECIAL_CHARS_RESOURCE_PATH = + "product-key-with-special-character.json"; + public static final String PRODUCT_KEY_1_CHANGED_RESOURCE_PATH = "product-key-1-changed.json"; + public static final String PRODUCT_KEY_1_CHANGED_ATTRIBUTES_RESOURCE_PATH = + "product-key-1-changed-attributes.json"; + public static final String PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH = + "product-key-1-with-prices.json"; + public static final String PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH = + "product-key-1-changed-with-prices.json"; + public static final String PRODUCT_KEY_2_RESOURCE_PATH = "product-key-2.json"; + public static final String PRODUCT_WITH_VARS_RESOURCE_PATH = "product-with-variants.json"; + public static final String PRODUCT_NO_VARS_RESOURCE_PATH = "product-with-no-variants.json"; + public static final String PRODUCT_TYPE_RESOURCE_PATH = "product-type.json"; + public static final String PRODUCT_TYPE_WITH_REFERENCES_RESOURCE_PATH = + "product-type-with-references.json"; + public static final String PRODUCT_TYPE_NO_KEY_RESOURCE_PATH = "product-type-no-key.json"; + public static final String CATEGORY_KEY_1_RESOURCE_PATH = "category-key-1.json"; + public static final String SIMPLE_PRODUCT_WITH_MASTER_VARIANT_RESOURCE_PATH = + "simple-product-with-master-variant.json"; + public static final String SIMPLE_PRODUCT_WITH_MULTIPLE_VARIANTS_RESOURCE_PATH = + "simple-product-with-multiple-variants.json"; + + /** + * Unfortunately, + * Category Order Hints in CTP platform is quite picky: it requires number values as a + * string and only without trailing zeros and only in fixed point format. + * + * @see + * http://dev.commercetools.com/http-api-projects-products.html#category-order-hints + */ + private static final DecimalFormat ORDER_HINT_FORMAT; + + static { + ORDER_HINT_FORMAT = new DecimalFormat(); + ORDER_HINT_FORMAT.setMaximumFractionDigits(Integer.MAX_VALUE); + ORDER_HINT_FORMAT.setMaximumIntegerDigits(1); + DecimalFormatSymbols formatSymbols = new DecimalFormatSymbols(Locale.ENGLISH); + formatSymbols.setDecimalSeparator('.'); + formatSymbols.setGroupingSeparator('.'); + ORDER_HINT_FORMAT.setDecimalFormatSymbols(formatSymbols); + } + + /** + * Builds a {@link ProductDraftBuilder} based on the staged projection of the product JSON + * resource located at the {@code jsonResourcePath} and based on the supplied {@code productType}. + * TODO: GITHUB ISSUE#152 + * + * @param jsonResourcePath the path of the JSON resource to build the product draft from. + * @param productTypeReference the reference of the product type that the product draft belongs + * to. + * @return a {@link ProductDraftBuilder} instance containing the data from the current projection + * of the specified JSON resource and the product type. + */ + public static ProductDraftBuilder createProductDraftBuilder( + @Nonnull final String jsonResourcePath, + @Nonnull final ResourceIdentifiable productTypeReference) { + final Product productFromJson = readObjectFromResource(jsonResourcePath, Product.class); + final ProductData productData = productFromJson.getMasterData().getStaged(); + + @SuppressWarnings("ConstantConditions") + final List allVariants = + productData.getAllVariants().stream() .map(productVariant -> ProductVariantDraftBuilder.of(productVariant).build()) .collect(toList()); - return ProductDraftBuilder - .of(productTypeReference, productData.getName(), productData.getSlug(), allVariants) - .metaDescription(productData.getMetaDescription()) - .metaKeywords(productData.getMetaKeywords()) - .metaTitle(productData.getMetaTitle()) - .description(productData.getDescription()) - .searchKeywords(productData.getSearchKeywords()) - .taxCategory(productFromJson.getTaxCategory()) - .state(productFromJson.getState()) - .key(productFromJson.getKey()) - .categories( - productData.getCategories().stream().map(Reference::toResourceIdentifier).collect(Collectors.toSet())) - .categoryOrderHints(productData.getCategoryOrderHints()) - .publish(productFromJson.getMasterData().isPublished()); - } - - /** - * Given a {@link Set} of {@link Category} {@link ResourceIdentifier}, this method returns an instance of - * {@link CategoryOrderHints} containing a {@link Map}, in which each entry has category id from the supplied - * {@link Set} as a key and a random categoryOrderHint which is a {@link String} containing a random double value - * between 0 and 1 (exclusive). - * - *

Note: The random double value is generated by the {@link ThreadLocalRandom#current()} nextDouble method. - * - * @param categoryResourceIdentifiers set of resource identifiers of categories to build categoryOrderHints for. - * @return an instance of {@link CategoryOrderHints} containing a categoryOrderHint for each category in the - * supplied set of category resource identifiers. - */ - public static CategoryOrderHints createRandomCategoryOrderHints( - @Nonnull final Set> categoryResourceIdentifiers) { - - final List> references = categoryResourceIdentifiers - .stream() - .map(categoryResourceIdentifier -> Category.referenceOfId(categoryResourceIdentifier.getId())) + return ProductDraftBuilder.of( + productTypeReference, productData.getName(), productData.getSlug(), allVariants) + .metaDescription(productData.getMetaDescription()) + .metaKeywords(productData.getMetaKeywords()) + .metaTitle(productData.getMetaTitle()) + .description(productData.getDescription()) + .searchKeywords(productData.getSearchKeywords()) + .taxCategory(productFromJson.getTaxCategory()) + .state(productFromJson.getState()) + .key(productFromJson.getKey()) + .categories( + productData.getCategories().stream() + .map(Reference::toResourceIdentifier) + .collect(Collectors.toSet())) + .categoryOrderHints(productData.getCategoryOrderHints()) + .publish(productFromJson.getMasterData().isPublished()); + } + + /** + * Given a {@link Set} of {@link Category} {@link ResourceIdentifier}, this method returns an + * instance of {@link CategoryOrderHints} containing a {@link Map}, in which each entry has + * category id from the supplied {@link Set} as a key and a random categoryOrderHint which is a + * {@link String} containing a random double value between 0 and 1 (exclusive). + * + *

Note: The random double value is generated by the {@link ThreadLocalRandom#current()} + * nextDouble method. + * + * @param categoryResourceIdentifiers set of resource identifiers of categories to build + * categoryOrderHints for. + * @return an instance of {@link CategoryOrderHints} containing a categoryOrderHint for each + * category in the supplied set of category resource identifiers. + */ + public static CategoryOrderHints createRandomCategoryOrderHints( + @Nonnull final Set> categoryResourceIdentifiers) { + + final List> references = + categoryResourceIdentifiers.stream() + .map( + categoryResourceIdentifier -> + Category.referenceOfId(categoryResourceIdentifier.getId())) .collect(toList()); - return createRandomCategoryOrderHints(references); - } - - /** - * Given a {@link List} of {@link Category}, this method returns an instance of {@link CategoryOrderHints} - * containing a {@link Map}, in which each entry has category id from the supplied {@link List} as a key and a - * random categoryOrderHint which is a {@link String} containing a random double value between 0 and 1 (exclusive). - * - *

Note: The random double value is generated by the {@link ThreadLocalRandom#current()} nextDouble method. - * - * @param categoryResources list of references of categories to build categoryOrderHints for. - * @return an instance of {@link CategoryOrderHints} containing a categoryOrderHint for each category in the - * supplied list of categories. - */ - public static CategoryOrderHints createRandomCategoryOrderHints( - @Nonnull final List> categoryResources) { - - final Map categoryOrderHints = new HashMap<>(); - categoryResources.forEach(resourceIdentifier -> { - final double randomDouble = ThreadLocalRandom.current().nextDouble(1e-8, 1); - categoryOrderHints.put(resourceIdentifier.getId(), ORDER_HINT_FORMAT.format(randomDouble)); + return createRandomCategoryOrderHints(references); + } + + /** + * Given a {@link List} of {@link Category}, this method returns an instance of {@link + * CategoryOrderHints} containing a {@link Map}, in which each entry has category id from the + * supplied {@link List} as a key and a random categoryOrderHint which is a {@link String} + * containing a random double value between 0 and 1 (exclusive). + * + *

Note: The random double value is generated by the {@link ThreadLocalRandom#current()} + * nextDouble method. + * + * @param categoryResources list of references of categories to build categoryOrderHints for. + * @return an instance of {@link CategoryOrderHints} containing a categoryOrderHint for each + * category in the supplied list of categories. + */ + public static CategoryOrderHints createRandomCategoryOrderHints( + @Nonnull final List> categoryResources) { + + final Map categoryOrderHints = new HashMap<>(); + categoryResources.forEach( + resourceIdentifier -> { + final double randomDouble = ThreadLocalRandom.current().nextDouble(1e-8, 1); + categoryOrderHints.put( + resourceIdentifier.getId(), ORDER_HINT_FORMAT.format(randomDouble)); }); - return CategoryOrderHints.of(categoryOrderHints); - } - - public static ProductDraft createProductDraft(@Nonnull final String jsonResourcePath, - @Nonnull final ResourceIdentifiable productTypeReference, - @Nullable final ResourceIdentifier taxCategoryReference, - @Nullable final ResourceIdentifier stateReference, - @Nonnull final Set> - categoryResourceIdentifiers, - @Nullable final CategoryOrderHints categoryOrderHints) { - return createProductDraftBuilder(jsonResourcePath, productTypeReference) - .taxCategory(taxCategoryReference) - .state(stateReference) - .categories(categoryResourceIdentifiers) - .categoryOrderHints(categoryOrderHints) - .build(); - } - - /** - * Builds a {@link ProductDraft} based on the current projection of the product JSON resource located at the - * {@code jsonResourcePath} and based on the supplied {@code productType}, {@code taxCategoryReference} and - * {@code stateReference}. The method also attaches the created {@link ProductDraft} to all the {@code categories} - * specified and assigns {@code categoryOrderHints} for it for each category assigned. - * TODO: GITHUB ISSUE#152 - * - * @param jsonResourcePath the path of the JSON resource to build the product draft from. - * @param productTypeReference the reference of the product type that the product draft belongs to. - * @param categoryReferences the references to the categories to attach this product draft to. - * @param categoryOrderHints the categoryOrderHint for each category this product belongs to. - * @return a {@link ProductDraft} instance containing the data from the current projection of the specified - * JSON resource and the product type. The draft would be assigned also to the specified {@code categories} - * with the supplied {@code categoryOrderHints}. - */ - public static ProductDraft createProductDraft(@Nonnull final String jsonResourcePath, - @Nonnull final Reference productTypeReference, - @Nullable final Reference taxCategoryReference, - @Nullable final Reference stateReference, - @Nonnull final List> categoryReferences, - @Nullable final CategoryOrderHints categoryOrderHints) { - return createProductDraftBuilder(jsonResourcePath, productTypeReference) - .taxCategory(taxCategoryReference) - .state(stateReference) - .categories(categoryReferences) - .categoryOrderHints(categoryOrderHints) - .build(); - } - - public static Product createProductFromJson(@Nonnull final String jsonResourcePath) { - return readObjectFromResource(jsonResourcePath, Product.class); - } - - public static ProductDraft createProductDraftFromJson(@Nonnull final String jsonResourcePath) { - return readObjectFromResource(jsonResourcePath, ProductDraft.class); - } - - - /** - * Creates a mock {@link ProductTypeService} that returns a completed {@link CompletableFuture} containing an - * {@link Optional} containing the id of the supplied value whenever the following method is called on the service: - *

    - *
  • {@link ProductTypeService#fetchCachedProductTypeId(String)}
  • - *
- * - * @return the created mock of the {@link ProductTypeService}. - */ - public static ProductTypeService getMockProductTypeService(@Nonnull final String id) { - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); - return productTypeService; - } - - /** - * Creates a mock {@link TaxCategoryService} that returns a completed {@link CompletableFuture} containing an - * {@link Optional} containing the id of the supplied value whenever the following method is called on the service: - *
    - *
  • {@link TaxCategoryService#fetchCachedTaxCategoryId(String)}
  • - *
- * - * @return the created mock of the {@link TaxCategoryService}. - */ - public static TaxCategoryService getMockTaxCategoryService(@Nonnull final String id) { - final TaxCategoryService taxCategoryService = mock(TaxCategoryService.class); - when(taxCategoryService.fetchCachedTaxCategoryId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); - return taxCategoryService; - } - - /** - * Creates a mock {@link StateService} that returns a completed {@link CompletableFuture} containing an - * {@link Optional} containing the id of the supplied value whenever the following method is called on the service: - *
    - *
  • {@link StateService#fetchCachedStateId(String)}
  • - *
- * - * @return the created mock of the {@link StateService}. - */ - public static StateService getMockStateService(@Nonnull final String id) { - final StateService stateService = mock(StateService.class); - when(stateService.fetchCachedStateId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); - return stateService; - } - - /** - * Creates a mock {@link CustomerGroup} with the supplied {@code id} and {@code key}. - * - * @param id the id of the created mock {@link CustomerGroup}. - * @param key the key of the created mock {@link CustomerGroup}. - * @return a mock customerGroup with the supplied id and key. - */ - public static CustomerGroup getMockCustomerGroup(final String id, final String key) { - final CustomerGroup customerGroup = mock(CustomerGroup.class); - when(customerGroup.getId()).thenReturn(id); - when(customerGroup.getKey()).thenReturn(key); - return customerGroup; - } - - /** - * Creates a mock {@link CustomerGroupService} that returns a completed {@link CompletableFuture} containing an - * {@link Optional} containing the id of the supplied value whenever the following method is called on the service: - *
    - *
  • {@link CustomerGroupService#fetchCachedCustomerGroupId(String)}
  • - *
- * - * @return the created mock of the {@link CustomerGroupService}. - */ - public static CustomerGroupService getMockCustomerGroupService(@Nonnull final CustomerGroup customerGroup) { - final String customerGroupId = customerGroup.getId(); - - final CustomerGroupService customerGroupService = mock(CustomerGroupService.class); - when(customerGroupService.fetchCachedCustomerGroupId(anyString())) - .thenReturn(completedFuture(Optional.of(customerGroupId))); - return customerGroupService; - } - - /** - * Creates a mock {@link ProductService} that returns a completed {@link CompletableFuture} containing an - * {@link Optional} containing the id of the supplied value whenever the following method is called on the service: - *
    - *
  • {@link ProductService#getIdFromCacheOrFetch(String)}
  • - *
- * - * @return the created mock of the {@link ProductService}. - */ - public static ProductService getMockProductService(@Nonnull final String id) { - final ProductService productService = mock(ProductService.class); - when(productService.getIdFromCacheOrFetch(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); - return productService; - } - - /** - * Creates a mock {@link CategoryService} that returns a completed {@link CompletableFuture} containing an - * {@link Optional} containing the id of the supplied value whenever the following method is called on the service: - *
    - *
  • {@link CategoryService#fetchCachedCategoryId(String)}
  • - *
- * - * @return the created mock of the {@link CategoryService}. - */ - public static CategoryService getMockCategoryService(@Nonnull final String id) { - final CategoryService categoryService = mock(CategoryService.class); - when(categoryService.fetchCachedCategoryId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); - return categoryService; - } - - /** - * Creates a mock {@link Price} with the supplied {@link Channel} {@link Reference}, - * {@link CustomerGroup} {@link Reference}, and custom {@link Type} {@link Reference}. - * - *

If the supplied {@code customTypeReference} is {@code null}, no custom fields are stubbed on the - * resulting price mock. - * - * @param channelReference the channel reference to attach on the mock {@link Price}. - * @param customTypeReference the custom type reference to attach on the mock {@link Price}. - * @param customerGroupReference the custom type reference to attach on the mock {@link Price}. - * @return a mock price with the supplied references. - */ - @Nonnull - public static Price getPriceMockWithReferences(@Nullable final Reference channelReference, - @Nullable final Reference customTypeReference, - @Nullable final Reference customerGroupReference) { - final Price price = mock(Price.class); - when(price.getChannel()).thenReturn(channelReference); - when(price.getCustomerGroup()).thenReturn(customerGroupReference); - - return ofNullable(customTypeReference) - .map(typeReference -> { - // If type reference is supplied, mock Custom with expanded type reference. - final CustomFields mockCustomFields = mock(CustomFields.class); - when(mockCustomFields.getType()).thenReturn(customTypeReference); - when(price.getCustom()).thenReturn(mockCustomFields); - return price; + return CategoryOrderHints.of(categoryOrderHints); + } + + public static ProductDraft createProductDraft( + @Nonnull final String jsonResourcePath, + @Nonnull final ResourceIdentifiable productTypeReference, + @Nullable final ResourceIdentifier taxCategoryReference, + @Nullable final ResourceIdentifier stateReference, + @Nonnull final Set> categoryResourceIdentifiers, + @Nullable final CategoryOrderHints categoryOrderHints) { + return createProductDraftBuilder(jsonResourcePath, productTypeReference) + .taxCategory(taxCategoryReference) + .state(stateReference) + .categories(categoryResourceIdentifiers) + .categoryOrderHints(categoryOrderHints) + .build(); + } + + /** + * Builds a {@link ProductDraft} based on the current projection of the product JSON resource + * located at the {@code jsonResourcePath} and based on the supplied {@code productType}, {@code + * taxCategoryReference} and {@code stateReference}. The method also attaches the created {@link + * ProductDraft} to all the {@code categories} specified and assigns {@code categoryOrderHints} + * for it for each category assigned. TODO: GITHUB ISSUE#152 + * + * @param jsonResourcePath the path of the JSON resource to build the product draft from. + * @param productTypeReference the reference of the product type that the product draft belongs + * to. + * @param categoryReferences the references to the categories to attach this product draft to. + * @param categoryOrderHints the categoryOrderHint for each category this product belongs to. + * @return a {@link ProductDraft} instance containing the data from the current projection of the + * specified JSON resource and the product type. The draft would be assigned also to the + * specified {@code categories} with the supplied {@code categoryOrderHints}. + */ + public static ProductDraft createProductDraft( + @Nonnull final String jsonResourcePath, + @Nonnull final Reference productTypeReference, + @Nullable final Reference taxCategoryReference, + @Nullable final Reference stateReference, + @Nonnull final List> categoryReferences, + @Nullable final CategoryOrderHints categoryOrderHints) { + return createProductDraftBuilder(jsonResourcePath, productTypeReference) + .taxCategory(taxCategoryReference) + .state(stateReference) + .categories(categoryReferences) + .categoryOrderHints(categoryOrderHints) + .build(); + } + + public static Product createProductFromJson(@Nonnull final String jsonResourcePath) { + return readObjectFromResource(jsonResourcePath, Product.class); + } + + public static ProductDraft createProductDraftFromJson(@Nonnull final String jsonResourcePath) { + return readObjectFromResource(jsonResourcePath, ProductDraft.class); + } + + /** + * Creates a mock {@link ProductTypeService} that returns a completed {@link CompletableFuture} + * containing an {@link Optional} containing the id of the supplied value whenever the following + * method is called on the service: + * + *

    + *
  • {@link ProductTypeService#fetchCachedProductTypeId(String)} + *
+ * + * @return the created mock of the {@link ProductTypeService}. + */ + public static ProductTypeService getMockProductTypeService(@Nonnull final String id) { + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); + return productTypeService; + } + + /** + * Creates a mock {@link TaxCategoryService} that returns a completed {@link CompletableFuture} + * containing an {@link Optional} containing the id of the supplied value whenever the following + * method is called on the service: + * + *
    + *
  • {@link TaxCategoryService#fetchCachedTaxCategoryId(String)} + *
+ * + * @return the created mock of the {@link TaxCategoryService}. + */ + public static TaxCategoryService getMockTaxCategoryService(@Nonnull final String id) { + final TaxCategoryService taxCategoryService = mock(TaxCategoryService.class); + when(taxCategoryService.fetchCachedTaxCategoryId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); + return taxCategoryService; + } + + /** + * Creates a mock {@link StateService} that returns a completed {@link CompletableFuture} + * containing an {@link Optional} containing the id of the supplied value whenever the following + * method is called on the service: + * + *
    + *
  • {@link StateService#fetchCachedStateId(String)} + *
+ * + * @return the created mock of the {@link StateService}. + */ + public static StateService getMockStateService(@Nonnull final String id) { + final StateService stateService = mock(StateService.class); + when(stateService.fetchCachedStateId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); + return stateService; + } + + /** + * Creates a mock {@link CustomerGroup} with the supplied {@code id} and {@code key}. + * + * @param id the id of the created mock {@link CustomerGroup}. + * @param key the key of the created mock {@link CustomerGroup}. + * @return a mock customerGroup with the supplied id and key. + */ + public static CustomerGroup getMockCustomerGroup(final String id, final String key) { + final CustomerGroup customerGroup = mock(CustomerGroup.class); + when(customerGroup.getId()).thenReturn(id); + when(customerGroup.getKey()).thenReturn(key); + return customerGroup; + } + + /** + * Creates a mock {@link CustomerGroupService} that returns a completed {@link CompletableFuture} + * containing an {@link Optional} containing the id of the supplied value whenever the following + * method is called on the service: + * + *
    + *
  • {@link CustomerGroupService#fetchCachedCustomerGroupId(String)} + *
+ * + * @return the created mock of the {@link CustomerGroupService}. + */ + public static CustomerGroupService getMockCustomerGroupService( + @Nonnull final CustomerGroup customerGroup) { + final String customerGroupId = customerGroup.getId(); + + final CustomerGroupService customerGroupService = mock(CustomerGroupService.class); + when(customerGroupService.fetchCachedCustomerGroupId(anyString())) + .thenReturn(completedFuture(Optional.of(customerGroupId))); + return customerGroupService; + } + + /** + * Creates a mock {@link ProductService} that returns a completed {@link CompletableFuture} + * containing an {@link Optional} containing the id of the supplied value whenever the following + * method is called on the service: + * + *
    + *
  • {@link ProductService#getIdFromCacheOrFetch(String)} + *
+ * + * @return the created mock of the {@link ProductService}. + */ + public static ProductService getMockProductService(@Nonnull final String id) { + final ProductService productService = mock(ProductService.class); + when(productService.getIdFromCacheOrFetch(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); + return productService; + } + + /** + * Creates a mock {@link CategoryService} that returns a completed {@link CompletableFuture} + * containing an {@link Optional} containing the id of the supplied value whenever the following + * method is called on the service: + * + *
    + *
  • {@link CategoryService#fetchCachedCategoryId(String)} + *
+ * + * @return the created mock of the {@link CategoryService}. + */ + public static CategoryService getMockCategoryService(@Nonnull final String id) { + final CategoryService categoryService = mock(CategoryService.class); + when(categoryService.fetchCachedCategoryId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); + return categoryService; + } + + /** + * Creates a mock {@link Price} with the supplied {@link Channel} {@link Reference}, {@link + * CustomerGroup} {@link Reference}, and custom {@link Type} {@link Reference}. + * + *

If the supplied {@code customTypeReference} is {@code null}, no custom fields are stubbed on + * the resulting price mock. + * + * @param channelReference the channel reference to attach on the mock {@link Price}. + * @param customTypeReference the custom type reference to attach on the mock {@link Price}. + * @param customerGroupReference the custom type reference to attach on the mock {@link Price}. + * @return a mock price with the supplied references. + */ + @Nonnull + public static Price getPriceMockWithReferences( + @Nullable final Reference channelReference, + @Nullable final Reference customTypeReference, + @Nullable final Reference customerGroupReference) { + final Price price = mock(Price.class); + when(price.getChannel()).thenReturn(channelReference); + when(price.getCustomerGroup()).thenReturn(customerGroupReference); + + return ofNullable(customTypeReference) + .map( + typeReference -> { + // If type reference is supplied, mock Custom with expanded type reference. + final CustomFields mockCustomFields = mock(CustomFields.class); + when(mockCustomFields.getType()).thenReturn(customTypeReference); + when(price.getCustom()).thenReturn(mockCustomFields); + return price; }) - .orElse(price); - } - - /** - * Creates a mock {@link ProductVariant} with the supplied {@link Price} {@link List}. - * - * @param prices the prices to attach on the mock {@link ProductVariant}. - * @return a mock product variant with the supplied prices. - */ - @Nonnull - public static ProductVariant getProductVariantMock(@Nonnull final List prices) { - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getPrices()).thenReturn(prices); - return productVariant; - } - - /** - * Creates a mock {@link ProductVariant} with the supplied {@link Price} and {@link Asset} {@link List}. - * - * @param prices the prices to attach on the mock {@link ProductVariant}. - * @param assets the assets to attach on the mock {@link ProductVariant}. - * @return a mock product variant with the supplied prices and assets. - */ - @Nonnull - public static ProductVariant getProductVariantMock(@Nonnull final List prices, - @Nonnull final List assets) { - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getPrices()).thenReturn(prices); - when(productVariant.getAssets()).thenReturn(assets); - return productVariant; - } - - /** - * Creates a mock {@link Channel} with the supplied {@code key}.. - * - * @param key the key to to set on the mock {@link Channel}. - * @return a mock channel with the supplied key. - */ - @Nonnull - public static Channel getChannelMock(@Nonnull final String key) { - final Channel channel = mock(Channel.class); - when(channel.getKey()).thenReturn(key); - when(channel.getId()).thenReturn(UUID.randomUUID().toString()); - return channel; - } - - /** - * Creates an {@link AttributeDraft} with the supplied {@code attributeName} and {@code references}. - * - * @param attributeName the name to set on the {@link AttributeDraft}. - * @param references the references to set on the {@link AttributeDraft}. - * @return an {@link AttributeDraft} with the supplied {@code attributeName} and {@code references}. - */ - @Nonnull - public static AttributeDraft getReferenceSetAttributeDraft(@Nonnull final String attributeName, - @Nonnull final ObjectNode... references) { - final ArrayNode referenceSet = JsonNodeFactory.instance.arrayNode(); - referenceSet.addAll(Arrays.asList(references)); - return AttributeDraft.of(attributeName, referenceSet); - } - - /** - * Creates an {@link ObjectNode} that represents a product reference with a random uuid in the id field. - * - * @return an {@link ObjectNode} that represents a product reference with a random uuid in the id field. - */ - @Nonnull - public static ObjectNode getProductReferenceWithRandomId() { - final ObjectNode productReference = JsonNodeFactory.instance.objectNode(); - productReference.put("typeId", "product"); - productReference.put("id", UUID.randomUUID().toString()); - return productReference; - } - - /** - * Creates an {@link ObjectNode} that represents a product reference with the supplied {@code id} in the id field. - * - * @return an {@link ObjectNode} that represents a product reference with the supplied {@code id} in the id field. - */ - @Nonnull - public static ObjectNode getProductReferenceWithId(@Nonnull final String id) { - return createReferenceObject(id, Product.referenceTypeId()); - } - - /** - * Creates an {@link ObjectNode} that represents a reference with the supplied {@code id} in the id field and - * {@code typeId} field in the typeId field. - * - * @return an {@link ObjectNode} that represents a product reference with the supplied {@code id} in the id field - * and {@code typeId} field in the typeId field. - */ - @Nonnull - public static ObjectNode createReferenceObject(@Nonnull final String id, @Nonnull final String typeId) { - final ObjectNode reference = JsonNodeFactory.instance.objectNode(); - reference.put(REFERENCE_TYPE_ID_FIELD, typeId); - reference.put(REFERENCE_ID_FIELD, id); - return reference; - } - - - @Nonnull - public static ProductDraftBuilder getBuilderWithProductTypeRefKey(@Nullable final String refKey) { - return ProductDraftBuilder.of(ResourceIdentifier.ofKey(refKey), - LocalizedString.ofEnglish("testName"), - LocalizedString.ofEnglish("testSlug"), - (ProductVariantDraft) null); - } - - @Nonnull - public static ProductDraftBuilder getBuilderWithRandomProductType() { - return getBuilderWithProductTypeRefKey("anyKey"); - } - - /** - * Creates a mock {@link CustomObjectService} that returns a completed {@link CompletableFuture} containing an - * {@link Optional} containing the id of the supplied value whenever the following method is called on the service: - *

    - *
  • {@link CustomObjectService#fetchCachedCustomObjectId}
  • - *
- * - * @return the created mock of the {@link CustomObjectService}. - */ - public static CustomObjectService getMockCustomObjectService(@Nonnull final String id) { - final CustomObjectService customObjectService = mock(CustomObjectService.class); - when(customObjectService.fetchCachedCustomObjectId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); - return customObjectService; - } + .orElse(price); + } + + /** + * Creates a mock {@link ProductVariant} with the supplied {@link Price} {@link List}. + * + * @param prices the prices to attach on the mock {@link ProductVariant}. + * @return a mock product variant with the supplied prices. + */ + @Nonnull + public static ProductVariant getProductVariantMock(@Nonnull final List prices) { + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getPrices()).thenReturn(prices); + return productVariant; + } + + /** + * Creates a mock {@link ProductVariant} with the supplied {@link Price} and {@link Asset} {@link + * List}. + * + * @param prices the prices to attach on the mock {@link ProductVariant}. + * @param assets the assets to attach on the mock {@link ProductVariant}. + * @return a mock product variant with the supplied prices and assets. + */ + @Nonnull + public static ProductVariant getProductVariantMock( + @Nonnull final List prices, @Nonnull final List assets) { + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getPrices()).thenReturn(prices); + when(productVariant.getAssets()).thenReturn(assets); + return productVariant; + } + + /** + * Creates a mock {@link Channel} with the supplied {@code key}.. + * + * @param key the key to to set on the mock {@link Channel}. + * @return a mock channel with the supplied key. + */ + @Nonnull + public static Channel getChannelMock(@Nonnull final String key) { + final Channel channel = mock(Channel.class); + when(channel.getKey()).thenReturn(key); + when(channel.getId()).thenReturn(UUID.randomUUID().toString()); + return channel; + } + + /** + * Creates an {@link AttributeDraft} with the supplied {@code attributeName} and {@code + * references}. + * + * @param attributeName the name to set on the {@link AttributeDraft}. + * @param references the references to set on the {@link AttributeDraft}. + * @return an {@link AttributeDraft} with the supplied {@code attributeName} and {@code + * references}. + */ + @Nonnull + public static AttributeDraft getReferenceSetAttributeDraft( + @Nonnull final String attributeName, @Nonnull final ObjectNode... references) { + final ArrayNode referenceSet = JsonNodeFactory.instance.arrayNode(); + referenceSet.addAll(Arrays.asList(references)); + return AttributeDraft.of(attributeName, referenceSet); + } + + /** + * Creates an {@link ObjectNode} that represents a product reference with a random uuid in the id + * field. + * + * @return an {@link ObjectNode} that represents a product reference with a random uuid in the id + * field. + */ + @Nonnull + public static ObjectNode getProductReferenceWithRandomId() { + final ObjectNode productReference = JsonNodeFactory.instance.objectNode(); + productReference.put("typeId", "product"); + productReference.put("id", UUID.randomUUID().toString()); + return productReference; + } + + /** + * Creates an {@link ObjectNode} that represents a product reference with the supplied {@code id} + * in the id field. + * + * @return an {@link ObjectNode} that represents a product reference with the supplied {@code id} + * in the id field. + */ + @Nonnull + public static ObjectNode getProductReferenceWithId(@Nonnull final String id) { + return createReferenceObject(id, Product.referenceTypeId()); + } + + /** + * Creates an {@link ObjectNode} that represents a reference with the supplied {@code id} in the + * id field and {@code typeId} field in the typeId field. + * + * @return an {@link ObjectNode} that represents a product reference with the supplied {@code id} + * in the id field and {@code typeId} field in the typeId field. + */ + @Nonnull + public static ObjectNode createReferenceObject( + @Nonnull final String id, @Nonnull final String typeId) { + final ObjectNode reference = JsonNodeFactory.instance.objectNode(); + reference.put(REFERENCE_TYPE_ID_FIELD, typeId); + reference.put(REFERENCE_ID_FIELD, id); + return reference; + } + + @Nonnull + public static ProductDraftBuilder getBuilderWithProductTypeRefKey(@Nullable final String refKey) { + return ProductDraftBuilder.of( + ResourceIdentifier.ofKey(refKey), + LocalizedString.ofEnglish("testName"), + LocalizedString.ofEnglish("testSlug"), + (ProductVariantDraft) null); + } + + @Nonnull + public static ProductDraftBuilder getBuilderWithRandomProductType() { + return getBuilderWithProductTypeRefKey("anyKey"); + } + + /** + * Creates a mock {@link CustomObjectService} that returns a completed {@link CompletableFuture} + * containing an {@link Optional} containing the id of the supplied value whenever the following + * method is called on the service: + * + *
    + *
  • {@link CustomObjectService#fetchCachedCustomObjectId} + *
+ * + * @return the created mock of the {@link CustomObjectService}. + */ + public static CustomObjectService getMockCustomObjectService(@Nonnull final String id) { + final CustomObjectService customObjectService = mock(CustomObjectService.class); + when(customObjectService.fetchCachedCustomObjectId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of(id))); + return customObjectService; + } } diff --git a/src/test/java/com/commercetools/sync/products/ProductSyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/products/ProductSyncOptionsBuilderTest.java index a6852574a8..b1086cb143 100644 --- a/src/test/java/com/commercetools/sync/products/ProductSyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/products/ProductSyncOptionsBuilderTest.java @@ -1,5 +1,16 @@ package com.commercetools.sync.products; +import static com.commercetools.sync.products.ActionGroup.IMAGES; +import static com.commercetools.sync.products.SyncFilter.ofWhiteList; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -10,281 +21,267 @@ import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.ProductDraftBuilder; import io.sphere.sdk.products.commands.updateactions.ChangeName; -import org.junit.jupiter.api.Test; - import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static com.commercetools.sync.products.ActionGroup.IMAGES; -import static com.commercetools.sync.products.SyncFilter.ofWhiteList; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductSyncOptionsBuilderTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private ProductSyncOptionsBuilder productSyncOptionsBuilder = ProductSyncOptionsBuilder.of(CTP_CLIENT); - - @Test - void of_WithClient_ShouldCreateProductSyncOptionsBuilder() { - final ProductSyncOptionsBuilder builder = ProductSyncOptionsBuilder.of(CTP_CLIENT); - assertThat(builder).isNotNull(); - } - - @Test - void build_WithClient_ShouldBuildProductSyncOptions() { - final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); - assertThat(productSyncOptions).isNotNull(); - assertThat(productSyncOptions.getSyncFilter()).isNotNull(); - assertThat(productSyncOptions.getSyncFilter()).isSameAs(SyncFilter.of()); - assertThat(productSyncOptions.getBeforeUpdateCallback()).isNull(); - assertThat(productSyncOptions.getBeforeCreateCallback()).isNull(); - assertThat(productSyncOptions.getErrorCallback()).isNull(); - assertThat(productSyncOptions.getWarningCallback()).isNull(); - assertThat(productSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); - assertThat(productSyncOptions.getBatchSize()).isEqualTo(ProductSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(productSyncOptions.getCacheSize()).isEqualTo(10_000); - } - - @Test - void syncFilter_WithNoSyncFilter_ShouldSetDefaultFilter() { - final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); - assertThat(productSyncOptions.getSyncFilter()).isNotNull(); - assertThat(productSyncOptions.getSyncFilter()).isSameAs(SyncFilter.of()); - } - - @Test - void syncFilter_WithSyncFilter_ShouldSetFilter() { - productSyncOptionsBuilder.syncFilter(ofWhiteList(IMAGES)); - - final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); - assertThat(productSyncOptions.getSyncFilter()).isNotNull(); - } - - @Test - void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { - final TriFunction>, ProductDraft, Product, List>> - beforeUpdateCallback = (updateActions, newProduct, oldProduct) -> emptyList(); - productSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - - final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); - assertThat(productSyncOptions.getBeforeUpdateCallback()).isNotNull(); - } - - @Test - void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { - productSyncOptionsBuilder.beforeCreateCallback((newProduct) -> null); - - final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); - assertThat(productSyncOptions.getBeforeCreateCallback()).isNotNull(); - } - - @Test - public void errorCallBack_WithCallBack_ShouldSetCallBack() { - final QuadConsumer, Optional, - List>> mockErrorCallback = (exception, newDraft, old, actions) -> { - }; - productSyncOptionsBuilder.errorCallback(mockErrorCallback); - - final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); - assertThat(productSyncOptions.getErrorCallback()).isNotNull(); - } - - @Test - public void warningCallBack_WithCallBack_ShouldSetCallBack() { - final TriConsumer, Optional> mockWarningCallBack = - (exception, newDraft, old) -> { }; - productSyncOptionsBuilder.warningCallback(mockWarningCallBack); - - final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); - assertThat(productSyncOptions.getWarningCallback()).isNotNull(); - } - - @Test - void getThis_ShouldReturnCorrectInstance() { - final ProductSyncOptionsBuilder instance = productSyncOptionsBuilder.getThis(); - assertThat(instance).isNotNull(); - assertThat(instance).isInstanceOf(ProductSyncOptionsBuilder.class); - assertThat(instance).isEqualTo(productSyncOptionsBuilder); - } - - @Test - void productSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder - .of(CTP_CLIENT) + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private ProductSyncOptionsBuilder productSyncOptionsBuilder = + ProductSyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateProductSyncOptionsBuilder() { + final ProductSyncOptionsBuilder builder = ProductSyncOptionsBuilder.of(CTP_CLIENT); + assertThat(builder).isNotNull(); + } + + @Test + void build_WithClient_ShouldBuildProductSyncOptions() { + final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); + assertThat(productSyncOptions).isNotNull(); + assertThat(productSyncOptions.getSyncFilter()).isNotNull(); + assertThat(productSyncOptions.getSyncFilter()).isSameAs(SyncFilter.of()); + assertThat(productSyncOptions.getBeforeUpdateCallback()).isNull(); + assertThat(productSyncOptions.getBeforeCreateCallback()).isNull(); + assertThat(productSyncOptions.getErrorCallback()).isNull(); + assertThat(productSyncOptions.getWarningCallback()).isNull(); + assertThat(productSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); + assertThat(productSyncOptions.getBatchSize()) + .isEqualTo(ProductSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + assertThat(productSyncOptions.getCacheSize()).isEqualTo(10_000); + } + + @Test + void syncFilter_WithNoSyncFilter_ShouldSetDefaultFilter() { + final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); + assertThat(productSyncOptions.getSyncFilter()).isNotNull(); + assertThat(productSyncOptions.getSyncFilter()).isSameAs(SyncFilter.of()); + } + + @Test + void syncFilter_WithSyncFilter_ShouldSetFilter() { + productSyncOptionsBuilder.syncFilter(ofWhiteList(IMAGES)); + + final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); + assertThat(productSyncOptions.getSyncFilter()).isNotNull(); + } + + @Test + void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { + final TriFunction< + List>, ProductDraft, Product, List>> + beforeUpdateCallback = (updateActions, newProduct, oldProduct) -> emptyList(); + productSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); + + final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); + assertThat(productSyncOptions.getBeforeUpdateCallback()).isNotNull(); + } + + @Test + void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { + productSyncOptionsBuilder.beforeCreateCallback((newProduct) -> null); + + final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); + assertThat(productSyncOptions.getBeforeCreateCallback()).isNotNull(); + } + + @Test + public void errorCallBack_WithCallBack_ShouldSetCallBack() { + final QuadConsumer< + SyncException, Optional, Optional, List>> + mockErrorCallback = (exception, newDraft, old, actions) -> {}; + productSyncOptionsBuilder.errorCallback(mockErrorCallback); + + final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); + assertThat(productSyncOptions.getErrorCallback()).isNotNull(); + } + + @Test + public void warningCallBack_WithCallBack_ShouldSetCallBack() { + final TriConsumer, Optional> + mockWarningCallBack = (exception, newDraft, old) -> {}; + productSyncOptionsBuilder.warningCallback(mockWarningCallBack); + + final ProductSyncOptions productSyncOptions = productSyncOptionsBuilder.build(); + assertThat(productSyncOptions.getWarningCallback()).isNotNull(); + } + + @Test + void getThis_ShouldReturnCorrectInstance() { + final ProductSyncOptionsBuilder instance = productSyncOptionsBuilder.getThis(); + assertThat(instance).isNotNull(); + assertThat(instance).isInstanceOf(ProductSyncOptionsBuilder.class); + assertThat(instance).isEqualTo(productSyncOptionsBuilder); + } + + @Test + void productSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT) .batchSize(30) .beforeCreateCallback((newProduct) -> null) .beforeUpdateCallback((updateActions, newCategory, oldCategory) -> emptyList()) .build(); - assertThat(productSyncOptions).isNotNull(); - } - - @Test - void batchSize_WithPositiveValue_ShouldSetBatchSize() { - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(10) - .build(); - assertThat(productSyncOptions.getBatchSize()).isEqualTo(10); - } - - @Test - void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final ProductSyncOptions productSyncOptionsWithZeroBatchSize = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(0) - .build(); - assertThat(productSyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(ProductSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - - final ProductSyncOptions productSyncOptionsWithNegativeBatchSize = ProductSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(-100) - .build(); - assertThat(productSyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(ProductSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } - - @Test - void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .build(); - assertThat(productSyncOptions.getBeforeUpdateCallback()).isNull(); - - final List> updateActions = Collections - .singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = - productSyncOptions.applyBeforeUpdateCallback(updateActions, mock(ProductDraft.class), mock(Product.class)); - assertThat(filteredList).isSameAs(updateActions); - } - - @Test - void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { - final TriFunction>, ProductDraft, Product, List>> - beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(productSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = Collections.singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = - productSyncOptions.applyBeforeUpdateCallback(updateActions, mock(ProductDraft.class), mock(Product.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - private interface MockTriFunction extends - TriFunction>, ProductDraft, Product, List>> { - } - - @Test - void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); - - final ProductSyncOptions productSyncOptions = - ProductSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - assertThat(productSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = emptyList(); - final List> filteredList = - productSyncOptions.applyBeforeUpdateCallback(updateActions, mock(ProductDraft.class), mock(Product.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - @Test - void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { - final TriFunction>, ProductDraft, Product, List>> - beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> emptyList(); - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(productSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = Collections.singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = - productSyncOptions.applyBeforeUpdateCallback(updateActions, mock(ProductDraft.class), mock(Product.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { - final Function draftFunction = - productDraft -> ProductDraftBuilder.of(productDraft) - .key(productDraft.getKey() + "_filteredKey").build(); - - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - assertThat(productSyncOptions.getBeforeCreateCallback()).isNotNull(); - - final ProductDraft resourceDraft = mock(ProductDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); - - - final Optional filteredDraft = productSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isNotEmpty(); - assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filteredKey"); - } - - @Test - void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT).build(); - assertThat(productSyncOptions.getBeforeCreateCallback()).isNull(); - - final ProductDraft resourceDraft = mock(ProductDraft.class); - final Optional filteredDraft = productSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).containsSame(resourceDraft); - } - - @Test - void applyBeforeCreateCallBack_WithNullReturnCallback_ShouldReturnEmptyOptional() { - final Function draftFunction = productDraft -> null; - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - assertThat(productSyncOptions.getBeforeCreateCallback()).isNotNull(); - - final ProductDraft resourceDraft = mock(ProductDraft.class); - final Optional filteredDraft = productSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isEmpty(); - } - - @Test - void cacheSize_WithPositiveValue_ShouldSetCacheSize() { - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(10) - .build(); - assertThat(productSyncOptions.getCacheSize()).isEqualTo(10); - } - - @Test - void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final ProductSyncOptions productSyncOptionsWithZeroCacheSize = ProductSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(0) - .build(); - assertThat(productSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - - final ProductSyncOptions productSyncOptionsWithNegativeCacheSize = ProductSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(-100) - .build(); - assertThat(productSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } + assertThat(productSyncOptions).isNotNull(); + } + + @Test + void batchSize_WithPositiveValue_ShouldSetBatchSize() { + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); + assertThat(productSyncOptions.getBatchSize()).isEqualTo(10); + } + + @Test + void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final ProductSyncOptions productSyncOptionsWithZeroBatchSize = + ProductSyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); + assertThat(productSyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(ProductSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + + final ProductSyncOptions productSyncOptionsWithNegativeBatchSize = + ProductSyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); + assertThat(productSyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(ProductSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + + @Test + void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { + final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(productSyncOptions.getBeforeUpdateCallback()).isNull(); + + final List> updateActions = + Collections.singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + productSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ProductDraft.class), mock(Product.class)); + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { + final TriFunction< + List>, ProductDraft, Product, List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).beforeUpdateCallback(beforeUpdateCallback).build(); + assertThat(productSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + Collections.singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + productSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ProductDraft.class), mock(Product.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + private interface MockTriFunction + extends TriFunction< + List>, ProductDraft, Product, List>> {} + + @Test + void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); + + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).beforeUpdateCallback(beforeUpdateCallback).build(); + + assertThat(productSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = emptyList(); + final List> filteredList = + productSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ProductDraft.class), mock(Product.class)); + + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { + final TriFunction< + List>, ProductDraft, Product, List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> emptyList(); + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).beforeUpdateCallback(beforeUpdateCallback).build(); + assertThat(productSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + Collections.singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + productSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ProductDraft.class), mock(Product.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { + final Function draftFunction = + productDraft -> + ProductDraftBuilder.of(productDraft) + .key(productDraft.getKey() + "_filteredKey") + .build(); + + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(productSyncOptions.getBeforeCreateCallback()).isNotNull(); + + final ProductDraft resourceDraft = mock(ProductDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); + + final Optional filteredDraft = + productSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isNotEmpty(); + assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filteredKey"); + } + + @Test + void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(productSyncOptions.getBeforeCreateCallback()).isNull(); + + final ProductDraft resourceDraft = mock(ProductDraft.class); + final Optional filteredDraft = + productSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).containsSame(resourceDraft); + } + + @Test + void applyBeforeCreateCallBack_WithNullReturnCallback_ShouldReturnEmptyOptional() { + final Function draftFunction = productDraft -> null; + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(productSyncOptions.getBeforeCreateCallback()).isNotNull(); + + final ProductDraft resourceDraft = mock(ProductDraft.class); + final Optional filteredDraft = + productSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isEmpty(); + } + + @Test + void cacheSize_WithPositiveValue_ShouldSetCacheSize() { + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); + assertThat(productSyncOptions.getCacheSize()).isEqualTo(10); + } + + @Test + void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final ProductSyncOptions productSyncOptionsWithZeroCacheSize = + ProductSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); + assertThat(productSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); + + final ProductSyncOptions productSyncOptionsWithNegativeCacheSize = + ProductSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); + assertThat(productSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/products/ProductSyncTest.java b/src/test/java/com/commercetools/sync/products/ProductSyncTest.java index eba47935d6..2517387003 100644 --- a/src/test/java/com/commercetools/sync/products/ProductSyncTest.java +++ b/src/test/java/com/commercetools/sync/products/ProductSyncTest.java @@ -1,5 +1,28 @@ package com.commercetools.sync.products; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_2_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics; import com.commercetools.sync.products.helpers.ProductSyncStatistics; import com.commercetools.sync.services.CategoryService; @@ -20,8 +43,6 @@ import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.ProductDraftBuilder; import io.sphere.sdk.products.queries.ProductQuery; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -29,340 +50,376 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletionException; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_2_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.CompletableFuture.supplyAsync; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductSyncTest { - @Test - void sync_WithNoValidDrafts_ShouldCompleteWithoutAnyProcessing() { - // preparation - final SphereClient ctpClient = mock(SphereClient.class); - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder - .of(ctpClient) - .build(); - - final ProductService productService = mock(ProductService.class); - final ProductSync productSync = new ProductSync(productSyncOptions, productService, - mock(ProductTypeService.class), mock(CategoryService.class), mock(TypeService.class), - mock(ChannelService.class), mock(CustomerGroupService.class), mock(TaxCategoryService.class), + @Test + void sync_WithNoValidDrafts_ShouldCompleteWithoutAnyProcessing() { + // preparation + final SphereClient ctpClient = mock(SphereClient.class); + final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(ctpClient).build(); + + final ProductService productService = mock(ProductService.class); + final ProductSync productSync = + new ProductSync( + productSyncOptions, + productService, + mock(ProductTypeService.class), + mock(CategoryService.class), + mock(TypeService.class), + mock(ChannelService.class), + mock(CustomerGroupService.class), + mock(TaxCategoryService.class), mock(StateService.class), - mock(UnresolvedReferencesService.class), mock(CustomObjectService.class)); + mock(UnresolvedReferencesService.class), + mock(CustomObjectService.class)); - final ProductDraft productDraftWithoutKey = ProductDraftBuilder - .of(ResourceIdentifier.ofKey("productTypeKey"), ofEnglish("name"), ofEnglish("slug"), emptyList()) + final ProductDraft productDraftWithoutKey = + ProductDraftBuilder.of( + ResourceIdentifier.ofKey("productTypeKey"), + ofEnglish("name"), + ofEnglish("slug"), + emptyList()) .build(); - // test - final ProductSyncStatistics statistics = productSync - .sync(singletonList(productDraftWithoutKey)) - .toCompletableFuture() - .join(); - - // assertion - verifyNoMoreInteractions(ctpClient); - verifyNoMoreInteractions(productService); - assertThat(statistics).hasValues(1, 0, 0, 1, 0); - } - - @Test - void sync_WithErrorCachingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey("productTypeKey")) + // test + final ProductSyncStatistics statistics = + productSync.sync(singletonList(productDraftWithoutKey)).toCompletableFuture().join(); + + // assertion + verifyNoMoreInteractions(ctpClient); + verifyNoMoreInteractions(productService); + assertThat(statistics).hasValues(1, 0, 0, 1, 0); + } + + @Test + void sync_WithErrorCachingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey("productTypeKey")) .build(); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final ProductService productService = spy(new ProductServiceImpl(syncOptions)); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.cacheKeysToIds(any())) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - - final ProductSync productSync = new ProductSync(syncOptions, productService, - productTypeService, mock(CategoryService.class), mock(TypeService.class), - mock(ChannelService.class), mock(CustomerGroupService.class), mock(TaxCategoryService.class), + final ProductService productService = spy(new ProductServiceImpl(syncOptions)); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.cacheKeysToIds(any())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final ProductSync productSync = + new ProductSync( + syncOptions, + productService, + productTypeService, + mock(CategoryService.class), + mock(TypeService.class), + mock(ChannelService.class), + mock(CustomerGroupService.class), + mock(TaxCategoryService.class), mock(StateService.class), mock(UnresolvedReferencesService.class), mock(CustomObjectService.class)); - // test - final ProductSyncStatistics productSyncStatistics = productSync - .sync(singletonList(productDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to build a cache of keys to ids.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); - }); - - assertThat(productSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey("productTypeKey")) - .taxCategory(null) - .state(null) - .build(); - - - - final SphereClient mockClient = mock(SphereClient.class); - when(mockClient.execute(any(ProductQuery.class))) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder - .of(mockClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) - .build(); - - final ProductService productService = spy(new ProductServiceImpl(syncOptions)); - final Map keyToIds = new HashMap<>(); - keyToIds.put(productDraft.getKey(), UUID.randomUUID().toString()); - when(productService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(completedFuture(Optional.of(UUID.randomUUID().toString()))); - - final CategoryService categoryService = mock(CategoryService.class); - when(categoryService.fetchMatchingCategoriesByKeys(any())).thenReturn(completedFuture(emptySet())); + // test + final ProductSyncStatistics productSyncStatistics = + productSync.sync(singletonList(productDraft)).toCompletableFuture().join(); + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to build a cache of keys to ids.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + }); - final ProductSync productSync = new ProductSync(syncOptions, productService, - productTypeService, categoryService, mock(TypeService.class), - mock(ChannelService.class), mock(CustomerGroupService.class), mock(TaxCategoryService.class), - mock(StateService.class), - mock(UnresolvedReferencesService.class), - mock(CustomObjectService.class)); + assertThat(productSyncStatistics).hasValues(1, 0, 0, 1); + } - // test - final ProductSyncStatistics productSyncStatistics = productSync - .sync(singletonList(productDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to fetch existing products") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); - }); - - assertThat(productSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { - // preparation - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_2_RESOURCE_PATH, - ResourceIdentifier.ofKey("productTypeKey")) + @Test + void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey("productTypeKey")) .taxCategory(null) .state(null) .build(); - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder - .of(mock(SphereClient.class)) + final SphereClient mockClient = mock(SphereClient.class); + when(mockClient.execute(any(ProductQuery.class))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mockClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final ProductService productService = mock(ProductService.class); - when(productService.cacheKeysToIds(anySet())).thenReturn(completedFuture(emptyMap())); - when(productService.fetchMatchingProductsByKeys(anySet())).thenReturn(completedFuture(emptySet())); - when(productService.createProduct(any())).thenReturn(completedFuture(Optional.empty())); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(completedFuture(Optional.of(UUID.randomUUID().toString()))); - - final CategoryService categoryService = mock(CategoryService.class); - when(categoryService.fetchMatchingCategoriesByKeys(any())).thenReturn(completedFuture(emptySet())); - - final ProductSyncOptions spyProductSyncOptions = spy(productSyncOptions); - - final ProductSync productSync = new ProductSync(spyProductSyncOptions, productService, - productTypeService, categoryService, mock(TypeService.class), - mock(ChannelService.class), mock(CustomerGroupService.class), mock(TaxCategoryService.class), + final ProductService productService = spy(new ProductServiceImpl(syncOptions)); + final Map keyToIds = new HashMap<>(); + keyToIds.put(productDraft.getKey(), UUID.randomUUID().toString()); + when(productService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(completedFuture(Optional.of(UUID.randomUUID().toString()))); + + final CategoryService categoryService = mock(CategoryService.class); + when(categoryService.fetchMatchingCategoriesByKeys(any())) + .thenReturn(completedFuture(emptySet())); + + final ProductSync productSync = + new ProductSync( + syncOptions, + productService, + productTypeService, + categoryService, + mock(TypeService.class), + mock(ChannelService.class), + mock(CustomerGroupService.class), + mock(TaxCategoryService.class), mock(StateService.class), mock(UnresolvedReferencesService.class), mock(CustomObjectService.class)); - // test + // test + final ProductSyncStatistics productSyncStatistics = productSync.sync(singletonList(productDraft)).toCompletableFuture().join(); + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to fetch existing products")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + }); - // assertion - verify(spyProductSyncOptions).applyBeforeCreateCallback(any()); - verify(spyProductSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } + assertThat(productSyncStatistics).hasValues(1, 0, 0, 1); + } - @Test - void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { - // preparation - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, - ResourceIdentifier.ofKey("productTypeKey")) + @Test + void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { + // preparation + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_2_RESOURCE_PATH, ResourceIdentifier.ofKey("productTypeKey")) .taxCategory(null) .state(null) .build(); - final Product mockedExistingProduct = - readObjectFromResource(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, Product.class); - - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); - - final ProductService productService = mock(ProductService.class); - final Map keyToIds = new HashMap<>(); - keyToIds.put(productDraft.getKey(), UUID.randomUUID().toString()); - when(productService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); - when(productService.fetchMatchingProductsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockedExistingProduct))); - when(productService.updateProduct(any(), any())).thenReturn(completedFuture(mockedExistingProduct)); + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductService productService = mock(ProductService.class); + when(productService.cacheKeysToIds(anySet())).thenReturn(completedFuture(emptyMap())); + when(productService.fetchMatchingProductsByKeys(anySet())) + .thenReturn(completedFuture(emptySet())); + when(productService.createProduct(any())).thenReturn(completedFuture(Optional.empty())); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(completedFuture(Optional.of(UUID.randomUUID().toString()))); + + final CategoryService categoryService = mock(CategoryService.class); + when(categoryService.fetchMatchingCategoriesByKeys(any())) + .thenReturn(completedFuture(emptySet())); + + final ProductSyncOptions spyProductSyncOptions = spy(productSyncOptions); + + final ProductSync productSync = + new ProductSync( + spyProductSyncOptions, + productService, + productTypeService, + categoryService, + mock(TypeService.class), + mock(ChannelService.class), + mock(CustomerGroupService.class), + mock(TaxCategoryService.class), + mock(StateService.class), + mock(UnresolvedReferencesService.class), + mock(CustomObjectService.class)); - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(completedFuture(Optional.of(UUID.randomUUID().toString()))); - when(productTypeService.fetchCachedProductAttributeMetaDataMap(any())) - .thenReturn(completedFuture(Optional.of(new HashMap<>()))); + // test + productSync.sync(singletonList(productDraft)).toCompletableFuture().join(); - final CategoryService categoryService = mock(CategoryService.class); - when(categoryService.fetchMatchingCategoriesByKeys(any())).thenReturn(completedFuture(emptySet())); + // assertion + verify(spyProductSyncOptions).applyBeforeCreateCallback(any()); + verify(spyProductSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } - final ProductSyncOptions spyProductSyncOptions = spy(productSyncOptions); + @Test + void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { + // preparation + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, ResourceIdentifier.ofKey("productTypeKey")) + .taxCategory(null) + .state(null) + .build(); - final ProductSync productSync = new ProductSync(spyProductSyncOptions, productService, - productTypeService, categoryService, mock(TypeService.class), - mock(ChannelService.class), mock(CustomerGroupService.class), mock(TaxCategoryService.class), + final Product mockedExistingProduct = + readObjectFromResource(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, Product.class); + + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductService productService = mock(ProductService.class); + final Map keyToIds = new HashMap<>(); + keyToIds.put(productDraft.getKey(), UUID.randomUUID().toString()); + when(productService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); + when(productService.fetchMatchingProductsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockedExistingProduct))); + when(productService.updateProduct(any(), any())) + .thenReturn(completedFuture(mockedExistingProduct)); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(completedFuture(Optional.of(UUID.randomUUID().toString()))); + when(productTypeService.fetchCachedProductAttributeMetaDataMap(any())) + .thenReturn(completedFuture(Optional.of(new HashMap<>()))); + + final CategoryService categoryService = mock(CategoryService.class); + when(categoryService.fetchMatchingCategoriesByKeys(any())) + .thenReturn(completedFuture(emptySet())); + + final ProductSyncOptions spyProductSyncOptions = spy(productSyncOptions); + + final ProductSync productSync = + new ProductSync( + spyProductSyncOptions, + productService, + productTypeService, + categoryService, + mock(TypeService.class), + mock(ChannelService.class), + mock(CustomerGroupService.class), + mock(TaxCategoryService.class), mock(StateService.class), mock(UnresolvedReferencesService.class), mock(CustomObjectService.class)); - // test - productSync.sync(singletonList(productDraft)).toCompletableFuture().join(); + // test + productSync.sync(singletonList(productDraft)).toCompletableFuture().join(); - // assertion - verify(spyProductSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spyProductSyncOptions, never()).applyBeforeCreateCallback(any()); - } + // assertion + verify(spyProductSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spyProductSyncOptions, never()).applyBeforeCreateCallback(any()); + } - @Test - void sync_WithEmptyAttributeMetaDataMap_ShouldCallErrorCallback() { - // preparation - final ProductDraft productDraft = createProductDraftBuilder(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, - ResourceIdentifier.ofKey("productTypeKey")) + @Test + void sync_WithEmptyAttributeMetaDataMap_ShouldCallErrorCallback() { + // preparation + final ProductDraft productDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, ResourceIdentifier.ofKey("productTypeKey")) .taxCategory(null) .state(null) .build(); - final Product mockedExistingProduct = - readObjectFromResource(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, Product.class); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + final Product mockedExistingProduct = + readObjectFromResource(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, Product.class); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final ProductService productService = mock(ProductService.class); - final Map keyToIds = new HashMap<>(); - keyToIds.put(productDraft.getKey(), UUID.randomUUID().toString()); - when(productService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); - when(productService.fetchMatchingProductsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockedExistingProduct))); - when(productService.updateProduct(any(), any())).thenReturn(completedFuture(mockedExistingProduct)); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(completedFuture(Optional.of(UUID.randomUUID().toString()))); - when(productTypeService.fetchCachedProductAttributeMetaDataMap(any())) - .thenReturn(completedFuture(Optional.empty())); - - final CategoryService categoryService = mock(CategoryService.class); - when(categoryService.fetchMatchingCategoriesByKeys(any())).thenReturn(completedFuture(emptySet())); - - final ProductSyncOptions spyProductSyncOptions = spy(productSyncOptions); - - final ProductSync productSync = new ProductSync(spyProductSyncOptions, productService, - productTypeService, categoryService, mock(TypeService.class), - mock(ChannelService.class), mock(CustomerGroupService.class), mock(TaxCategoryService.class), + final ProductService productService = mock(ProductService.class); + final Map keyToIds = new HashMap<>(); + keyToIds.put(productDraft.getKey(), UUID.randomUUID().toString()); + when(productService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); + when(productService.fetchMatchingProductsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockedExistingProduct))); + when(productService.updateProduct(any(), any())) + .thenReturn(completedFuture(mockedExistingProduct)); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(completedFuture(Optional.of(UUID.randomUUID().toString()))); + when(productTypeService.fetchCachedProductAttributeMetaDataMap(any())) + .thenReturn(completedFuture(Optional.empty())); + + final CategoryService categoryService = mock(CategoryService.class); + when(categoryService.fetchMatchingCategoriesByKeys(any())) + .thenReturn(completedFuture(emptySet())); + + final ProductSyncOptions spyProductSyncOptions = spy(productSyncOptions); + + final ProductSync productSync = + new ProductSync( + spyProductSyncOptions, + productService, + productTypeService, + categoryService, + mock(TypeService.class), + mock(ChannelService.class), + mock(CustomerGroupService.class), + mock(TaxCategoryService.class), mock(StateService.class), mock(UnresolvedReferencesService.class), mock(CustomObjectService.class)); - // test - ProductSyncStatistics productSyncStatistics = - productSync.sync(singletonList(productDraft)).toCompletableFuture().join(); - - // assertion - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to update Product with key: 'productKey1'. Reason: Failed to" - + " fetch a productType for the product to build the products' attributes metadata.") - ); + // test + ProductSyncStatistics productSyncStatistics = + productSync.sync(singletonList(productDraft)).toCompletableFuture().join(); - AssertionsForStatistics.assertThat(productSyncStatistics).hasValues(1, 0, 0, 1); - } + // assertion + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .contains( + "Failed to update Product with key: 'productKey1'. Reason: Failed to" + + " fetch a productType for the product to build the products' attributes metadata.")); + AssertionsForStatistics.assertThat(productSyncStatistics).hasValues(1, 0, 0, 1); + } } diff --git a/src/test/java/com/commercetools/sync/products/SyncFilterTest.java b/src/test/java/com/commercetools/sync/products/SyncFilterTest.java index 38df3b9cf4..45a45d9011 100644 --- a/src/test/java/com/commercetools/sync/products/SyncFilterTest.java +++ b/src/test/java/com/commercetools/sync/products/SyncFilterTest.java @@ -1,10 +1,5 @@ package com.commercetools.sync.products; - -import org.junit.jupiter.api.Test; - -import java.util.EnumSet; - import static com.commercetools.sync.products.ActionGroup.ASSETS; import static com.commercetools.sync.products.ActionGroup.ATTRIBUTES; import static com.commercetools.sync.products.ActionGroup.CATEGORIES; @@ -17,71 +12,74 @@ import static com.commercetools.sync.products.SyncFilter.ofWhiteList; import static org.assertj.core.api.Assertions.assertThat; +import java.util.EnumSet; +import org.junit.jupiter.api.Test; + class SyncFilterTest { - @Test - void of_returnsSameInstanceOfDefaultSyncFilter() { - final SyncFilter syncFilter = SyncFilter.of(); - final SyncFilter otherSyncFilter = SyncFilter.of(); - assertThat(syncFilter).isSameAs(otherSyncFilter); - } + @Test + void of_returnsSameInstanceOfDefaultSyncFilter() { + final SyncFilter syncFilter = SyncFilter.of(); + final SyncFilter otherSyncFilter = SyncFilter.of(); + assertThat(syncFilter).isSameAs(otherSyncFilter); + } - @Test - void filterActionGroup_WithWhiteListingSingleGroup_ShouldFilterInOnlyThisGroup() { - final SyncFilter syncFilter = ofWhiteList(IMAGES); + @Test + void filterActionGroup_WithWhiteListingSingleGroup_ShouldFilterInOnlyThisGroup() { + final SyncFilter syncFilter = ofWhiteList(IMAGES); - assertThat(syncFilter.filterActionGroup(IMAGES)).isTrue(); - assertThat(syncFilter.filterActionGroup(ATTRIBUTES)).isFalse(); - assertThat(syncFilter.filterActionGroup(CATEGORIES)).isFalse(); - assertThat(syncFilter.filterActionGroup(DESCRIPTION)).isFalse(); - assertThat(syncFilter.filterActionGroup(METADESCRIPTION)).isFalse(); - assertThat(syncFilter.filterActionGroup(NAME)).isFalse(); - assertThat(syncFilter.filterActionGroup(ASSETS)).isFalse(); - } + assertThat(syncFilter.filterActionGroup(IMAGES)).isTrue(); + assertThat(syncFilter.filterActionGroup(ATTRIBUTES)).isFalse(); + assertThat(syncFilter.filterActionGroup(CATEGORIES)).isFalse(); + assertThat(syncFilter.filterActionGroup(DESCRIPTION)).isFalse(); + assertThat(syncFilter.filterActionGroup(METADESCRIPTION)).isFalse(); + assertThat(syncFilter.filterActionGroup(NAME)).isFalse(); + assertThat(syncFilter.filterActionGroup(ASSETS)).isFalse(); + } - @Test - void filterActionGroup_WithWhiteListingMultipleGroups_ShouldFilterInOnlyTheseGroups() { - final SyncFilter syncFilter = ofWhiteList(IMAGES, ATTRIBUTES, CATEGORIES, ASSETS); + @Test + void filterActionGroup_WithWhiteListingMultipleGroups_ShouldFilterInOnlyTheseGroups() { + final SyncFilter syncFilter = ofWhiteList(IMAGES, ATTRIBUTES, CATEGORIES, ASSETS); - assertThat(syncFilter.filterActionGroup(IMAGES)).isTrue(); - assertThat(syncFilter.filterActionGroup(ATTRIBUTES)).isTrue(); - assertThat(syncFilter.filterActionGroup(CATEGORIES)).isTrue(); - assertThat(syncFilter.filterActionGroup(ASSETS)).isTrue(); - assertThat(syncFilter.filterActionGroup(DESCRIPTION)).isFalse(); - assertThat(syncFilter.filterActionGroup(METADESCRIPTION)).isFalse(); - assertThat(syncFilter.filterActionGroup(NAME)).isFalse(); - } + assertThat(syncFilter.filterActionGroup(IMAGES)).isTrue(); + assertThat(syncFilter.filterActionGroup(ATTRIBUTES)).isTrue(); + assertThat(syncFilter.filterActionGroup(CATEGORIES)).isTrue(); + assertThat(syncFilter.filterActionGroup(ASSETS)).isTrue(); + assertThat(syncFilter.filterActionGroup(DESCRIPTION)).isFalse(); + assertThat(syncFilter.filterActionGroup(METADESCRIPTION)).isFalse(); + assertThat(syncFilter.filterActionGroup(NAME)).isFalse(); + } - @Test - void filterActionGroup_WithBlackListingSingleGroup_ShouldFilterOutOnlyThisGroup() { - final SyncFilter syncFilter = ofBlackList(PRICES); + @Test + void filterActionGroup_WithBlackListingSingleGroup_ShouldFilterOutOnlyThisGroup() { + final SyncFilter syncFilter = ofBlackList(PRICES); - assertThat(syncFilter.filterActionGroup(PRICES)).isFalse(); - assertThat(syncFilter.filterActionGroup(ATTRIBUTES)).isTrue(); - assertThat(syncFilter.filterActionGroup(CATEGORIES)).isTrue(); - assertThat(syncFilter.filterActionGroup(DESCRIPTION)).isTrue(); - assertThat(syncFilter.filterActionGroup(METADESCRIPTION)).isTrue(); - assertThat(syncFilter.filterActionGroup(NAME)).isTrue(); - } + assertThat(syncFilter.filterActionGroup(PRICES)).isFalse(); + assertThat(syncFilter.filterActionGroup(ATTRIBUTES)).isTrue(); + assertThat(syncFilter.filterActionGroup(CATEGORIES)).isTrue(); + assertThat(syncFilter.filterActionGroup(DESCRIPTION)).isTrue(); + assertThat(syncFilter.filterActionGroup(METADESCRIPTION)).isTrue(); + assertThat(syncFilter.filterActionGroup(NAME)).isTrue(); + } - @Test - void filterActionGroup_WithBlackListingMultiplGroups_ShouldFilterOutOnlyTheseGroups() { - final SyncFilter syncFilter = ofBlackList(PRICES, IMAGES, ATTRIBUTES, NAME); + @Test + void filterActionGroup_WithBlackListingMultiplGroups_ShouldFilterOutOnlyTheseGroups() { + final SyncFilter syncFilter = ofBlackList(PRICES, IMAGES, ATTRIBUTES, NAME); - assertThat(syncFilter.filterActionGroup(PRICES)).isFalse(); - assertThat(syncFilter.filterActionGroup(IMAGES)).isFalse(); - assertThat(syncFilter.filterActionGroup(ATTRIBUTES)).isFalse(); - assertThat(syncFilter.filterActionGroup(NAME)).isFalse(); - assertThat(syncFilter.filterActionGroup(CATEGORIES)).isTrue(); - assertThat(syncFilter.filterActionGroup(DESCRIPTION)).isTrue(); - assertThat(syncFilter.filterActionGroup(METADESCRIPTION)).isTrue(); - assertThat(syncFilter.filterActionGroup(ASSETS)).isTrue(); - } + assertThat(syncFilter.filterActionGroup(PRICES)).isFalse(); + assertThat(syncFilter.filterActionGroup(IMAGES)).isFalse(); + assertThat(syncFilter.filterActionGroup(ATTRIBUTES)).isFalse(); + assertThat(syncFilter.filterActionGroup(NAME)).isFalse(); + assertThat(syncFilter.filterActionGroup(CATEGORIES)).isTrue(); + assertThat(syncFilter.filterActionGroup(DESCRIPTION)).isTrue(); + assertThat(syncFilter.filterActionGroup(METADESCRIPTION)).isTrue(); + assertThat(syncFilter.filterActionGroup(ASSETS)).isTrue(); + } - @Test - void filterActionGroup_WithDefaultSyncFilterShouldFilterInAllActionGroups() { - final SyncFilter syncFilter = SyncFilter.of(); - EnumSet.allOf(ActionGroup.class) - .forEach(actionGroup -> assertThat(syncFilter.filterActionGroup(actionGroup)).isTrue()); - } + @Test + void filterActionGroup_WithDefaultSyncFilterShouldFilterInAllActionGroups() { + final SyncFilter syncFilter = SyncFilter.of(); + EnumSet.allOf(ActionGroup.class) + .forEach(actionGroup -> assertThat(syncFilter.filterActionGroup(actionGroup)).isTrue()); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/PriceCustomerGroupReferenceResolverTest.java b/src/test/java/com/commercetools/sync/products/helpers/PriceCustomerGroupReferenceResolverTest.java index b16d8c601e..d9a54fae00 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/PriceCustomerGroupReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/PriceCustomerGroupReferenceResolverTest.java @@ -1,5 +1,17 @@ package com.commercetools.sync.products.helpers; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroup; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroupService; +import static com.commercetools.sync.products.helpers.PriceReferenceResolver.CUSTOMER_GROUP_DOES_NOT_EXIST; +import static com.commercetools.sync.products.helpers.PriceReferenceResolver.FAILED_TO_RESOLVE_REFERENCE; +import static java.lang.String.format; +import static java.util.Objects.isNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -13,127 +25,126 @@ import io.sphere.sdk.models.SphereException; import io.sphere.sdk.products.PriceDraftBuilder; import io.sphere.sdk.utils.MoneyImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.math.BigDecimal; import java.util.Optional; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroup; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroupService; -import static com.commercetools.sync.products.helpers.PriceReferenceResolver.CUSTOMER_GROUP_DOES_NOT_EXIST; -import static com.commercetools.sync.products.helpers.PriceReferenceResolver.FAILED_TO_RESOLVE_REFERENCE; -import static java.lang.String.format; -import static java.util.Objects.isNull; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class PriceCustomerGroupReferenceResolverTest { - private static final String CUSTOMER_GROUP_KEY = "customer-group-key_1"; - private static final String CUSTOMER_GROUP_ID = "1"; - - private CustomerGroupService customerGroupService; - private PriceReferenceResolver referenceResolver; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - customerGroupService = getMockCustomerGroupService(getMockCustomerGroup(CUSTOMER_GROUP_ID, CUSTOMER_GROUP_KEY)); - ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new PriceReferenceResolver(syncOptions, mock(TypeService.class), mock(ChannelService.class), - customerGroupService); - } - - @Test - void resolveCustomerGroupReference_WithKeys_ShouldResolveReference() { - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + private static final String CUSTOMER_GROUP_KEY = "customer-group-key_1"; + private static final String CUSTOMER_GROUP_ID = "1"; + + private CustomerGroupService customerGroupService; + private PriceReferenceResolver referenceResolver; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + customerGroupService = + getMockCustomerGroupService(getMockCustomerGroup(CUSTOMER_GROUP_ID, CUSTOMER_GROUP_KEY)); + ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new PriceReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), customerGroupService); + } + + @Test + void resolveCustomerGroupReference_WithKeys_ShouldResolveReference() { + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .customerGroup(CustomerGroup.referenceOfId("anyKey")); - final PriceDraftBuilder resolvedDraft = referenceResolver.resolveCustomerGroupReference(priceBuilder) - .toCompletableFuture().join(); + final PriceDraftBuilder resolvedDraft = + referenceResolver.resolveCustomerGroupReference(priceBuilder).toCompletableFuture().join(); - assertThat(resolvedDraft.getCustomerGroup()).isNotNull(); - assertThat(resolvedDraft.getCustomerGroup().getId()).isEqualTo(CUSTOMER_GROUP_ID); - } + assertThat(resolvedDraft.getCustomerGroup()).isNotNull(); + assertThat(resolvedDraft.getCustomerGroup().getId()).isEqualTo(CUSTOMER_GROUP_ID); + } - @Test - void resolveCustomerGroupReference_WithNullCustomerGroup_ShouldNotResolveReference() { - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)); + @Test + void resolveCustomerGroupReference_WithNullCustomerGroup_ShouldNotResolveReference() { + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)); - assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> isNull(resolvedDraft.getCustomerGroup())); - } + assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder).toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching(resolvedDraft -> isNull(resolvedDraft.getCustomerGroup())); + } - @Test - void resolveCustomerGroupReference_WithNonExistentCustomerGroup_ShouldNotResolveReference() { - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + @Test + void resolveCustomerGroupReference_WithNonExistentCustomerGroup_ShouldNotResolveReference() { + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .customerGroup(CustomerGroup.referenceOfId("nonExistentKey")); - when(customerGroupService.fetchCachedCustomerGroupId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - - assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_REFERENCE, CustomerGroup.referenceTypeId(), priceBuilder.getCountry(), - priceBuilder.getValue(), format(CUSTOMER_GROUP_DOES_NOT_EXIST, "nonExistentKey"))); - } - - @Test - void resolveCustomerGroupReference_WithNullIdOnCustomerGroupReference_ShouldNotResolveReference() { - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) - .customerGroup(Reference.of(CustomerGroup.referenceTypeId(), (String)null)); - - assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'customer-group' reference on PriceDraft with country:'%s' and" - + " value: '%s'. Reason: %s", priceBuilder.getCountry(), priceBuilder.getValue(), - BLANK_ID_VALUE_ON_REFERENCE)); - } - - @Test - void resolveCustomerGroupReference_WithEmptyIdOnCustomerGroupReference_ShouldNotResolveReference() { - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + when(customerGroupService.fetchCachedCustomerGroupId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_REFERENCE, + CustomerGroup.referenceTypeId(), + priceBuilder.getCountry(), + priceBuilder.getValue(), + format(CUSTOMER_GROUP_DOES_NOT_EXIST, "nonExistentKey"))); + } + + @Test + void + resolveCustomerGroupReference_WithNullIdOnCustomerGroupReference_ShouldNotResolveReference() { + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + .customerGroup(Reference.of(CustomerGroup.referenceTypeId(), (String) null)); + + assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'customer-group' reference on PriceDraft with country:'%s' and" + + " value: '%s'. Reason: %s", + priceBuilder.getCountry(), priceBuilder.getValue(), BLANK_ID_VALUE_ON_REFERENCE)); + } + + @Test + void + resolveCustomerGroupReference_WithEmptyIdOnCustomerGroupReference_ShouldNotResolveReference() { + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .customerGroup(CustomerGroup.referenceOfId("")); - assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'customer-group' reference on PriceDraft with country:'%s' and" - + " value: '%s'. Reason: %s", priceBuilder.getCountry(), priceBuilder.getValue(), - BLANK_ID_VALUE_ON_REFERENCE)); - } - - @Test - void resolveCustomerGroupReference_WithExceptionOnFetch_ShouldNotResolveReference() { - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'customer-group' reference on PriceDraft with country:'%s' and" + + " value: '%s'. Reason: %s", + priceBuilder.getCountry(), priceBuilder.getValue(), BLANK_ID_VALUE_ON_REFERENCE)); + } + + @Test + void resolveCustomerGroupReference_WithExceptionOnFetch_ShouldNotResolveReference() { + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .customerGroup(CustomerGroup.referenceOfId("CustomerGroupKey")); - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(customerGroupService.fetchCachedCustomerGroupId(anyString())).thenReturn(futureThrowingSphereException); - - assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(customerGroupService.fetchCachedCustomerGroupId(anyString())) + .thenReturn(futureThrowingSphereException); + + assertThat(referenceResolver.resolveCustomerGroupReference(priceBuilder).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/PriceReferenceResolverTest.java b/src/test/java/com/commercetools/sync/products/helpers/PriceReferenceResolverTest.java index 29e8d49c1b..61c6c21438 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/PriceReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/PriceReferenceResolverTest.java @@ -1,5 +1,23 @@ package com.commercetools.sync.products.helpers; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockChannelService; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroup; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroupService; +import static com.commercetools.sync.products.helpers.PriceReferenceResolver.CHANNEL_DOES_NOT_EXIST; +import static com.commercetools.sync.products.helpers.PriceReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; +import static com.commercetools.sync.products.helpers.PriceReferenceResolver.FAILED_TO_RESOLVE_REFERENCE; +import static java.lang.String.format; +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -20,339 +38,356 @@ import io.sphere.sdk.types.Type; import io.sphere.sdk.types.queries.TypeQuery; import io.sphere.sdk.utils.MoneyImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.math.BigDecimal; import java.util.HashMap; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockChannelService; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroup; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomerGroupService; -import static com.commercetools.sync.products.helpers.PriceReferenceResolver.CHANNEL_DOES_NOT_EXIST; -import static com.commercetools.sync.products.helpers.PriceReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; -import static com.commercetools.sync.products.helpers.PriceReferenceResolver.FAILED_TO_RESOLVE_REFERENCE; -import static java.lang.String.format; -import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class PriceReferenceResolverTest { - private TypeService typeService; - private ChannelService channelService; - private CustomerGroupService customerGroupService; - private ProductSyncOptions syncOptions; - - private static final String CHANNEL_KEY = "channel-key_1"; - private static final String CHANNEL_ID = "1"; - - private static final String CUSTOMER_GROUP_KEY = "customer-group-key_1"; - private static final String CUSTOMER_GROUP_ID = "1"; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - typeService = getMockTypeService(); - channelService = getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)); - customerGroupService = getMockCustomerGroupService(getMockCustomerGroup(CUSTOMER_GROUP_ID, CUSTOMER_GROUP_KEY)); - syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - } - - @Test - void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { - // Preparation - final SphereClient ctpClient = mock(SphereClient.class); - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(ctpClient).build(); - - final TypeService typeService = new TypeServiceImpl(productSyncOptions); - - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(ctpClient.execute(any(TypeQuery.class))).thenReturn(futureThrowingSphereException); - - final String customTypeKey = "customTypeKey"; - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + private TypeService typeService; + private ChannelService channelService; + private CustomerGroupService customerGroupService; + private ProductSyncOptions syncOptions; + + private static final String CHANNEL_KEY = "channel-key_1"; + private static final String CHANNEL_ID = "1"; + + private static final String CUSTOMER_GROUP_KEY = "customer-group-key_1"; + private static final String CUSTOMER_GROUP_ID = "1"; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + typeService = getMockTypeService(); + channelService = getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)); + customerGroupService = + getMockCustomerGroupService(getMockCustomerGroup(CUSTOMER_GROUP_ID, CUSTOMER_GROUP_KEY)); + syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + } + + @Test + void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { + // Preparation + final SphereClient ctpClient = mock(SphereClient.class); + final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(ctpClient).build(); + + final TypeService typeService = new TypeServiceImpl(productSyncOptions); + + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(ctpClient.execute(any(TypeQuery.class))).thenReturn(futureThrowingSphereException); + + final String customTypeKey = "customTypeKey"; + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .custom(CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>())); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(productSyncOptions, typeService, channelService, customerGroupService); - - // Test and assertion - assertThat(priceReferenceResolver.resolveCustomTypeReference(priceBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { - final String customTypeKey = "customTypeKey"; - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver( + productSyncOptions, typeService, channelService, customerGroupService); + + // Test and assertion + assertThat(priceReferenceResolver.resolveCustomTypeReference(priceBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { + final String customTypeKey = "customTypeKey"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .custom(customFieldsDraft); - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); - - // Test and assertion - final String expectedExceptionMessage = format(FAILED_TO_RESOLVE_CUSTOM_TYPE, - priceBuilder.getCountry(), priceBuilder.getValue()); - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey));; - assertThat(priceReferenceResolver.resolveCustomTypeReference(priceBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); + + // Test and assertion + final String expectedExceptionMessage = + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, priceBuilder.getCountry(), priceBuilder.getValue()); + final String expectedMessageWithCause = + format( + "%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); + ; + assertThat(priceReferenceResolver.resolveCustomTypeReference(priceBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .custom(CustomFieldsDraft.ofTypeKeyAndJson("", emptyMap())); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); - - assertThat(priceReferenceResolver.resolveCustomTypeReference(priceBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on PriceDraft" - + " with country:'DE' and value: 'EUR 10'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomTypeReference_WithNonNullIdOnCustomTypeResId_ShouldResolveCustomTypeReference() { - // Preparation - final String customTypeId = UUID.randomUUID().toString(); - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); + + assertThat(priceReferenceResolver.resolveCustomTypeReference(priceBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on PriceDraft" + + " with country:'DE' and value: 'EUR 10'. Reason: %s", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void + resolveCustomTypeReference_WithNonNullIdOnCustomTypeResId_ShouldResolveCustomTypeReference() { + // Preparation + final String customTypeId = UUID.randomUUID().toString(); + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .custom(CustomFieldsDraft.ofTypeIdAndJson(customTypeId, new HashMap<>())); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); - - // Test - final PriceDraftBuilder resolvedDraftBuilder = priceReferenceResolver.resolveCustomTypeReference(priceBuilder) - .toCompletableFuture().join(); - - // Assertion - assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); - assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo(customTypeId); - } - - @Test - void resolveCustomTypeReference_WithNonNullKeyOnCustomTypeResId_ShouldResolveCustomTypeReference() { - // Preparation - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); + + // Test + final PriceDraftBuilder resolvedDraftBuilder = + priceReferenceResolver + .resolveCustomTypeReference(priceBuilder) + .toCompletableFuture() + .join(); + + // Assertion + assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); + assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo(customTypeId); + } + + @Test + void + resolveCustomTypeReference_WithNonNullKeyOnCustomTypeResId_ShouldResolveCustomTypeReference() { + // Preparation + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .custom(CustomFieldsDraft.ofTypeKeyAndJson("foo", new HashMap<>())); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); - - // Test - final PriceDraftBuilder resolvedDraftBuilder = priceReferenceResolver.resolveCustomTypeReference(priceBuilder) - .toCompletableFuture().join(); - // Assertion - assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); - assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo("typeId"); - } - - @Test - void resolveChannelReference_WithNonExistingChannelAndNotEnsureChannel_ShouldNotResolveChannelReference() { - // Preparation - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); + + // Test + final PriceDraftBuilder resolvedDraftBuilder = + priceReferenceResolver + .resolveCustomTypeReference(priceBuilder) + .toCompletableFuture() + .join(); + // Assertion + assertThat(resolvedDraftBuilder.getCustom()).isNotNull(); + assertThat(resolvedDraftBuilder.getCustom().getType().getId()).isEqualTo("typeId"); + } + + @Test + void + resolveChannelReference_WithNonExistingChannelAndNotEnsureChannel_ShouldNotResolveChannelReference() { + // Preparation + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .channel(ResourceIdentifier.ofKey("channelKey")); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); - - // Test and assertion - assertThat(priceReferenceResolver.resolveChannelReference(priceBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_REFERENCE, Channel.resourceTypeId(), priceBuilder.getCountry(), - priceBuilder.getValue(), format(CHANNEL_DOES_NOT_EXIST, "channelKey"))); - } - - @Test - void resolveChannelReference_WithNonExistingChannelAndEnsureChannel_ShouldResolveSupplyChannelReference() { - // Preparation - final ProductSyncOptions optionsWithEnsureChannels = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .ensurePriceChannels(true) - .build(); - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); + + // Test and assertion + assertThat(priceReferenceResolver.resolveChannelReference(priceBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_REFERENCE, + Channel.resourceTypeId(), + priceBuilder.getCountry(), + priceBuilder.getValue(), + format(CHANNEL_DOES_NOT_EXIST, "channelKey"))); + } + + @Test + void + resolveChannelReference_WithNonExistingChannelAndEnsureChannel_ShouldResolveSupplyChannelReference() { + // Preparation + final ProductSyncOptions optionsWithEnsureChannels = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).ensurePriceChannels(true).build(); + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .channel(ResourceIdentifier.ofKey("channelKey")); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(optionsWithEnsureChannels, typeService, channelService, customerGroupService); + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver( + optionsWithEnsureChannels, typeService, channelService, customerGroupService); - // Test - final PriceDraftBuilder resolvedDraftBuilder = priceReferenceResolver.resolveChannelReference(priceBuilder) - .toCompletableFuture().join(); + // Test + final PriceDraftBuilder resolvedDraftBuilder = + priceReferenceResolver.resolveChannelReference(priceBuilder).toCompletableFuture().join(); - // Assertion - assertThat(resolvedDraftBuilder.getChannel()).isNotNull(); - assertThat(resolvedDraftBuilder.getChannel().getId()).isEqualTo(CHANNEL_ID); - } + // Assertion + assertThat(resolvedDraftBuilder.getChannel()).isNotNull(); + assertThat(resolvedDraftBuilder.getChannel().getId()).isEqualTo(CHANNEL_ID); + } - @Test - void resolveChannelReference_WithEmptyChannelKey_ShouldNotResolveChannelReference() { - // Preparation - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + @Test + void resolveChannelReference_WithEmptyChannelKey_ShouldNotResolveChannelReference() { + // Preparation + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .channel(ResourceIdentifier.ofKey("")); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); - - // Test and assertion - assertThat(priceReferenceResolver.resolveChannelReference(priceBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_REFERENCE, Channel.resourceTypeId(), priceBuilder.getCountry(), - priceBuilder.getValue(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveChannelReference_WithNullChannelKey_ShouldNotResolveChannelReference() { - // Preparation - when(channelService.fetchCachedChannelId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); + + // Test and assertion + assertThat(priceReferenceResolver.resolveChannelReference(priceBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_REFERENCE, + Channel.resourceTypeId(), + priceBuilder.getCountry(), + priceBuilder.getValue(), + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveChannelReference_WithNullChannelKey_ShouldNotResolveChannelReference() { + // Preparation + when(channelService.fetchCachedChannelId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .channel(ResourceIdentifier.ofKey(null)); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); - - // Test and assertion - assertThat(priceReferenceResolver.resolveChannelReference(priceBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_REFERENCE, Channel.resourceTypeId(), priceBuilder.getCountry(), - priceBuilder.getValue(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveChannelReference_WithNonNullChannelKey_ShouldResolveSupplyChannelReference() { - // Preparation - final ProductSyncOptions optionsWithEnsureChannels = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); + + // Test and assertion + assertThat(priceReferenceResolver.resolveChannelReference(priceBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_REFERENCE, + Channel.resourceTypeId(), + priceBuilder.getCountry(), + priceBuilder.getValue(), + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveChannelReference_WithNonNullChannelKey_ShouldResolveSupplyChannelReference() { + // Preparation + final ProductSyncOptions optionsWithEnsureChannels = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .channel(ResourceIdentifier.ofKey("channelKey")); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(optionsWithEnsureChannels, typeService, channelService, customerGroupService); + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver( + optionsWithEnsureChannels, typeService, channelService, customerGroupService); - // Test - final PriceDraftBuilder resolvedDraftBuilder = priceReferenceResolver.resolveChannelReference(priceBuilder) - .toCompletableFuture().join(); + // Test + final PriceDraftBuilder resolvedDraftBuilder = + priceReferenceResolver.resolveChannelReference(priceBuilder).toCompletableFuture().join(); - // Assertion - assertThat(resolvedDraftBuilder.getChannel()).isNotNull(); - assertThat(resolvedDraftBuilder.getChannel().getId()).isEqualTo(CHANNEL_ID); - } + // Assertion + assertThat(resolvedDraftBuilder.getChannel()).isNotNull(); + assertThat(resolvedDraftBuilder.getChannel().getId()).isEqualTo(CHANNEL_ID); + } - @Test - void resolveReferences_WithNoReferences_ShouldNotResolveReferences() { - final PriceDraft priceDraft = PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) - .country(CountryCode.DE) - .build(); + @Test + void resolveReferences_WithNoReferences_ShouldNotResolveReferences() { + final PriceDraft priceDraft = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + .country(CountryCode.DE) + .build(); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver(syncOptions, typeService, channelService, customerGroupService); - final PriceDraft referencesResolvedDraft = priceReferenceResolver.resolveReferences(priceDraft) - .toCompletableFuture().join(); + final PriceDraft referencesResolvedDraft = + priceReferenceResolver.resolveReferences(priceDraft).toCompletableFuture().join(); - assertThat(referencesResolvedDraft.getCustom()).isNull(); - assertThat(referencesResolvedDraft.getChannel()).isNull(); - assertThat(referencesResolvedDraft.getCustomerGroup()).isNull(); - } + assertThat(referencesResolvedDraft.getCustom()).isNull(); + assertThat(referencesResolvedDraft.getChannel()).isNull(); + assertThat(referencesResolvedDraft.getCustomerGroup()).isNull(); + } - @Test - void resolveChannelReference_WithNullChannelReference_ShouldNotResolveReference() { - final ProductSyncOptions optionsWithEnsureChannels = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); + @Test + void resolveChannelReference_WithNullChannelReference_ShouldNotResolveReference() { + final ProductSyncOptions optionsWithEnsureChannels = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) - .channel((ResourceIdentifier)null); + .channel((ResourceIdentifier) null); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(optionsWithEnsureChannels, typeService, channelService, customerGroupService); + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver( + optionsWithEnsureChannels, typeService, channelService, customerGroupService); - // Test - final PriceDraftBuilder resolvedDraftBuilder = priceReferenceResolver.resolveChannelReference(priceBuilder) - .toCompletableFuture().join(); + // Test + final PriceDraftBuilder resolvedDraftBuilder = + priceReferenceResolver.resolveChannelReference(priceBuilder).toCompletableFuture().join(); - // Assertion - assertThat(resolvedDraftBuilder.getChannel()).isNull(); - } + // Assertion + assertThat(resolvedDraftBuilder.getChannel()).isNull(); + } - @Test - void resolveChannelReference_WithChannelReferenceWithId_ShouldNotResolveReference() { - final ProductSyncOptions optionsWithEnsureChannels = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); + @Test + void resolveChannelReference_WithChannelReferenceWithId_ShouldNotResolveReference() { + final ProductSyncOptions optionsWithEnsureChannels = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final PriceDraftBuilder priceBuilder = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceDraftBuilder priceBuilder = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .country(CountryCode.DE) .channel(ResourceIdentifier.ofId("existing-id")); - final PriceReferenceResolver priceReferenceResolver = - new PriceReferenceResolver(optionsWithEnsureChannels, typeService, channelService, customerGroupService); + final PriceReferenceResolver priceReferenceResolver = + new PriceReferenceResolver( + optionsWithEnsureChannels, typeService, channelService, customerGroupService); - // Test - final PriceDraftBuilder resolvedDraftBuilder = priceReferenceResolver.resolveChannelReference(priceBuilder) - .toCompletableFuture().join(); + // Test + final PriceDraftBuilder resolvedDraftBuilder = + priceReferenceResolver.resolveChannelReference(priceBuilder).toCompletableFuture().join(); - // Assertion - assertThat(resolvedDraftBuilder.getChannel()).isNotNull(); - assertThat(resolvedDraftBuilder.getChannel().getId()).isEqualTo("existing-id"); - } + // Assertion + assertThat(resolvedDraftBuilder.getChannel()).isNotNull(); + assertThat(resolvedDraftBuilder.getChannel().getId()).isEqualTo("existing-id"); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/ProductAssetActionFactoryTest.java b/src/test/java/com/commercetools/sync/products/helpers/ProductAssetActionFactoryTest.java index a4490d4ed9..d6ed2d6e13 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/ProductAssetActionFactoryTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/ProductAssetActionFactoryTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.products.helpers; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Lists.emptyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import io.sphere.sdk.client.SphereClient; @@ -13,89 +19,80 @@ import io.sphere.sdk.products.commands.updateactions.ChangeAssetOrder; import io.sphere.sdk.products.commands.updateactions.RemoveAsset; import io.sphere.sdk.products.commands.updateactions.SetAssetTags; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashSet; import java.util.List; - -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.util.Lists.emptyList; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductAssetActionFactoryTest { - private ProductAssetActionFactory productAssetActionFactory; - - @BeforeEach - void setup() { - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - - productAssetActionFactory = new ProductAssetActionFactory(1, syncOptions); - } - - @Test - void buildRemoveAssetAction_always_ShouldBuildCorrectAction() { - final UpdateAction action = productAssetActionFactory.buildRemoveAssetAction("foo"); - assertThat(action).isNotNull(); - assertThat(action).isInstanceOf(RemoveAsset.class); - final RemoveAsset removeAsset = (RemoveAsset) action; - assertThat(removeAsset.getVariantId()).isEqualTo(1); - assertThat(removeAsset.getAssetKey()).isEqualTo("foo"); - assertThat(removeAsset.isStaged()).isTrue(); - } - - @Test - void buildChangeAssetOrderAction_always_ShouldBuildCorrectAction() { - final UpdateAction action = productAssetActionFactory.buildChangeAssetOrderAction(emptyList()); - assertThat(action).isNotNull(); - assertThat(action).isInstanceOf(ChangeAssetOrder.class); - final ChangeAssetOrder changeAssetOrder = (ChangeAssetOrder) action; - assertThat(changeAssetOrder.getVariantId()).isEqualTo(1); - assertThat(changeAssetOrder.getAssetOrder()).isEqualTo(emptyList()); - assertThat(changeAssetOrder.isStaged()).isTrue(); - } - - @Test - void buildAddAssetAction_always_ShouldBuildCorrectAction() { - final AssetDraft assetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")) - .build(); - - - final UpdateAction action = productAssetActionFactory.buildAddAssetAction(assetDraft, 0); - assertThat(action).isNotNull(); - assertThat(action).isInstanceOf(AddAsset.class); - final AddAsset addAsset = (AddAsset) action; - assertThat(addAsset.getVariantId()).isEqualTo(1); - assertThat(addAsset.getAsset().getName()).isEqualTo(ofEnglish("assetName")); - assertThat(addAsset.getAsset().getSources()).isEqualTo(emptyList()); - assertThat(addAsset.getPosition()).isEqualTo(0); - assertThat(addAsset.isStaged()).isTrue(); - } - - @Test - void buildAssetActions_always_ShouldBuildCorrectAction() { - final Product mainProduct = mock(Product.class); - final ProductDraft mainProductDraft = mock(ProductDraft.class); - final Asset asset = mock(Asset.class); - when(asset.getKey()).thenReturn("assetKey"); - when(asset.getName()).thenReturn(ofEnglish("assetName")); - - final HashSet newTags = new HashSet<>(); - newTags.add("newTag"); - - final AssetDraft assetDraft = AssetDraftBuilder.of(asset) - .tags(newTags) - .build(); - - final List> updateActions = productAssetActionFactory - .buildAssetActions(mainProduct, mainProductDraft, asset, assetDraft); - - assertThat(updateActions).isNotNull(); - assertThat(updateActions).containsExactly( - SetAssetTags.ofVariantIdAndAssetKey(1, asset.getKey(), newTags, true) - ); - } + private ProductAssetActionFactory productAssetActionFactory; + + @BeforeEach + void setup() { + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + productAssetActionFactory = new ProductAssetActionFactory(1, syncOptions); + } + + @Test + void buildRemoveAssetAction_always_ShouldBuildCorrectAction() { + final UpdateAction action = productAssetActionFactory.buildRemoveAssetAction("foo"); + assertThat(action).isNotNull(); + assertThat(action).isInstanceOf(RemoveAsset.class); + final RemoveAsset removeAsset = (RemoveAsset) action; + assertThat(removeAsset.getVariantId()).isEqualTo(1); + assertThat(removeAsset.getAssetKey()).isEqualTo("foo"); + assertThat(removeAsset.isStaged()).isTrue(); + } + + @Test + void buildChangeAssetOrderAction_always_ShouldBuildCorrectAction() { + final UpdateAction action = + productAssetActionFactory.buildChangeAssetOrderAction(emptyList()); + assertThat(action).isNotNull(); + assertThat(action).isInstanceOf(ChangeAssetOrder.class); + final ChangeAssetOrder changeAssetOrder = (ChangeAssetOrder) action; + assertThat(changeAssetOrder.getVariantId()).isEqualTo(1); + assertThat(changeAssetOrder.getAssetOrder()).isEqualTo(emptyList()); + assertThat(changeAssetOrder.isStaged()).isTrue(); + } + + @Test + void buildAddAssetAction_always_ShouldBuildCorrectAction() { + final AssetDraft assetDraft = AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")).build(); + + final UpdateAction action = + productAssetActionFactory.buildAddAssetAction(assetDraft, 0); + assertThat(action).isNotNull(); + assertThat(action).isInstanceOf(AddAsset.class); + final AddAsset addAsset = (AddAsset) action; + assertThat(addAsset.getVariantId()).isEqualTo(1); + assertThat(addAsset.getAsset().getName()).isEqualTo(ofEnglish("assetName")); + assertThat(addAsset.getAsset().getSources()).isEqualTo(emptyList()); + assertThat(addAsset.getPosition()).isEqualTo(0); + assertThat(addAsset.isStaged()).isTrue(); + } + + @Test + void buildAssetActions_always_ShouldBuildCorrectAction() { + final Product mainProduct = mock(Product.class); + final ProductDraft mainProductDraft = mock(ProductDraft.class); + final Asset asset = mock(Asset.class); + when(asset.getKey()).thenReturn("assetKey"); + when(asset.getName()).thenReturn(ofEnglish("assetName")); + + final HashSet newTags = new HashSet<>(); + newTags.add("newTag"); + + final AssetDraft assetDraft = AssetDraftBuilder.of(asset).tags(newTags).build(); + + final List> updateActions = + productAssetActionFactory.buildAssetActions( + mainProduct, mainProductDraft, asset, assetDraft); + + assertThat(updateActions).isNotNull(); + assertThat(updateActions) + .containsExactly(SetAssetTags.ofVariantIdAndAssetKey(1, asset.getKey(), newTags, true)); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/ProductBatchValidatorTest.java b/src/test/java/com/commercetools/sync/products/helpers/ProductBatchValidatorTest.java index c20fe040d3..88653b3985 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/ProductBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/ProductBatchValidatorTest.java @@ -1,5 +1,22 @@ package com.commercetools.sync.products.helpers; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getProductReferenceWithId; +import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; +import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_DRAFT_IS_NULL; +import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_DRAFT_KEY_NOT_SET; +import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_VARIANT_DRAFT_IS_NULL; +import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_VARIANT_DRAFT_KEY_NOT_SET; +import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_VARIANT_DRAFT_SKU_NOT_SET; +import static com.commercetools.sync.products.helpers.ProductBatchValidator.getReferencedProductKeys; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -20,11 +37,6 @@ import io.sphere.sdk.states.State; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.MoneyImpl; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; @@ -32,481 +44,485 @@ import java.util.List; import java.util.Set; import java.util.UUID; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getProductReferenceWithId; -import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; -import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_DRAFT_IS_NULL; -import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_DRAFT_KEY_NOT_SET; -import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_VARIANT_DRAFT_IS_NULL; -import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_VARIANT_DRAFT_KEY_NOT_SET; -import static com.commercetools.sync.products.helpers.ProductBatchValidator.PRODUCT_VARIANT_DRAFT_SKU_NOT_SET; -import static com.commercetools.sync.products.helpers.ProductBatchValidator.getReferencedProductKeys; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductBatchValidatorTest { - private List errorCallBackMessages; - private ProductSyncOptions syncOptions; - private ProductSyncStatistics syncStatistics; - - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = ProductSyncOptionsBuilder.of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorCallBackMessages.add(exception.getMessage()); + private List errorCallBackMessages; + private ProductSyncOptions syncOptions; + private ProductSyncStatistics syncStatistics; + + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); + syncOptions = + ProductSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorCallBackMessages.add(exception.getMessage()); }) - .build(); - syncStatistics = mock(ProductSyncStatistics.class); - } - - @Test - void getReferencedProductKeys_WithNullAttributes_ShouldReturnEmptySet() { - assertThat(getReferencedProductKeys(ProductVariantDraftBuilder.of().build())).isEmpty(); - } - - @Test - void getReferencedProductKeys_WithNoAttributes_ShouldReturnEmptySet() { - assertThat(getReferencedProductKeys(ProductVariantDraftBuilder.of().attributes().build())).isEmpty(); - } - - @Test - void getReferencedProductKeys_WithANullAttribute_ShouldReturnEmptySet() { - assertThat(getReferencedProductKeys(ProductVariantDraftBuilder.of().attributes(singletonList(null)).build())) - .isEmpty(); - } - - @Test - void getReferencedProductKeys_WithAProductRefAttribute_ShouldReturnEmptySet() { - final AttributeDraft productReferenceSetAttribute = - getReferenceSetAttributeDraft("foo", getProductReferenceWithId("foo"), - getProductReferenceWithId("bar")); - final AttributeDraft productReferenceAttribute = AttributeDraft.of("foo", getProductReferenceWithId("foo")); - - final List attributes = asList(null, productReferenceAttribute, productReferenceSetAttribute); - - final ProductVariantDraft variantDraft = ProductVariantDraftBuilder.of() - .attributes(attributes) - .build(); - assertThat(getReferencedProductKeys(variantDraft)).containsExactlyInAnyOrder("foo", "bar"); - } - - @Test - void getReferencedProductKeys_WithANullAttrValue_ShouldReturnEmptySet() { - final AttributeDraft attributeDraft = AttributeDraft.of("foo", null); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - final Set result = getReferencedProductKeys(productVariantDraft); - - assertThat(result).isEmpty(); - } - - @Test - void getReferencedProductKeys_WithSetAsValue_ShouldReturnSetKeys() { - final AttributeDraft productReferenceSetAttribute = - getReferenceSetAttributeDraft("foo", getProductReferenceWithId("foo"), - getProductReferenceWithId("bar")); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceSetAttribute) .build(); + syncStatistics = mock(ProductSyncStatistics.class); + } - final Set result = getReferencedProductKeys(productVariantDraft); - - assertThat(result).containsExactlyInAnyOrder("foo", "bar"); - } - - @Test - void getReferencedProductKeys_WithProductRefAsValue_ShouldReturnKeyInSet() { - final AttributeDraft productReferenceAttribute = AttributeDraft.of("foo", getProductReferenceWithId("foo")); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceAttribute) - .build(); - - final Set result = getReferencedProductKeys(productVariantDraft); - - assertThat(result).containsExactly("foo"); - } + @Test + void getReferencedProductKeys_WithNullAttributes_ShouldReturnEmptySet() { + assertThat(getReferencedProductKeys(ProductVariantDraftBuilder.of().build())).isEmpty(); + } - @Test - void getProductKeyFromReference_WithNullJsonNode_ShouldReturnEmptyOpt() { - final NullNode nullNode = JsonNodeFactory.instance.nullNode(); - final AttributeDraft productReferenceAttribute = AttributeDraft.of("foo", nullNode); + @Test + void getReferencedProductKeys_WithNoAttributes_ShouldReturnEmptySet() { + assertThat(getReferencedProductKeys(ProductVariantDraftBuilder.of().attributes().build())) + .isEmpty(); + } - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceAttribute) - .build(); + @Test + void getReferencedProductKeys_WithANullAttribute_ShouldReturnEmptySet() { + assertThat( + getReferencedProductKeys( + ProductVariantDraftBuilder.of().attributes(singletonList(null)).build())) + .isEmpty(); + } - final Set result = getReferencedProductKeys(productVariantDraft); + @Test + void getReferencedProductKeys_WithAProductRefAttribute_ShouldReturnEmptySet() { + final AttributeDraft productReferenceSetAttribute = + getReferenceSetAttributeDraft( + "foo", getProductReferenceWithId("foo"), getProductReferenceWithId("bar")); + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("foo", getProductReferenceWithId("foo")); - assertThat(result).isEmpty(); - } + final List attributes = + asList(null, productReferenceAttribute, productReferenceSetAttribute); - @Test - void getProductKeyFromReference_WithoutAProductReference_ShouldReturnEmptyOpt() { - final ObjectNode objectNode = JsonNodeFactory.instance.objectNode(); - objectNode.put("key", "value"); - final AttributeDraft productReferenceAttribute = AttributeDraft.of("foo", objectNode); + final ProductVariantDraft variantDraft = + ProductVariantDraftBuilder.of().attributes(attributes).build(); + assertThat(getReferencedProductKeys(variantDraft)).containsExactlyInAnyOrder("foo", "bar"); + } - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceAttribute) - .build(); + @Test + void getReferencedProductKeys_WithANullAttrValue_ShouldReturnEmptySet() { + final AttributeDraft attributeDraft = AttributeDraft.of("foo", null); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); - final Set result = getReferencedProductKeys(productVariantDraft); + final Set result = getReferencedProductKeys(productVariantDraft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void getReferencedProductKeysFromSet_WithOnlyNullRefsInSet_ShouldReturnEmptySet() { - final AttributeDraft productReferenceSetAttribute = - getReferenceSetAttributeDraft("foo", null, null); + @Test + void getReferencedProductKeys_WithSetAsValue_ShouldReturnSetKeys() { + final AttributeDraft productReferenceSetAttribute = + getReferenceSetAttributeDraft( + "foo", getProductReferenceWithId("foo"), getProductReferenceWithId("bar")); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceSetAttribute) - .build(); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceSetAttribute).build(); - final Set result = getReferencedProductKeys(productVariantDraft); + final Set result = getReferencedProductKeys(productVariantDraft); - assertThat(result).isEmpty(); - } + assertThat(result).containsExactlyInAnyOrder("foo", "bar"); + } - @Test - void getReferencedProductKeysFromSet_WithNullAndOtherRefsInSet_ShouldReturnSetOfNonNullIds() { - final ObjectNode objectNode = JsonNodeFactory.instance.objectNode(); - objectNode.put("key", "value"); + @Test + void getReferencedProductKeys_WithProductRefAsValue_ShouldReturnKeyInSet() { + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("foo", getProductReferenceWithId("foo")); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceAttribute).build(); - final AttributeDraft productReferenceSetAttribute = - getReferenceSetAttributeDraft("foo", getProductReferenceWithId("foo"), - getProductReferenceWithId("bar"), objectNode); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceSetAttribute) - .build(); + final Set result = getReferencedProductKeys(productVariantDraft); + + assertThat(result).containsExactly("foo"); + } + + @Test + void getProductKeyFromReference_WithNullJsonNode_ShouldReturnEmptyOpt() { + final NullNode nullNode = JsonNodeFactory.instance.nullNode(); + final AttributeDraft productReferenceAttribute = AttributeDraft.of("foo", nullNode); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceAttribute).build(); + + final Set result = getReferencedProductKeys(productVariantDraft); - final Set result = getReferencedProductKeys(productVariantDraft); + assertThat(result).isEmpty(); + } + + @Test + void getProductKeyFromReference_WithoutAProductReference_ShouldReturnEmptyOpt() { + final ObjectNode objectNode = JsonNodeFactory.instance.objectNode(); + objectNode.put("key", "value"); + final AttributeDraft productReferenceAttribute = AttributeDraft.of("foo", objectNode); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceAttribute).build(); + + final Set result = getReferencedProductKeys(productVariantDraft); + + assertThat(result).isEmpty(); + } + + @Test + void getReferencedProductKeysFromSet_WithOnlyNullRefsInSet_ShouldReturnEmptySet() { + final AttributeDraft productReferenceSetAttribute = + getReferenceSetAttributeDraft("foo", null, null); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceSetAttribute).build(); + + final Set result = getReferencedProductKeys(productVariantDraft); + + assertThat(result).isEmpty(); + } + + @Test + void getReferencedProductKeysFromSet_WithNullAndOtherRefsInSet_ShouldReturnSetOfNonNullIds() { + final ObjectNode objectNode = JsonNodeFactory.instance.objectNode(); + objectNode.put("key", "value"); - assertThat(result).containsExactlyInAnyOrder("foo", "bar"); - } + final AttributeDraft productReferenceSetAttribute = + getReferenceSetAttributeDraft( + "foo", getProductReferenceWithId("foo"), getProductReferenceWithId("bar"), objectNode); - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.emptyList()); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceSetAttribute).build(); - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).isEmpty(); - } + final Set result = getReferencedProductKeys(productVariantDraft); - @Test - void validateAndCollectReferencedKeys_WithNullProductDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.singletonList(null)); + assertThat(result).containsExactlyInAnyOrder("foo", "bar"); + } - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(PRODUCT_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.emptyList()); - @Test - void validateAndCollectReferencedKeys_WithProductDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final ProductDraft productDraft = mock(ProductDraft.class); - final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).isEmpty(); + } - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format(PRODUCT_DRAFT_KEY_NOT_SET, productDraft.getName())); - assertThat(validDrafts).isEmpty(); - } + @Test + void + validateAndCollectReferencedKeys_WithNullProductDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.singletonList(null)); - @Test - void validateAndCollectReferencedKeys_WithCategoryDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { - final ProductDraft productDraft = mock(ProductDraft.class); - when(productDraft.getKey()).thenReturn(EMPTY); - final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(PRODUCT_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format(PRODUCT_DRAFT_KEY_NOT_SET, productDraft.getName())); - assertThat(validDrafts).isEmpty(); - } + @Test + void + validateAndCollectReferencedKeys_WithProductDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final ProductDraft productDraft = mock(ProductDraft.class); + final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); - @Test - void validateAndCollectReferencedKeys_WithNullVariant_ShouldHaveValidationErrors() { - final String productDraftKey = "key"; - final int variantPosition = 0; + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(PRODUCT_DRAFT_KEY_NOT_SET, productDraft.getName())); + assertThat(validDrafts).isEmpty(); + } - final ProductDraft productDraft = mock(ProductDraft.class); - when(productDraft.getKey()).thenReturn(productDraftKey); + @Test + void + validateAndCollectReferencedKeys_WithCategoryDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { + final ProductDraft productDraft = mock(ProductDraft.class); + when(productDraft.getKey()).thenReturn(EMPTY); + final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); - final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(PRODUCT_DRAFT_KEY_NOT_SET, productDraft.getName())); + assertThat(validDrafts).isEmpty(); + } - assertThat(validDrafts).isEmpty(); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(PRODUCT_VARIANT_DRAFT_IS_NULL, variantPosition, productDraftKey)); - } + @Test + void validateAndCollectReferencedKeys_WithNullVariant_ShouldHaveValidationErrors() { + final String productDraftKey = "key"; + final int variantPosition = 0; - @Test - void validateAndCollectReferencedKeys_WithVariantButNoKeyAndSku_ShouldHaveValidationErrors() { - final int variantPosition = 0; - final String productDraftKey = "key"; + final ProductDraft productDraft = mock(ProductDraft.class); + when(productDraft.getKey()).thenReturn(productDraftKey); - final ProductDraft productDraft = mock(ProductDraft.class); - when(productDraft.getKey()).thenReturn(productDraftKey); - final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); - when(productDraft.getMasterVariant()).thenReturn(masterVariant); + final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); - final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); - - assertThat(validDrafts).isEmpty(); - assertThat(errorCallBackMessages).hasSize(2); - assertThat(errorCallBackMessages).containsExactlyInAnyOrder( + assertThat(validDrafts).isEmpty(); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(PRODUCT_VARIANT_DRAFT_IS_NULL, variantPosition, productDraftKey)); + } + + @Test + void validateAndCollectReferencedKeys_WithVariantButNoKeyAndSku_ShouldHaveValidationErrors() { + final int variantPosition = 0; + final String productDraftKey = "key"; + + final ProductDraft productDraft = mock(ProductDraft.class); + when(productDraft.getKey()).thenReturn(productDraftKey); + final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); + when(productDraft.getMasterVariant()).thenReturn(masterVariant); + + final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); + + assertThat(validDrafts).isEmpty(); + assertThat(errorCallBackMessages).hasSize(2); + assertThat(errorCallBackMessages) + .containsExactlyInAnyOrder( format(PRODUCT_VARIANT_DRAFT_SKU_NOT_SET, variantPosition, productDraftKey), format(PRODUCT_VARIANT_DRAFT_KEY_NOT_SET, variantPosition, productDraftKey)); - } - - @Test - void validateAndCollectReferencedKeys_WithNoVariantKey_ShouldHaveKeyValidationError() { - final int variantPosition = 0; - final String productDraftKey = "key"; - - final ProductDraft productDraft = mock(ProductDraft.class); - when(productDraft.getKey()).thenReturn(productDraftKey); - - final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); - when(masterVariant.getSku()).thenReturn("sku"); - when(productDraft.getMasterVariant()).thenReturn(masterVariant); - - final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); - - assertThat(validDrafts).isEmpty(); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo( - format(PRODUCT_VARIANT_DRAFT_KEY_NOT_SET, variantPosition, productDraftKey)); - } - - @Test - void validateAndCollectReferencedKeys_WithNoVariantSku_ShouldHaveSkuValidationError() { - final int variantPosition = 0; - final String productDraftKey = "key"; - final ProductDraft productDraft = mock(ProductDraft.class); - when(productDraft.getKey()).thenReturn(productDraftKey); - - final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); - when(masterVariant.getKey()).thenReturn("key"); - when(productDraft.getMasterVariant()).thenReturn(masterVariant); - - final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); - - assertThat(validDrafts).isEmpty(); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo( - format(PRODUCT_VARIANT_DRAFT_SKU_NOT_SET, variantPosition, productDraftKey)); - } - - @Test - void validateAndCollectReferencedKeys_WithSkuAndKey_ShouldHaveNoValidationErrors() { - final String productDraftKey = "key"; - final ProductDraft productDraft = mock(ProductDraft.class); - when(productDraft.getKey()).thenReturn(productDraftKey); - - final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); - when(masterVariant.getKey()).thenReturn("key"); - when(masterVariant.getSku()).thenReturn("sku"); - when(productDraft.getMasterVariant()).thenReturn(masterVariant); - - final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); - - assertThat(validDrafts).hasSize(1); - } - - @Test - void validateAndCollectReferencedKeys_WithDrafts_ShouldValidateCorrectly() { - final AttributeDraft productReferenceSetAttribute = - getReferenceSetAttributeDraft("foo", getProductReferenceWithId("foo"), - getProductReferenceWithId("bar")); - final AttributeDraft productReferenceAttribute = AttributeDraft.of("foo", getProductReferenceWithId("foo")); - - final List attributes = asList(null, productReferenceAttribute, productReferenceSetAttribute); - - final ProductVariantDraft validVariantDraft = ProductVariantDraftBuilder.of() - .key("variantKey") - .sku("variantSku") - .attributes(attributes) - .build(); - final ProductVariantDraft invalidVariantDraft = ProductVariantDraftBuilder.of() - .key("invalidVariant") - .attributes(attributes) - .build(); - - final ProductDraft validProductDraft = mock(ProductDraft.class); - when(validProductDraft.getKey()).thenReturn("validProductDraft"); - when(validProductDraft.getMasterVariant()).thenReturn(validVariantDraft); - when(validProductDraft.getVariants()).thenReturn(singletonList(validVariantDraft)); - - final ProductDraft inValidProductDraft1 = mock(ProductDraft.class); - when(inValidProductDraft1.getKey()).thenReturn("invalidProductDraft1"); - when(inValidProductDraft1.getMasterVariant()).thenReturn(null); - when(inValidProductDraft1.getVariants()).thenReturn(singletonList(validVariantDraft)); - - final ProductDraft inValidProductDraft2 = mock(ProductDraft.class); - when(inValidProductDraft2.getKey()).thenReturn("invalidProductDraft2"); - when(inValidProductDraft2.getMasterVariant()).thenReturn(invalidVariantDraft); - when(inValidProductDraft2.getVariants()).thenReturn(singletonList(invalidVariantDraft)); - - - final List productDrafts = asList(null, mock(ProductDraft.class), - validProductDraft, inValidProductDraft1, inValidProductDraft2); - - final ProductBatchValidator productBatchValidator = new ProductBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = - productBatchValidator.validateAndCollectReferencedKeys(productDrafts); - - assertThat(pair.getLeft()).hasSize(1); - assertThat(pair.getLeft()).containsExactly(validProductDraft); - assertThat(pair.getRight().getProductKeys()).hasSize(2); - assertThat(pair.getRight().getProductKeys()).containsExactlyInAnyOrder("foo", "bar"); - - assertThat(errorCallBackMessages).hasSize(5); - assertThat(errorCallBackMessages).containsExactlyInAnyOrder( + } + + @Test + void validateAndCollectReferencedKeys_WithNoVariantKey_ShouldHaveKeyValidationError() { + final int variantPosition = 0; + final String productDraftKey = "key"; + + final ProductDraft productDraft = mock(ProductDraft.class); + when(productDraft.getKey()).thenReturn(productDraftKey); + + final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); + when(masterVariant.getSku()).thenReturn("sku"); + when(productDraft.getMasterVariant()).thenReturn(masterVariant); + + final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); + + assertThat(validDrafts).isEmpty(); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(PRODUCT_VARIANT_DRAFT_KEY_NOT_SET, variantPosition, productDraftKey)); + } + + @Test + void validateAndCollectReferencedKeys_WithNoVariantSku_ShouldHaveSkuValidationError() { + final int variantPosition = 0; + final String productDraftKey = "key"; + final ProductDraft productDraft = mock(ProductDraft.class); + when(productDraft.getKey()).thenReturn(productDraftKey); + + final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); + when(masterVariant.getKey()).thenReturn("key"); + when(productDraft.getMasterVariant()).thenReturn(masterVariant); + + final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); + + assertThat(validDrafts).isEmpty(); + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(PRODUCT_VARIANT_DRAFT_SKU_NOT_SET, variantPosition, productDraftKey)); + } + + @Test + void validateAndCollectReferencedKeys_WithSkuAndKey_ShouldHaveNoValidationErrors() { + final String productDraftKey = "key"; + final ProductDraft productDraft = mock(ProductDraft.class); + when(productDraft.getKey()).thenReturn(productDraftKey); + + final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); + when(masterVariant.getKey()).thenReturn("key"); + when(masterVariant.getSku()).thenReturn("sku"); + when(productDraft.getMasterVariant()).thenReturn(masterVariant); + + final Set validDrafts = getValidDrafts(Collections.singletonList(productDraft)); + + assertThat(validDrafts).hasSize(1); + } + + @Test + void validateAndCollectReferencedKeys_WithDrafts_ShouldValidateCorrectly() { + final AttributeDraft productReferenceSetAttribute = + getReferenceSetAttributeDraft( + "foo", getProductReferenceWithId("foo"), getProductReferenceWithId("bar")); + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("foo", getProductReferenceWithId("foo")); + + final List attributes = + asList(null, productReferenceAttribute, productReferenceSetAttribute); + + final ProductVariantDraft validVariantDraft = + ProductVariantDraftBuilder.of() + .key("variantKey") + .sku("variantSku") + .attributes(attributes) + .build(); + final ProductVariantDraft invalidVariantDraft = + ProductVariantDraftBuilder.of().key("invalidVariant").attributes(attributes).build(); + + final ProductDraft validProductDraft = mock(ProductDraft.class); + when(validProductDraft.getKey()).thenReturn("validProductDraft"); + when(validProductDraft.getMasterVariant()).thenReturn(validVariantDraft); + when(validProductDraft.getVariants()).thenReturn(singletonList(validVariantDraft)); + + final ProductDraft inValidProductDraft1 = mock(ProductDraft.class); + when(inValidProductDraft1.getKey()).thenReturn("invalidProductDraft1"); + when(inValidProductDraft1.getMasterVariant()).thenReturn(null); + when(inValidProductDraft1.getVariants()).thenReturn(singletonList(validVariantDraft)); + + final ProductDraft inValidProductDraft2 = mock(ProductDraft.class); + when(inValidProductDraft2.getKey()).thenReturn("invalidProductDraft2"); + when(inValidProductDraft2.getMasterVariant()).thenReturn(invalidVariantDraft); + when(inValidProductDraft2.getVariants()).thenReturn(singletonList(invalidVariantDraft)); + + final List productDrafts = + asList( + null, + mock(ProductDraft.class), + validProductDraft, + inValidProductDraft1, + inValidProductDraft2); + + final ProductBatchValidator productBatchValidator = + new ProductBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = + productBatchValidator.validateAndCollectReferencedKeys(productDrafts); + + assertThat(pair.getLeft()).hasSize(1); + assertThat(pair.getLeft()).containsExactly(validProductDraft); + assertThat(pair.getRight().getProductKeys()).hasSize(2); + assertThat(pair.getRight().getProductKeys()).containsExactlyInAnyOrder("foo", "bar"); + + assertThat(errorCallBackMessages).hasSize(5); + assertThat(errorCallBackMessages) + .containsExactlyInAnyOrder( PRODUCT_DRAFT_IS_NULL, format(PRODUCT_DRAFT_KEY_NOT_SET, "null"), format(PRODUCT_VARIANT_DRAFT_IS_NULL, 0, inValidProductDraft1.getKey()), format(PRODUCT_VARIANT_DRAFT_SKU_NOT_SET, 0, inValidProductDraft2.getKey()), format(PRODUCT_VARIANT_DRAFT_SKU_NOT_SET, 1, inValidProductDraft2.getKey())); - } - + } - @Test - void validateAndCollectReferencedKeys_WithCustomerGroupRefsInPrices_ShouldCollectReferencesCorrectly() { - final PriceDraft priceDraft = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + @Test + void + validateAndCollectReferencedKeys_WithCustomerGroupRefsInPrices_ShouldCollectReferencesCorrectly() { + final PriceDraft priceDraft = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .custom(CustomFieldsDraft.ofTypeKeyAndJson("customTypeKey", new HashMap<>())) .customerGroup(CustomerGroup.referenceOfId("customerGroupKey")) .channel(ResourceIdentifier.ofKey("channelKey")) .build(); - final PriceDraft priceDraftWithBlankCustomerGroup = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceDraft priceDraftWithBlankCustomerGroup = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .customerGroup(CustomerGroup.referenceOfId(" ")) .build(); - final PriceDraft priceDraftWithNullCustomerGroupId = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceDraft priceDraftWithNullCustomerGroupId = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .customerGroup(CustomerGroup.referenceOfId(null)) .build(); - final PriceDraft priceDraftWithNullCustomerGroup = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + final PriceDraft priceDraftWithNullCustomerGroup = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) .customerGroup(null) .build(); - final ProductVariantDraft validVariantDraft = - ProductVariantDraftBuilder.of() - .key("variantKey") - .sku("variantSku") - .prices(asList(priceDraft, - priceDraftWithBlankCustomerGroup, - priceDraftWithNullCustomerGroup, - priceDraftWithNullCustomerGroupId)) - .build(); - - final ProductDraft validProductDraft = mock(ProductDraft.class); - when(validProductDraft.getKey()).thenReturn("validProductDraft"); - when(validProductDraft.getMasterVariant()).thenReturn(validVariantDraft); - when(validProductDraft.getVariants()).thenReturn(singletonList(validVariantDraft)); - - final List productDrafts = singletonList(validProductDraft); - - final ProductBatchValidator productBatchValidator = new ProductBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = - productBatchValidator.validateAndCollectReferencedKeys(productDrafts); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(pair.getLeft()).hasSize(1); - assertThat(pair.getLeft()).containsExactly(validProductDraft); - assertThat(pair.getRight().getTypeKeys()).containsExactly("customTypeKey"); - assertThat(pair.getRight().getChannelKeys()).containsExactly("channelKey"); - assertThat(pair.getRight().getCustomerGroupKeys()).containsExactly("customerGroupKey"); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyKeys_ShouldNotCollectKeys() { - final ProductDraft productDraft = mock(ProductDraft.class); - when(productDraft.getKey()).thenReturn("key"); - when(productDraft.getTaxCategory()).thenReturn(ResourceIdentifier.ofKey(EMPTY)); - when(productDraft.getState()).thenReturn(State.referenceOfId(EMPTY)); - final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); - when(masterVariant.getKey()).thenReturn("key"); - when(masterVariant.getSku()).thenReturn("sku"); - when(productDraft.getMasterVariant()).thenReturn(masterVariant); - - final ProductBatchValidator productBatchValidator = new ProductBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = - productBatchValidator.validateAndCollectReferencedKeys(singletonList(productDraft)); - - assertThat(pair.getLeft()).contains(productDraft); - assertThat(pair.getRight().getTaxCategoryKeys()).isEmpty(); - assertThat(pair.getRight().getStateKeys()).isEmpty(); - assertThat(errorCallBackMessages).hasSize(0); - } - - @Test - void validateAndCollectReferencedKeys_WithKeyValueDocumentAttDrafts_ShouldNotHaveKeyValidationError() { - final String uuid = UUID.randomUUID().toString(); - final String validIdentifier = "container|key"; - final String invalidIdentifier = "container-key"; - - final AttributeDraft referenceSetAttributeDraft = - getReferenceSetAttributeDraft("foo", - createReferenceObject(validIdentifier, CustomObject.referenceTypeId()), - createReferenceObject(uuid, CustomObject.referenceTypeId()), - createReferenceObject(invalidIdentifier, CustomObject.referenceTypeId())); - - final List attributes = asList(referenceSetAttributeDraft); - - final ProductVariantDraft validVariantDraft = ProductVariantDraftBuilder.of() - .key("variantKey") - .sku("variantSku") - .attributes(attributes) - .build(); - - final ProductDraft validProductDraft = mock(ProductDraft.class); - when(validProductDraft.getKey()).thenReturn("validProductDraft"); - when(validProductDraft.getMasterVariant()).thenReturn(validVariantDraft); - - final ProductBatchValidator productBatchValidator = new ProductBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = - productBatchValidator.validateAndCollectReferencedKeys(singletonList(validProductDraft)); - - assertThat(pair.getLeft()).hasSize(1); - assertThat(pair.getLeft()).containsExactly(validProductDraft); - - Set identifiers = pair.getRight().getCustomObjectCompositeIdentifiers(); - assertThat(identifiers).containsExactlyInAnyOrder(CustomObjectCompositeIdentifier.of(validIdentifier)); - - assertThat(errorCallBackMessages).hasSize(0); - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List productDrafts) { - final ProductBatchValidator productBatchValidator = new ProductBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = - productBatchValidator.validateAndCollectReferencedKeys(productDrafts); - return pair.getLeft(); - } + final ProductVariantDraft validVariantDraft = + ProductVariantDraftBuilder.of() + .key("variantKey") + .sku("variantSku") + .prices( + asList( + priceDraft, + priceDraftWithBlankCustomerGroup, + priceDraftWithNullCustomerGroup, + priceDraftWithNullCustomerGroupId)) + .build(); + + final ProductDraft validProductDraft = mock(ProductDraft.class); + when(validProductDraft.getKey()).thenReturn("validProductDraft"); + when(validProductDraft.getMasterVariant()).thenReturn(validVariantDraft); + when(validProductDraft.getVariants()).thenReturn(singletonList(validVariantDraft)); + + final List productDrafts = singletonList(validProductDraft); + + final ProductBatchValidator productBatchValidator = + new ProductBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = + productBatchValidator.validateAndCollectReferencedKeys(productDrafts); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(pair.getLeft()).hasSize(1); + assertThat(pair.getLeft()).containsExactly(validProductDraft); + assertThat(pair.getRight().getTypeKeys()).containsExactly("customTypeKey"); + assertThat(pair.getRight().getChannelKeys()).containsExactly("channelKey"); + assertThat(pair.getRight().getCustomerGroupKeys()).containsExactly("customerGroupKey"); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyKeys_ShouldNotCollectKeys() { + final ProductDraft productDraft = mock(ProductDraft.class); + when(productDraft.getKey()).thenReturn("key"); + when(productDraft.getTaxCategory()).thenReturn(ResourceIdentifier.ofKey(EMPTY)); + when(productDraft.getState()).thenReturn(State.referenceOfId(EMPTY)); + final ProductVariantDraft masterVariant = mock(ProductVariantDraft.class); + when(masterVariant.getKey()).thenReturn("key"); + when(masterVariant.getSku()).thenReturn("sku"); + when(productDraft.getMasterVariant()).thenReturn(masterVariant); + + final ProductBatchValidator productBatchValidator = + new ProductBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = + productBatchValidator.validateAndCollectReferencedKeys(singletonList(productDraft)); + + assertThat(pair.getLeft()).contains(productDraft); + assertThat(pair.getRight().getTaxCategoryKeys()).isEmpty(); + assertThat(pair.getRight().getStateKeys()).isEmpty(); + assertThat(errorCallBackMessages).hasSize(0); + } + + @Test + void + validateAndCollectReferencedKeys_WithKeyValueDocumentAttDrafts_ShouldNotHaveKeyValidationError() { + final String uuid = UUID.randomUUID().toString(); + final String validIdentifier = "container|key"; + final String invalidIdentifier = "container-key"; + + final AttributeDraft referenceSetAttributeDraft = + getReferenceSetAttributeDraft( + "foo", + createReferenceObject(validIdentifier, CustomObject.referenceTypeId()), + createReferenceObject(uuid, CustomObject.referenceTypeId()), + createReferenceObject(invalidIdentifier, CustomObject.referenceTypeId())); + + final List attributes = asList(referenceSetAttributeDraft); + + final ProductVariantDraft validVariantDraft = + ProductVariantDraftBuilder.of() + .key("variantKey") + .sku("variantSku") + .attributes(attributes) + .build(); + + final ProductDraft validProductDraft = mock(ProductDraft.class); + when(validProductDraft.getKey()).thenReturn("validProductDraft"); + when(validProductDraft.getMasterVariant()).thenReturn(validVariantDraft); + + final ProductBatchValidator productBatchValidator = + new ProductBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = + productBatchValidator.validateAndCollectReferencedKeys(singletonList(validProductDraft)); + + assertThat(pair.getLeft()).hasSize(1); + assertThat(pair.getLeft()).containsExactly(validProductDraft); + + Set identifiers = + pair.getRight().getCustomObjectCompositeIdentifiers(); + assertThat(identifiers) + .containsExactlyInAnyOrder(CustomObjectCompositeIdentifier.of(validIdentifier)); + + assertThat(errorCallBackMessages).hasSize(0); + } + + @Nonnull + private Set getValidDrafts(@Nonnull final List productDrafts) { + final ProductBatchValidator productBatchValidator = + new ProductBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, ProductBatchValidator.ReferencedKeys> pair = + productBatchValidator.validateAndCollectReferencedKeys(productDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/ProductSyncStatisticsTest.java b/src/test/java/com/commercetools/sync/products/helpers/ProductSyncStatisticsTest.java index bba696cd35..65420dcd50 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/ProductSyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/ProductSyncStatisticsTest.java @@ -1,136 +1,136 @@ package com.commercetools.sync.products.helpers; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncStatisticsTest { - private ProductSyncStatistics productSyncStatistics; - - @BeforeEach - void setup() { - productSyncStatistics = new ProductSyncStatistics(); - } - - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - productSyncStatistics.incrementCreated(1); - productSyncStatistics.incrementFailed(1); - productSyncStatistics.incrementUpdated(1); - productSyncStatistics.incrementProcessed(3); - - assertThat(productSyncStatistics.getReportMessage()).isEqualTo("Summary: 3 product(s) were processed in total " - + "(1 created, 1 updated, 1 failed to sync and 0 product(s) with missing reference(s))."); - } - - @Test - void getNumberOfProductsWithMissingParents_WithEmptyMap_ShouldReturn0() { - final int result = productSyncStatistics.getNumberOfProductsWithMissingParents(); - - assertThat(result).isZero(); - } - - @Test - void getNumberOfCategoriesWithMissingParents_WithNonEmptyNonDuplicateKeyMap_ShouldReturnCorrectValue() { - // preparation - productSyncStatistics.addMissingDependency("parent1", "key1"); - productSyncStatistics.addMissingDependency("parent1", "key2"); - - productSyncStatistics.addMissingDependency("parent1", "key3"); - productSyncStatistics.addMissingDependency("parent2", "key4"); - - // test - final int result = productSyncStatistics.getNumberOfProductsWithMissingParents(); - - // assert - assertThat(result).isEqualTo(4); - } - - @Test - void getNumberOfCategoriesWithMissingParents_WithNonEmptyDuplicateKeyMap_ShouldReturnCorrectValue() { - // preparation - productSyncStatistics.addMissingDependency("parent1", "key1"); - productSyncStatistics.addMissingDependency("parent1", "key2"); - - productSyncStatistics.addMissingDependency("parent1", "key3"); - productSyncStatistics.addMissingDependency("parent2", "key1"); - - // test - final int result = productSyncStatistics.getNumberOfProductsWithMissingParents(); - - // assert - assertThat(result).isEqualTo(3); - } - - @Test - void addMissingDependency_WithEmptyParentsAndChildren_ShouldAddKeys() { - // preparation - productSyncStatistics.addMissingDependency("", ""); - productSyncStatistics.addMissingDependency("foo", ""); - productSyncStatistics.addMissingDependency("", ""); - - // test - final int result = productSyncStatistics.getNumberOfProductsWithMissingParents(); - - // assert - assertThat(result).isOne(); - } - - @Test - void removeAndGetReferencingKeys_WithEmptyMap_ShouldGetNull() { - // test - final Set result = productSyncStatistics.removeAndGetReferencingKeys("foo"); - - // assert - assertThat(result).isNull(); - } - - @Test - void removeAndGetReferencingKeys_WithNonExistingKey_ShouldGetAndRemoveNoKey() { - // preparation - productSyncStatistics.addMissingDependency("bar", "a"); - productSyncStatistics.addMissingDependency("foo", "b"); - - // test - final Set result = productSyncStatistics.removeAndGetReferencingKeys("x"); - - // assert - assertThat(result).isNull(); - assertThat(productSyncStatistics.getNumberOfProductsWithMissingParents()).isEqualTo(2); - } - - @Test - void removeAndGetReferencingKeys_WithExistingKey_ShouldGetAndRemoveKey() { - // preparation - productSyncStatistics.addMissingDependency("bar", "a"); - productSyncStatistics.addMissingDependency("foo", "b"); - - // test - final Set result = productSyncStatistics.removeAndGetReferencingKeys("foo"); - - // assert - assertThat(result).containsExactly("b"); - assertThat(productSyncStatistics.getNumberOfProductsWithMissingParents()).isEqualTo(1); - } - - @Test - void removeAndGetReferencingKeys_WithExistingKey_ShouldGetAndRemoveAllKeys() { - // preparation - productSyncStatistics.addMissingDependency("bar", "a"); - productSyncStatistics.addMissingDependency("foo", "b"); - productSyncStatistics.addMissingDependency("foo", "c"); - - // test - final Set result = productSyncStatistics.removeAndGetReferencingKeys("foo"); - - // assert - assertThat(result).containsExactly("b", "c"); - assertThat(productSyncStatistics.getNumberOfProductsWithMissingParents()).isEqualTo(1); - } - - - + private ProductSyncStatistics productSyncStatistics; + + @BeforeEach + void setup() { + productSyncStatistics = new ProductSyncStatistics(); + } + + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + productSyncStatistics.incrementCreated(1); + productSyncStatistics.incrementFailed(1); + productSyncStatistics.incrementUpdated(1); + productSyncStatistics.incrementProcessed(3); + + assertThat(productSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 3 product(s) were processed in total " + + "(1 created, 1 updated, 1 failed to sync and 0 product(s) with missing reference(s))."); + } + + @Test + void getNumberOfProductsWithMissingParents_WithEmptyMap_ShouldReturn0() { + final int result = productSyncStatistics.getNumberOfProductsWithMissingParents(); + + assertThat(result).isZero(); + } + + @Test + void + getNumberOfCategoriesWithMissingParents_WithNonEmptyNonDuplicateKeyMap_ShouldReturnCorrectValue() { + // preparation + productSyncStatistics.addMissingDependency("parent1", "key1"); + productSyncStatistics.addMissingDependency("parent1", "key2"); + + productSyncStatistics.addMissingDependency("parent1", "key3"); + productSyncStatistics.addMissingDependency("parent2", "key4"); + + // test + final int result = productSyncStatistics.getNumberOfProductsWithMissingParents(); + + // assert + assertThat(result).isEqualTo(4); + } + + @Test + void + getNumberOfCategoriesWithMissingParents_WithNonEmptyDuplicateKeyMap_ShouldReturnCorrectValue() { + // preparation + productSyncStatistics.addMissingDependency("parent1", "key1"); + productSyncStatistics.addMissingDependency("parent1", "key2"); + + productSyncStatistics.addMissingDependency("parent1", "key3"); + productSyncStatistics.addMissingDependency("parent2", "key1"); + + // test + final int result = productSyncStatistics.getNumberOfProductsWithMissingParents(); + + // assert + assertThat(result).isEqualTo(3); + } + + @Test + void addMissingDependency_WithEmptyParentsAndChildren_ShouldAddKeys() { + // preparation + productSyncStatistics.addMissingDependency("", ""); + productSyncStatistics.addMissingDependency("foo", ""); + productSyncStatistics.addMissingDependency("", ""); + + // test + final int result = productSyncStatistics.getNumberOfProductsWithMissingParents(); + + // assert + assertThat(result).isOne(); + } + + @Test + void removeAndGetReferencingKeys_WithEmptyMap_ShouldGetNull() { + // test + final Set result = productSyncStatistics.removeAndGetReferencingKeys("foo"); + + // assert + assertThat(result).isNull(); + } + + @Test + void removeAndGetReferencingKeys_WithNonExistingKey_ShouldGetAndRemoveNoKey() { + // preparation + productSyncStatistics.addMissingDependency("bar", "a"); + productSyncStatistics.addMissingDependency("foo", "b"); + + // test + final Set result = productSyncStatistics.removeAndGetReferencingKeys("x"); + + // assert + assertThat(result).isNull(); + assertThat(productSyncStatistics.getNumberOfProductsWithMissingParents()).isEqualTo(2); + } + + @Test + void removeAndGetReferencingKeys_WithExistingKey_ShouldGetAndRemoveKey() { + // preparation + productSyncStatistics.addMissingDependency("bar", "a"); + productSyncStatistics.addMissingDependency("foo", "b"); + + // test + final Set result = productSyncStatistics.removeAndGetReferencingKeys("foo"); + + // assert + assertThat(result).containsExactly("b"); + assertThat(productSyncStatistics.getNumberOfProductsWithMissingParents()).isEqualTo(1); + } + + @Test + void removeAndGetReferencingKeys_WithExistingKey_ShouldGetAndRemoveAllKeys() { + // preparation + productSyncStatistics.addMissingDependency("bar", "a"); + productSyncStatistics.addMissingDependency("foo", "b"); + productSyncStatistics.addMissingDependency("foo", "c"); + + // test + final Set result = productSyncStatistics.removeAndGetReferencingKeys("foo"); + + // assert + assertThat(result).containsExactly("b", "c"); + assertThat(productSyncStatistics.getNumberOfProductsWithMissingParents()).isEqualTo(1); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/VariantReferenceResolverTest.java b/src/test/java/com/commercetools/sync/products/helpers/VariantReferenceResolverTest.java index da13226959..7ea9103924 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/VariantReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/VariantReferenceResolverTest.java @@ -1,5 +1,23 @@ package com.commercetools.sync.products.helpers; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockChannelService; +import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCategoryService; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomObjectService; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductService; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductTypeService; +import static com.commercetools.sync.products.ProductSyncMockUtils.getProductReferenceWithRandomId; +import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.services.ChannelService; @@ -24,9 +42,6 @@ import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.MoneyImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.math.BigDecimal; import java.util.Arrays; import java.util.HashMap; @@ -36,471 +51,450 @@ import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockChannelService; -import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockSupplyChannel; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCategoryService; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomObjectService; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductService; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductTypeService; -import static com.commercetools.sync.products.ProductSyncMockUtils.getProductReferenceWithRandomId; -import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class VariantReferenceResolverTest { - private static final String CHANNEL_KEY = "channel-key_1"; - private static final String CHANNEL_ID = UUID.randomUUID().toString(); - private static final String PRODUCT_ID = UUID.randomUUID().toString(); - private static final String PRODUCT_TYPE_ID = UUID.randomUUID().toString(); - private static final String CATEGORY_ID = UUID.randomUUID().toString(); - private static final String CUSTOM_OBJECT_ID = UUID.randomUUID().toString(); - - private VariantReferenceResolver referenceResolver; - - @BeforeEach - void setup() { - final TypeService typeService = getMockTypeService(); - final ChannelService channelService = getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, typeService, channelService, + private static final String CHANNEL_KEY = "channel-key_1"; + private static final String CHANNEL_ID = UUID.randomUUID().toString(); + private static final String PRODUCT_ID = UUID.randomUUID().toString(); + private static final String PRODUCT_TYPE_ID = UUID.randomUUID().toString(); + private static final String CATEGORY_ID = UUID.randomUUID().toString(); + private static final String CUSTOM_OBJECT_ID = UUID.randomUUID().toString(); + + private VariantReferenceResolver referenceResolver; + + @BeforeEach + void setup() { + final TypeService typeService = getMockTypeService(); + final ChannelService channelService = + getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, + typeService, + channelService, mock(CustomerGroupService.class), getMockProductService(PRODUCT_ID), getMockProductTypeService(PRODUCT_TYPE_ID), getMockCategoryService(CATEGORY_ID), getMockCustomObjectService(CUSTOM_OBJECT_ID)); - } - - @Test - void resolveReferences_WithNoAttributes_ShouldReturnEqualDraft() { - // preparation - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithEmptyAttributes_ShouldReturnEqualDraft() { - // preparation - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(emptyList()) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithANullAttribute_ShouldReturnDraftWithoutNullAttribute() { - // preparation - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes((AttributeDraft) null) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isEmpty(); - } - - @Test - void resolveReferences_WithTextAttribute_ShouldReturnEqualDraft() { - // preparation - final AttributeDraft textAttribute = AttributeDraft.of("attributeName", "attributeValue"); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(textAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNullAttributeValue_ShouldReturnEqualDraft() { - // preparation - final AttributeDraft textAttribute = AttributeDraft.of("attributeName", "attributeValue"); - final AttributeDraft nullAttributeValue = AttributeDraft.of("attributeName", null); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(textAttribute, nullAttributeValue) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithEmptyJsonNodeAttributeValue_ShouldReturnEqualDraft() { - // preparation - final AttributeDraft attributeWithEmptyValue = - AttributeDraft.of("attributeName", JsonNodeFactory.instance.objectNode()); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeWithEmptyValue) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithEmptySetAttribute_ShouldReturnEqualDraft() { - // preparation - final AttributeDraft attributeWithEmptyValue = - AttributeDraft.of("attributeName", JsonNodeFactory.instance.arrayNode()); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeWithEmptyValue) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNoPriceReferences_ShouldResolveAttributeReferences() { - // preparation - final ObjectNode productReferenceWithRandomId = getProductReferenceWithRandomId(); - final AttributeDraft productReferenceSetAttribute = - getReferenceSetAttributeDraft("foo", productReferenceWithRandomId); - - final AttributeDraft textAttribute = AttributeDraft.of("attributeName", "textValue"); - - final List attributeDrafts = - Arrays.asList(productReferenceSetAttribute, textAttribute); - - - final ProductVariantDraft variantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDrafts) - .build(); - - // test - final ProductVariantDraft resolvedDraft = referenceResolver.resolveReferences(variantDraft) - .toCompletableFuture().join(); - - // assertions - final List resolvedDraftPrices = resolvedDraft.getPrices(); - assertThat(resolvedDraftPrices).isNull(); - - final List resolvedBuilderAttributes = resolvedDraft.getAttributes(); - assertThat(resolvedBuilderAttributes).hasSize(2); - assertThat(resolvedBuilderAttributes).contains(textAttribute); - - final AttributeDraft resolvedProductReferenceSetAttribute = resolvedBuilderAttributes.get(0); - assertThat(resolvedProductReferenceSetAttribute).isNotNull(); - - final JsonNode resolvedProductReferenceSetValue = resolvedProductReferenceSetAttribute.getValue(); - assertThat(resolvedProductReferenceSetValue).isNotNull(); - - final JsonNode resolvedProductReferenceValue = resolvedProductReferenceSetValue.get(0); - assertThat(resolvedProductReferenceValue).isNotNull(); - - final JsonNode resolvedProductReferenceIdTextNode = resolvedProductReferenceValue.get(REFERENCE_ID_FIELD); - assertThat(resolvedProductReferenceIdTextNode).isNotNull(); - assertThat(resolvedProductReferenceIdTextNode.asText()).isEqualTo(PRODUCT_ID); - } - - @Test - void resolveReferences_WithMixedReferences_ShouldResolveReferenceAttributes() { - // preparation - final ObjectNode productReferenceWithRandomId = getProductReferenceWithRandomId(); - final AttributeDraft productReferenceSetAttribute = - getReferenceSetAttributeDraft("foo", productReferenceWithRandomId); - - final ObjectNode categoryReference = createReferenceObject("foo", Category.referenceTypeId()); - final ObjectNode productTypeReference = createReferenceObject("foo", ProductType.referenceTypeId()); - final ObjectNode customerReference = createReferenceObject("foo", Customer.referenceTypeId()); - final ObjectNode customObjectReference = - createReferenceObject("container|key", CustomObject.referenceTypeId()); - - final AttributeDraft categoryReferenceAttribute = AttributeDraft - .of("cat-ref", categoryReference); - final AttributeDraft productTypeReferenceAttribute = AttributeDraft - .of("productType-ref", productTypeReference); - final AttributeDraft customerReferenceAttribute = AttributeDraft - .of("customer-ref", customerReference); - final AttributeDraft textAttribute = AttributeDraft - .of("attributeName", "textValue"); - final AttributeDraft customObjectReferenceAttribute = AttributeDraft - .of("customObject-ref", customObjectReference); - - final List attributeDrafts = - Arrays.asList(productReferenceSetAttribute, - categoryReferenceAttribute, - productTypeReferenceAttribute, - customerReferenceAttribute, - textAttribute, - customObjectReferenceAttribute); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDrafts) - .build(); - - // test - final ProductVariantDraft resolvedBuilder = referenceResolver - .resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - final List resolvedBuilderAttributes = resolvedBuilder.getAttributes(); - assertThat(resolvedBuilderAttributes).hasSize(6); - assertThat(resolvedBuilderAttributes).contains(textAttribute); - - final AttributeDraft resolvedProductReferenceSetAttribute = resolvedBuilderAttributes.get(0); - assertThat(resolvedProductReferenceSetAttribute).isNotNull(); - - final JsonNode resolvedProductReferenceSetValue = resolvedProductReferenceSetAttribute.getValue(); - assertThat(resolvedProductReferenceSetValue).isNotNull(); - - final JsonNode resolvedProductReferenceValue = resolvedProductReferenceSetValue.get(0); - assertThat(resolvedProductReferenceValue).isNotNull(); - - final JsonNode resolvedProductReferenceIdTextNode = resolvedProductReferenceValue.get(REFERENCE_ID_FIELD); - assertThat(resolvedProductReferenceIdTextNode).isNotNull(); - assertThat(resolvedProductReferenceIdTextNode.asText()).isEqualTo(PRODUCT_ID); - - final AttributeDraft resolvedCategoryReferenceAttribute = resolvedBuilderAttributes.get(1); - assertThat(resolvedCategoryReferenceAttribute).isNotNull(); - - final JsonNode resolvedCategoryReferenceAttributeValue = resolvedCategoryReferenceAttribute.getValue(); - assertThat(resolvedCategoryReferenceAttributeValue).isNotNull(); - - assertThat(resolvedCategoryReferenceAttributeValue.get(REFERENCE_ID_FIELD).asText()).isEqualTo(CATEGORY_ID); - assertThat(resolvedCategoryReferenceAttributeValue.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Category.referenceTypeId()); - - final AttributeDraft resolvedProductTypeReferenceAttribute = resolvedBuilderAttributes.get(2); - assertThat(resolvedProductTypeReferenceAttribute).isNotNull(); - - final JsonNode resolvedProductTypeReferenceAttributeValue = resolvedProductTypeReferenceAttribute.getValue(); - assertThat(resolvedProductTypeReferenceAttributeValue).isNotNull(); - - assertThat(resolvedProductTypeReferenceAttributeValue.get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(PRODUCT_TYPE_ID); - assertThat(resolvedProductTypeReferenceAttributeValue.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - - final AttributeDraft resolvedCustomerReferenceAttribute = resolvedBuilderAttributes.get(3); - assertThat(resolvedCustomerReferenceAttribute).isNotNull(); - - final JsonNode resolvedCustomerReferenceAttributeValue = resolvedCustomerReferenceAttribute.getValue(); - assertThat(resolvedCustomerReferenceAttributeValue).isNotNull(); - - assertThat(resolvedCustomerReferenceAttributeValue.get(REFERENCE_ID_FIELD)) - .isEqualTo(customerReference.get(REFERENCE_ID_FIELD)); - assertThat(resolvedCustomerReferenceAttributeValue.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Customer.referenceTypeId()); - - final AttributeDraft resolvedCustomObjectReferenceAttribute = resolvedBuilderAttributes.get(5); - assertThat(resolvedCustomObjectReferenceAttribute).isNotNull(); - - final JsonNode resolvedCustomObjectAttributeValue = resolvedCustomObjectReferenceAttribute.getValue(); - assertThat(resolvedCustomObjectAttributeValue).isNotNull(); - - assertThat(resolvedCustomObjectAttributeValue.get(REFERENCE_ID_FIELD).asText()) - .isEqualTo(CUSTOM_OBJECT_ID); - assertThat(resolvedCustomObjectAttributeValue.get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(CustomObject.referenceTypeId()); - } - - @Test - void resolveReferences_WithNullReferenceInSetAttribute_ShouldResolveReferences() { - // preparation - final ObjectNode productReference = getProductReferenceWithRandomId(); - final AttributeDraft productReferenceAttribute = - getReferenceSetAttributeDraft("foo", productReference, null); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final Set resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toSet()); - - assertThat(resolvedSet).isNotEmpty(); - final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); - resolvedReference.put(REFERENCE_TYPE_ID_FIELD, Product.referenceTypeId()); - resolvedReference.put(REFERENCE_ID_FIELD, PRODUCT_ID); - assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference, JsonNodeFactory.instance.nullNode()); - } - - @Test - void resolveAssetsReferences_WithEmptyAssets_ShouldNotResolveAssets() { - final ProductVariantDraftBuilder productVariantDraftBuilder = ProductVariantDraftBuilder - .of() - .assets(emptyList()); - - final ProductVariantDraftBuilder resolvedBuilder = referenceResolver + } + + @Test + void resolveReferences_WithNoAttributes_ShouldReturnEqualDraft() { + // preparation + final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder.of().build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithEmptyAttributes_ShouldReturnEqualDraft() { + // preparation + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(emptyList()).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithANullAttribute_ShouldReturnDraftWithoutNullAttribute() { + // preparation + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes((AttributeDraft) null).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isEmpty(); + } + + @Test + void resolveReferences_WithTextAttribute_ShouldReturnEqualDraft() { + // preparation + final AttributeDraft textAttribute = AttributeDraft.of("attributeName", "attributeValue"); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(textAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithNullAttributeValue_ShouldReturnEqualDraft() { + // preparation + final AttributeDraft textAttribute = AttributeDraft.of("attributeName", "attributeValue"); + final AttributeDraft nullAttributeValue = AttributeDraft.of("attributeName", null); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(textAttribute, nullAttributeValue).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithEmptyJsonNodeAttributeValue_ShouldReturnEqualDraft() { + // preparation + final AttributeDraft attributeWithEmptyValue = + AttributeDraft.of("attributeName", JsonNodeFactory.instance.objectNode()); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeWithEmptyValue).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithEmptySetAttribute_ShouldReturnEqualDraft() { + // preparation + final AttributeDraft attributeWithEmptyValue = + AttributeDraft.of("attributeName", JsonNodeFactory.instance.arrayNode()); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeWithEmptyValue).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithNoPriceReferences_ShouldResolveAttributeReferences() { + // preparation + final ObjectNode productReferenceWithRandomId = getProductReferenceWithRandomId(); + final AttributeDraft productReferenceSetAttribute = + getReferenceSetAttributeDraft("foo", productReferenceWithRandomId); + + final AttributeDraft textAttribute = AttributeDraft.of("attributeName", "textValue"); + + final List attributeDrafts = + Arrays.asList(productReferenceSetAttribute, textAttribute); + + final ProductVariantDraft variantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDrafts).build(); + + // test + final ProductVariantDraft resolvedDraft = + referenceResolver.resolveReferences(variantDraft).toCompletableFuture().join(); + + // assertions + final List resolvedDraftPrices = resolvedDraft.getPrices(); + assertThat(resolvedDraftPrices).isNull(); + + final List resolvedBuilderAttributes = resolvedDraft.getAttributes(); + assertThat(resolvedBuilderAttributes).hasSize(2); + assertThat(resolvedBuilderAttributes).contains(textAttribute); + + final AttributeDraft resolvedProductReferenceSetAttribute = resolvedBuilderAttributes.get(0); + assertThat(resolvedProductReferenceSetAttribute).isNotNull(); + + final JsonNode resolvedProductReferenceSetValue = + resolvedProductReferenceSetAttribute.getValue(); + assertThat(resolvedProductReferenceSetValue).isNotNull(); + + final JsonNode resolvedProductReferenceValue = resolvedProductReferenceSetValue.get(0); + assertThat(resolvedProductReferenceValue).isNotNull(); + + final JsonNode resolvedProductReferenceIdTextNode = + resolvedProductReferenceValue.get(REFERENCE_ID_FIELD); + assertThat(resolvedProductReferenceIdTextNode).isNotNull(); + assertThat(resolvedProductReferenceIdTextNode.asText()).isEqualTo(PRODUCT_ID); + } + + @Test + void resolveReferences_WithMixedReferences_ShouldResolveReferenceAttributes() { + // preparation + final ObjectNode productReferenceWithRandomId = getProductReferenceWithRandomId(); + final AttributeDraft productReferenceSetAttribute = + getReferenceSetAttributeDraft("foo", productReferenceWithRandomId); + + final ObjectNode categoryReference = createReferenceObject("foo", Category.referenceTypeId()); + final ObjectNode productTypeReference = + createReferenceObject("foo", ProductType.referenceTypeId()); + final ObjectNode customerReference = createReferenceObject("foo", Customer.referenceTypeId()); + final ObjectNode customObjectReference = + createReferenceObject("container|key", CustomObject.referenceTypeId()); + + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("cat-ref", categoryReference); + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("productType-ref", productTypeReference); + final AttributeDraft customerReferenceAttribute = + AttributeDraft.of("customer-ref", customerReference); + final AttributeDraft textAttribute = AttributeDraft.of("attributeName", "textValue"); + final AttributeDraft customObjectReferenceAttribute = + AttributeDraft.of("customObject-ref", customObjectReference); + + final List attributeDrafts = + Arrays.asList( + productReferenceSetAttribute, + categoryReferenceAttribute, + productTypeReferenceAttribute, + customerReferenceAttribute, + textAttribute, + customObjectReferenceAttribute); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDrafts).build(); + + // test + final ProductVariantDraft resolvedBuilder = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + final List resolvedBuilderAttributes = resolvedBuilder.getAttributes(); + assertThat(resolvedBuilderAttributes).hasSize(6); + assertThat(resolvedBuilderAttributes).contains(textAttribute); + + final AttributeDraft resolvedProductReferenceSetAttribute = resolvedBuilderAttributes.get(0); + assertThat(resolvedProductReferenceSetAttribute).isNotNull(); + + final JsonNode resolvedProductReferenceSetValue = + resolvedProductReferenceSetAttribute.getValue(); + assertThat(resolvedProductReferenceSetValue).isNotNull(); + + final JsonNode resolvedProductReferenceValue = resolvedProductReferenceSetValue.get(0); + assertThat(resolvedProductReferenceValue).isNotNull(); + + final JsonNode resolvedProductReferenceIdTextNode = + resolvedProductReferenceValue.get(REFERENCE_ID_FIELD); + assertThat(resolvedProductReferenceIdTextNode).isNotNull(); + assertThat(resolvedProductReferenceIdTextNode.asText()).isEqualTo(PRODUCT_ID); + + final AttributeDraft resolvedCategoryReferenceAttribute = resolvedBuilderAttributes.get(1); + assertThat(resolvedCategoryReferenceAttribute).isNotNull(); + + final JsonNode resolvedCategoryReferenceAttributeValue = + resolvedCategoryReferenceAttribute.getValue(); + assertThat(resolvedCategoryReferenceAttributeValue).isNotNull(); + + assertThat(resolvedCategoryReferenceAttributeValue.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(CATEGORY_ID); + assertThat(resolvedCategoryReferenceAttributeValue.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + + final AttributeDraft resolvedProductTypeReferenceAttribute = resolvedBuilderAttributes.get(2); + assertThat(resolvedProductTypeReferenceAttribute).isNotNull(); + + final JsonNode resolvedProductTypeReferenceAttributeValue = + resolvedProductTypeReferenceAttribute.getValue(); + assertThat(resolvedProductTypeReferenceAttributeValue).isNotNull(); + + assertThat(resolvedProductTypeReferenceAttributeValue.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(PRODUCT_TYPE_ID); + assertThat(resolvedProductTypeReferenceAttributeValue.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + + final AttributeDraft resolvedCustomerReferenceAttribute = resolvedBuilderAttributes.get(3); + assertThat(resolvedCustomerReferenceAttribute).isNotNull(); + + final JsonNode resolvedCustomerReferenceAttributeValue = + resolvedCustomerReferenceAttribute.getValue(); + assertThat(resolvedCustomerReferenceAttributeValue).isNotNull(); + + assertThat(resolvedCustomerReferenceAttributeValue.get(REFERENCE_ID_FIELD)) + .isEqualTo(customerReference.get(REFERENCE_ID_FIELD)); + assertThat(resolvedCustomerReferenceAttributeValue.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Customer.referenceTypeId()); + + final AttributeDraft resolvedCustomObjectReferenceAttribute = resolvedBuilderAttributes.get(5); + assertThat(resolvedCustomObjectReferenceAttribute).isNotNull(); + + final JsonNode resolvedCustomObjectAttributeValue = + resolvedCustomObjectReferenceAttribute.getValue(); + assertThat(resolvedCustomObjectAttributeValue).isNotNull(); + + assertThat(resolvedCustomObjectAttributeValue.get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(CUSTOM_OBJECT_ID); + assertThat(resolvedCustomObjectAttributeValue.get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(CustomObject.referenceTypeId()); + } + + @Test + void resolveReferences_WithNullReferenceInSetAttribute_ShouldResolveReferences() { + // preparation + final ObjectNode productReference = getProductReferenceWithRandomId(); + final AttributeDraft productReferenceAttribute = + getReferenceSetAttributeDraft("foo", productReference, null); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final Set resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toSet()); + + assertThat(resolvedSet).isNotEmpty(); + final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); + resolvedReference.put(REFERENCE_TYPE_ID_FIELD, Product.referenceTypeId()); + resolvedReference.put(REFERENCE_ID_FIELD, PRODUCT_ID); + assertThat(resolvedSet) + .containsExactlyInAnyOrder(resolvedReference, JsonNodeFactory.instance.nullNode()); + } + + @Test + void resolveAssetsReferences_WithEmptyAssets_ShouldNotResolveAssets() { + final ProductVariantDraftBuilder productVariantDraftBuilder = + ProductVariantDraftBuilder.of().assets(emptyList()); + + final ProductVariantDraftBuilder resolvedBuilder = + referenceResolver .resolveAssetsReferences(productVariantDraftBuilder) - .toCompletableFuture().join(); + .toCompletableFuture() + .join(); - final List resolvedBuilderAssets = resolvedBuilder.getAssets(); - assertThat(resolvedBuilderAssets).isEmpty(); - } + final List resolvedBuilderAssets = resolvedBuilder.getAssets(); + assertThat(resolvedBuilderAssets).isEmpty(); + } - @Test - void resolveAssetsReferences_WithNullAssets_ShouldNotResolveAssets() { - final ProductVariantDraftBuilder productVariantDraftBuilder = ProductVariantDraftBuilder.of(); + @Test + void resolveAssetsReferences_WithNullAssets_ShouldNotResolveAssets() { + final ProductVariantDraftBuilder productVariantDraftBuilder = ProductVariantDraftBuilder.of(); - final ProductVariantDraftBuilder resolvedBuilder = referenceResolver + final ProductVariantDraftBuilder resolvedBuilder = + referenceResolver .resolveAssetsReferences(productVariantDraftBuilder) - .toCompletableFuture().join(); + .toCompletableFuture() + .join(); - final List resolvedBuilderAssets = resolvedBuilder.getAssets(); - assertThat(resolvedBuilderAssets).isNull(); - } + final List resolvedBuilderAssets = resolvedBuilder.getAssets(); + assertThat(resolvedBuilderAssets).isNull(); + } - @Test - void resolveAssetsReferences_WithANullAsset_ShouldNotResolveAssets() { - final ProductVariantDraftBuilder productVariantDraftBuilder = - ProductVariantDraftBuilder.of().assets(singletonList(null)); + @Test + void resolveAssetsReferences_WithANullAsset_ShouldNotResolveAssets() { + final ProductVariantDraftBuilder productVariantDraftBuilder = + ProductVariantDraftBuilder.of().assets(singletonList(null)); - final ProductVariantDraftBuilder resolvedBuilder = referenceResolver + final ProductVariantDraftBuilder resolvedBuilder = + referenceResolver .resolveAssetsReferences(productVariantDraftBuilder) - .toCompletableFuture().join(); - - final List resolvedBuilderAssets = resolvedBuilder.getAssets(); - assertThat(resolvedBuilderAssets).isEmpty(); - } - - @Test - void resolveAssetsReferences_WithAssetReferences_ShouldResolveAssets() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("customTypeId", new HashMap<>()); + .toCompletableFuture() + .join(); - final AssetDraft assetDraft = AssetDraftBuilder - .of(emptyList(), ofEnglish("assetName")) - .custom(customFieldsDraft) - .build(); - - final ProductVariantDraftBuilder productVariantDraftBuilder = - ProductVariantDraftBuilder.of().assets(singletonList(assetDraft)); + final List resolvedBuilderAssets = resolvedBuilder.getAssets(); + assertThat(resolvedBuilderAssets).isEmpty(); + } - final ProductVariantDraftBuilder resolvedBuilder = referenceResolver - .resolveAssetsReferences(productVariantDraftBuilder).toCompletableFuture().join(); + @Test + void resolveAssetsReferences_WithAssetReferences_ShouldResolveAssets() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("customTypeId", new HashMap<>()); - final List resolvedBuilderAssets = resolvedBuilder.getAssets(); - assertThat(resolvedBuilderAssets).isNotEmpty(); - final AssetDraft resolvedAssetDraft = resolvedBuilderAssets.get(0); - assertThat(resolvedAssetDraft).isNotNull(); - assertThat(resolvedAssetDraft.getCustom()).isNotNull(); - assertThat(resolvedAssetDraft.getCustom().getType().getId()).isEqualTo(REFERENCE_TYPE_ID_FIELD); - } + final AssetDraft assetDraft = + AssetDraftBuilder.of(emptyList(), ofEnglish("assetName")).custom(customFieldsDraft).build(); - @Test - void resolvePricesReferences_WithNullPrices_ShouldNotResolvePrices() { - final ProductVariantDraftBuilder productVariantDraftBuilder = ProductVariantDraftBuilder.of(); + final ProductVariantDraftBuilder productVariantDraftBuilder = + ProductVariantDraftBuilder.of().assets(singletonList(assetDraft)); - final ProductVariantDraftBuilder resolvedBuilder = referenceResolver + final ProductVariantDraftBuilder resolvedBuilder = + referenceResolver + .resolveAssetsReferences(productVariantDraftBuilder) + .toCompletableFuture() + .join(); + + final List resolvedBuilderAssets = resolvedBuilder.getAssets(); + assertThat(resolvedBuilderAssets).isNotEmpty(); + final AssetDraft resolvedAssetDraft = resolvedBuilderAssets.get(0); + assertThat(resolvedAssetDraft).isNotNull(); + assertThat(resolvedAssetDraft.getCustom()).isNotNull(); + assertThat(resolvedAssetDraft.getCustom().getType().getId()).isEqualTo(REFERENCE_TYPE_ID_FIELD); + } + + @Test + void resolvePricesReferences_WithNullPrices_ShouldNotResolvePrices() { + final ProductVariantDraftBuilder productVariantDraftBuilder = ProductVariantDraftBuilder.of(); + + final ProductVariantDraftBuilder resolvedBuilder = + referenceResolver .resolvePricesReferences(productVariantDraftBuilder) - .toCompletableFuture().join(); + .toCompletableFuture() + .join(); - final List resolvedBuilderPrices = resolvedBuilder.getPrices(); - assertThat(resolvedBuilderPrices).isNull(); - } + final List resolvedBuilderPrices = resolvedBuilder.getPrices(); + assertThat(resolvedBuilderPrices).isNull(); + } - @Test - void resolvePricesReferences_WithANullPrice_ShouldNotResolvePrices() { - final ProductVariantDraftBuilder productVariantDraftBuilder = - ProductVariantDraftBuilder.of().prices((PriceDraft) null); + @Test + void resolvePricesReferences_WithANullPrice_ShouldNotResolvePrices() { + final ProductVariantDraftBuilder productVariantDraftBuilder = + ProductVariantDraftBuilder.of().prices((PriceDraft) null); - final ProductVariantDraftBuilder resolvedBuilder = referenceResolver + final ProductVariantDraftBuilder resolvedBuilder = + referenceResolver .resolvePricesReferences(productVariantDraftBuilder) - .toCompletableFuture().join(); + .toCompletableFuture() + .join(); - final List resolvedBuilderPrices = resolvedBuilder.getPrices(); - assertThat(resolvedBuilderPrices).isEmpty(); - } + final List resolvedBuilderPrices = resolvedBuilder.getPrices(); + assertThat(resolvedBuilderPrices).isEmpty(); + } - @Test - void resolvePricesReferences_WithPriceReferences_ShouldResolvePrices() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("customTypeId", new HashMap<>()); + @Test + void resolvePricesReferences_WithPriceReferences_ShouldResolvePrices() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("customTypeId", new HashMap<>()); - final PriceDraft priceDraft = PriceDraftBuilder - .of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) - .custom(customFieldsDraft).build(); + final PriceDraft priceDraft = + PriceDraftBuilder.of(MoneyImpl.of(BigDecimal.TEN, DefaultCurrencyUnits.EUR)) + .custom(customFieldsDraft) + .build(); - final ProductVariantDraftBuilder productVariantDraftBuilder = - ProductVariantDraftBuilder.of().prices(priceDraft); + final ProductVariantDraftBuilder productVariantDraftBuilder = + ProductVariantDraftBuilder.of().prices(priceDraft); - final ProductVariantDraftBuilder resolvedBuilder = referenceResolver + final ProductVariantDraftBuilder resolvedBuilder = + referenceResolver .resolvePricesReferences(productVariantDraftBuilder) - .toCompletableFuture().join(); - - final List resolvedBuilderPrices = resolvedBuilder.getPrices(); - assertThat(resolvedBuilderPrices).isNotEmpty(); - final PriceDraft resolvedPriceDraft = resolvedBuilderPrices.get(0); - assertThat(resolvedPriceDraft).isNotNull(); - assertThat(resolvedPriceDraft.getCustom()).isNotNull(); - assertThat(resolvedPriceDraft.getCustom().getType().getId()).isEqualTo(REFERENCE_TYPE_ID_FIELD); - } + .toCompletableFuture() + .join(); + + final List resolvedBuilderPrices = resolvedBuilder.getPrices(); + assertThat(resolvedBuilderPrices).isNotEmpty(); + final PriceDraft resolvedPriceDraft = resolvedBuilderPrices.get(0); + assertThat(resolvedPriceDraft).isNotNull(); + assertThat(resolvedPriceDraft.getCustom()).isNotNull(); + assertThat(resolvedPriceDraft.getCustom().getType().getId()).isEqualTo(REFERENCE_TYPE_ID_FIELD); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/CategoryReferenceResolverTest.java b/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/CategoryReferenceResolverTest.java index 96ff1e87c2..40f446d401 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/CategoryReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/CategoryReferenceResolverTest.java @@ -1,30 +1,5 @@ package com.commercetools.sync.products.helpers.productreferenceresolver; -import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; -import com.commercetools.sync.products.ProductSyncOptions; -import com.commercetools.sync.products.ProductSyncOptionsBuilder; -import com.commercetools.sync.products.helpers.ProductReferenceResolver; -import com.commercetools.sync.services.CategoryService; -import com.commercetools.sync.services.CustomerGroupService; -import io.sphere.sdk.categories.Category; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.models.SphereException; -import io.sphere.sdk.products.CategoryOrderHints; -import io.sphere.sdk.products.ProductDraftBuilder; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - import static com.commercetools.sync.categories.CategorySyncMockUtils.getMockCategory; import static com.commercetools.sync.commons.MockUtils.getMockTypeService; import static com.commercetools.sync.commons.MockUtils.mockCategoryService; @@ -49,274 +24,365 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; +import com.commercetools.sync.products.ProductSyncOptions; +import com.commercetools.sync.products.ProductSyncOptionsBuilder; +import com.commercetools.sync.products.helpers.ProductReferenceResolver; +import com.commercetools.sync.services.CategoryService; +import com.commercetools.sync.services.CustomerGroupService; +import io.sphere.sdk.categories.Category; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.models.SphereException; +import io.sphere.sdk.products.CategoryOrderHints; +import io.sphere.sdk.products.ProductDraftBuilder; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + class CategoryReferenceResolverTest { - private static final String CHANNEL_KEY = "channel-key_1"; - private static final String CHANNEL_ID = "1"; - private static final String PRODUCT_TYPE_ID = "productTypeId"; - private static final String TAX_CATEGORY_ID = "taxCategoryId"; - private static final String STATE_ID = "stateId"; - private static final String PRODUCT_ID = "productId"; - private static final String CUSTOM_OBJECT_ID = "customObjectId"; - - @Test - void resolveCategoryReferences_WithCategoryKeysAndCategoryOrderHints_ShouldResolveReferences() { - // preparation - final int nCategories = 10; - final List categories = IntStream.range(0, nCategories) - .mapToObj(i -> i + "") - .map(key -> getMockCategory(key, key)) - .collect(Collectors.toList()); - - final CategoryService mockCategoryService = mockCategoryService(new HashSet<>(categories), null); - - final Set> categoryResourceIdentifiers = - categories.stream() - .map(category -> ResourceIdentifier.ofKey(category.getKey())) - .collect(toSet()); - - final Map categoryOrderHintValues = - categories.stream() - .collect(Collectors.toMap(Category::getKey, (category -> "0.00" + category.getId()))); - - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + private static final String CHANNEL_KEY = "channel-key_1"; + private static final String CHANNEL_ID = "1"; + private static final String PRODUCT_TYPE_ID = "productTypeId"; + private static final String TAX_CATEGORY_ID = "taxCategoryId"; + private static final String STATE_ID = "stateId"; + private static final String PRODUCT_ID = "productId"; + private static final String CUSTOM_OBJECT_ID = "customObjectId"; + + @Test + void resolveCategoryReferences_WithCategoryKeysAndCategoryOrderHints_ShouldResolveReferences() { + // preparation + final int nCategories = 10; + final List categories = + IntStream.range(0, nCategories) + .mapToObj(i -> i + "") + .map(key -> getMockCategory(key, key)) + .collect(Collectors.toList()); + + final CategoryService mockCategoryService = + mockCategoryService(new HashSet<>(categories), null); + + final Set> categoryResourceIdentifiers = + categories.stream() + .map(category -> ResourceIdentifier.ofKey(category.getKey())) + .collect(toSet()); + + final Map categoryOrderHintValues = + categories.stream() + .collect(Collectors.toMap(Category::getKey, (category -> "0.00" + category.getId()))); + + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .categories(categoryResourceIdentifiers) .categoryOrderHints(CategoryOrderHints.of(categoryOrderHintValues)); + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); - - // test - final ProductDraftBuilder resolvedDraft = productReferenceResolver.resolveCategoryReferences(productBuilder) - .toCompletableFuture().join(); + // test + final ProductDraftBuilder resolvedDraft = + productReferenceResolver + .resolveCategoryReferences(productBuilder) + .toCompletableFuture() + .join(); - // assertion - assertThat(resolvedDraft.getCategories()) - .hasSameElementsAs(categoryResourceIdentifiers - .stream() - .map(categoryResourceIdentifier -> Category.referenceOfId(categoryResourceIdentifier.getKey())) + // assertion + assertThat(resolvedDraft.getCategories()) + .hasSameElementsAs( + categoryResourceIdentifiers.stream() + .map( + categoryResourceIdentifier -> + Category.referenceOfId(categoryResourceIdentifier.getKey())) .collect(Collectors.toSet())); - assertThat(resolvedDraft.getCategoryOrderHints()).isNotNull(); - assertThat(resolvedDraft.getCategoryOrderHints().getAsMap()).hasSize(categoryOrderHintValues.size()); - } - - @Test - void resolveCategoryReferences_WithCategoryKeysAndNoCategoryOrderHints_ShouldResolveReferences() { - // preparation - final int nCategories = 10; - final List categories = IntStream.range(0, nCategories) - .mapToObj(i -> i + "") - .map(key -> getMockCategory(key, key)) - .collect(Collectors.toList()); - final CategoryService mockCategoryService = mockCategoryService(new HashSet<>(categories), null); - - final Set> categoryResourceIdentifiers = - categories.stream() - .map(category -> ResourceIdentifier.ofKey(category.getKey())) - .collect(toSet()); - - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .categories(categoryResourceIdentifiers); - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); - - // test - final ProductDraftBuilder resolvedDraft = productReferenceResolver.resolveCategoryReferences(productBuilder) - .toCompletableFuture().join(); - - // assertion - assertThat(resolvedDraft.getCategories()) - .hasSameElementsAs(categoryResourceIdentifiers.stream().map(categoryResourceIdentifier -> - Category.referenceOfId(categoryResourceIdentifier.getKey())).collect(toSet())); - assertThat(resolvedDraft.getCategoryOrderHints()).isNotNull(); - assertThat(resolvedDraft.getCategoryOrderHints().getAsMap()).isEmpty(); - } - - @Test - void resolveCategoryReferences_WithCategoryKeysAndSomeCategoryOrderHints_ShouldResolveReferences() { - // preparation - final int nCategories = 10; - final List categories = IntStream.range(0, nCategories) - .mapToObj(i -> i + "") - .map(key -> getMockCategory(key, key)) - .collect(Collectors.toList()); - final CategoryService mockCategoryService = mockCategoryService(new HashSet<>(categories), null); - - final Set> categoryResourceIdentifiers = - categories.stream() - .map(category -> ResourceIdentifier.ofKey(category.getKey())) - .collect(toSet()); - - final Map categoryOrderHintValues = categories.stream().limit(3) - .collect(Collectors.toMap(Category::getKey, - (category -> "0.00" + category.getId()))); - final CategoryOrderHints categoryOrderHints = CategoryOrderHints.of(categoryOrderHintValues); - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + assertThat(resolvedDraft.getCategoryOrderHints()).isNotNull(); + assertThat(resolvedDraft.getCategoryOrderHints().getAsMap()) + .hasSize(categoryOrderHintValues.size()); + } + + @Test + void resolveCategoryReferences_WithCategoryKeysAndNoCategoryOrderHints_ShouldResolveReferences() { + // preparation + final int nCategories = 10; + final List categories = + IntStream.range(0, nCategories) + .mapToObj(i -> i + "") + .map(key -> getMockCategory(key, key)) + .collect(Collectors.toList()); + final CategoryService mockCategoryService = + mockCategoryService(new HashSet<>(categories), null); + + final Set> categoryResourceIdentifiers = + categories.stream() + .map(category -> ResourceIdentifier.ofKey(category.getKey())) + .collect(toSet()); + + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().categories(categoryResourceIdentifiers); + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); + + // test + final ProductDraftBuilder resolvedDraft = + productReferenceResolver + .resolveCategoryReferences(productBuilder) + .toCompletableFuture() + .join(); + + // assertion + assertThat(resolvedDraft.getCategories()) + .hasSameElementsAs( + categoryResourceIdentifiers.stream() + .map( + categoryResourceIdentifier -> + Category.referenceOfId(categoryResourceIdentifier.getKey())) + .collect(toSet())); + assertThat(resolvedDraft.getCategoryOrderHints()).isNotNull(); + assertThat(resolvedDraft.getCategoryOrderHints().getAsMap()).isEmpty(); + } + + @Test + void + resolveCategoryReferences_WithCategoryKeysAndSomeCategoryOrderHints_ShouldResolveReferences() { + // preparation + final int nCategories = 10; + final List categories = + IntStream.range(0, nCategories) + .mapToObj(i -> i + "") + .map(key -> getMockCategory(key, key)) + .collect(Collectors.toList()); + final CategoryService mockCategoryService = + mockCategoryService(new HashSet<>(categories), null); + + final Set> categoryResourceIdentifiers = + categories.stream() + .map(category -> ResourceIdentifier.ofKey(category.getKey())) + .collect(toSet()); + + final Map categoryOrderHintValues = + categories.stream() + .limit(3) + .collect(Collectors.toMap(Category::getKey, (category -> "0.00" + category.getId()))); + final CategoryOrderHints categoryOrderHints = CategoryOrderHints.of(categoryOrderHintValues); + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .categories(categoryResourceIdentifiers) .categoryOrderHints(categoryOrderHints); - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); - - // test - final ProductDraftBuilder resolvedDraft = productReferenceResolver.resolveCategoryReferences(productBuilder) - .toCompletableFuture().join(); - - // assertion - assertThat(resolvedDraft.getCategories()) - .hasSameElementsAs(categoryResourceIdentifiers.stream().map(categoryResourceIdentifier -> - Category.referenceOfId(categoryResourceIdentifier.getKey())).collect(toSet())); - assertThat(resolvedDraft.getCategoryOrderHints()).isNotNull(); - assertThat(resolvedDraft.getCategoryOrderHints().getAsMap()).hasSize(3); - } - - @Test - void resolveCategoryReferences_WithNullCategoryReferences_ShouldNotResolveReferences() { - // preparation - final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType(); - - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); - - // test and assertion - assertThat(productReferenceResolver.resolveCategoryReferences(productBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> resolvedDraft.getCategories().isEmpty()); - } - - @Test - void resolveCategoryReferences_WithNonExistentCategoryReferences_ShouldNotResolveReferences() { - // preparation - final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); - - final Set> categories = new HashSet<>(); - categories.add(ResourceIdentifier.ofKey("non-existent-category-1")); - categories.add(ResourceIdentifier.ofKey("non-existent-category-2")); - - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .key("dummyKey") - .categories(categories); - - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); - - // test and assertion - final String expectedMessageWithCause = format(FAILED_TO_RESOLVE_REFERENCE, Category.resourceTypeId(), - "dummyKey", format(CATEGORIES_DO_NOT_EXIST, "non-existent-category-1, non-existent-category-2")); + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); + // test + final ProductDraftBuilder resolvedDraft = productReferenceResolver .resolveCategoryReferences(productBuilder) - .exceptionally(exception -> { - assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(exception.getCause().getMessage()) - .isEqualTo(expectedMessageWithCause); - return null; - }) .toCompletableFuture() .join(); - } - - @Test - void resolveCategoryReferences_WithNullKeyOnCategoryReference_ShouldNotResolveReference() { - // preparation - final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .categories(singleton(ResourceIdentifier.ofKey(null))); - - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); - - // test and assertion - assertThat(productReferenceResolver.resolveCategoryReferences(productBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'category' resource identifier on ProductDraft with " - + "key:'%s'. Reason: %s", productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCategoryReferences_WithEmptyKeyOnCategoryReference_ShouldNotResolveReference() { - // preparation - final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .categories(singleton(ResourceIdentifier.ofKey(""))); - - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); - - // test and assertion - assertThat(productReferenceResolver.resolveCategoryReferences(productBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'category' resource identifier on ProductDraft with " - + "key:'%s'. Reason: %s", productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCategoryReferences_WithCategoryReferencesWithId_ShouldResolveReference() { - // preparation - final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .categories(singleton(ResourceIdentifier.ofId("existing-category-id"))); - - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); - - final ProductDraftBuilder resolvedDraft = productReferenceResolver.resolveCategoryReferences(productBuilder) - .toCompletableFuture().join(); - - // assertion - assertThat(resolvedDraft.getCategories()) - .isEqualTo(singleton(ResourceIdentifier.ofId("existing-category-id"))); - } - - @Test - void resolveCategoryReferences_WithExceptionCategoryFetch_ShouldNotResolveReference() { - final Category category = getMockCategory("categoryKey", "categoryKey"); - final List categories = Collections.singletonList(category); - - final Set> categoryResourceIdentifiers = - categories.stream() - .map(cat -> ResourceIdentifier.ofKey(cat.getKey())) - .collect(toSet()); - - final CategoryService mockCategoryService = mockCategoryService(new HashSet<>(categories), null); - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .categories(categoryResourceIdentifiers); - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(mockCategoryService.fetchMatchingCategoriesByKeys(anySet())).thenReturn(futureThrowingSphereException); + // assertion + assertThat(resolvedDraft.getCategories()) + .hasSameElementsAs( + categoryResourceIdentifiers.stream() + .map( + categoryResourceIdentifier -> + Category.referenceOfId(categoryResourceIdentifier.getKey())) + .collect(toSet())); + assertThat(resolvedDraft.getCategoryOrderHints()).isNotNull(); + assertThat(resolvedDraft.getCategoryOrderHints().getAsMap()).hasSize(3); + } + + @Test + void resolveCategoryReferences_WithNullCategoryReferences_ShouldNotResolveReferences() { + // preparation + final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); + final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType(); + + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); + + // test and assertion + assertThat( + productReferenceResolver + .resolveCategoryReferences(productBuilder) + .toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching(resolvedDraft -> resolvedDraft.getCategories().isEmpty()); + } + + @Test + void resolveCategoryReferences_WithNonExistentCategoryReferences_ShouldNotResolveReferences() { + // preparation + final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); + + final Set> categories = new HashSet<>(); + categories.add(ResourceIdentifier.ofKey("non-existent-category-1")); + categories.add(ResourceIdentifier.ofKey("non-existent-category-2")); + + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().key("dummyKey").categories(categories); + + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); + + // test and assertion + final String expectedMessageWithCause = + format( + FAILED_TO_RESOLVE_REFERENCE, + Category.resourceTypeId(), + "dummyKey", + format(CATEGORIES_DO_NOT_EXIST, "non-existent-category-1, non-existent-category-2")); + + productReferenceResolver + .resolveCategoryReferences(productBuilder) + .exceptionally( + exception -> { + assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(exception.getCause().getMessage()).isEqualTo(expectedMessageWithCause); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void resolveCategoryReferences_WithNullKeyOnCategoryReference_ShouldNotResolveReference() { + // preparation + final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().categories(singleton(ResourceIdentifier.ofKey(null))); + + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); + + // test and assertion + assertThat(productReferenceResolver.resolveCategoryReferences(productBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'category' resource identifier on ProductDraft with " + + "key:'%s'. Reason: %s", + productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveCategoryReferences_WithEmptyKeyOnCategoryReference_ShouldNotResolveReference() { + // preparation + final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().categories(singleton(ResourceIdentifier.ofKey(""))); + + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); + + // test and assertion + assertThat(productReferenceResolver.resolveCategoryReferences(productBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'category' resource identifier on ProductDraft with " + + "key:'%s'. Reason: %s", + productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveCategoryReferences_WithCategoryReferencesWithId_ShouldResolveReference() { + // preparation + final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() + .categories(singleton(ResourceIdentifier.ofId("existing-category-id"))); - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); - // test and assertion - assertThat(productReferenceResolver.resolveCategoryReferences(productBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } + final ProductDraftBuilder resolvedDraft = + productReferenceResolver + .resolveCategoryReferences(productBuilder) + .toCompletableFuture() + .join(); - @Test - void resolveCategoryReferences_WithIdOnCategoryReference_ShouldNotResolveReference() { - // preparation - final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + // assertion + assertThat(resolvedDraft.getCategories()) + .isEqualTo(singleton(ResourceIdentifier.ofId("existing-category-id"))); + } + + @Test + void resolveCategoryReferences_WithExceptionCategoryFetch_ShouldNotResolveReference() { + final Category category = getMockCategory("categoryKey", "categoryKey"); + final List categories = Collections.singletonList(category); + + final Set> categoryResourceIdentifiers = + categories.stream() + .map(cat -> ResourceIdentifier.ofKey(cat.getKey())) + .collect(toSet()); + + final CategoryService mockCategoryService = + mockCategoryService(new HashSet<>(categories), null); + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().categories(categoryResourceIdentifiers); + + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(mockCategoryService.fetchMatchingCategoriesByKeys(anySet())) + .thenReturn(futureThrowingSphereException); + + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); + + // test and assertion + assertThat(productReferenceResolver.resolveCategoryReferences(productBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveCategoryReferences_WithIdOnCategoryReference_ShouldNotResolveReference() { + // preparation + final CategoryService mockCategoryService = mockCategoryService(emptySet(), null); + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .categories(asSet(ResourceIdentifier.ofId("existing-id"), null)); - final ProductReferenceResolver productReferenceResolver = createProductReferenceResolver(mockCategoryService); - - // test and assertion - assertThat(productReferenceResolver.resolveCategoryReferences(productBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> Objects.equals(resolvedDraft.getCategories(), - productBuilder.getCategories())); - - } - - @Nonnull - private ProductReferenceResolver createProductReferenceResolver( - @Nonnull final CategoryService categoryService) { - - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - return new ProductReferenceResolver(productSyncOptions, - getMockProductTypeService(PRODUCT_TYPE_ID), categoryService, getMockTypeService(), - getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)), mock(CustomerGroupService.class), - getMockTaxCategoryService(TAX_CATEGORY_ID), getMockStateService(STATE_ID), - getMockProductService(PRODUCT_ID), getMockCustomObjectService(CUSTOM_OBJECT_ID)); - - } + final ProductReferenceResolver productReferenceResolver = + createProductReferenceResolver(mockCategoryService); + + // test and assertion + assertThat( + productReferenceResolver + .resolveCategoryReferences(productBuilder) + .toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching( + resolvedDraft -> + Objects.equals(resolvedDraft.getCategories(), productBuilder.getCategories())); + } + + @Nonnull + private ProductReferenceResolver createProductReferenceResolver( + @Nonnull final CategoryService categoryService) { + + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + return new ProductReferenceResolver( + productSyncOptions, + getMockProductTypeService(PRODUCT_TYPE_ID), + categoryService, + getMockTypeService(), + getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)), + mock(CustomerGroupService.class), + getMockTaxCategoryService(TAX_CATEGORY_ID), + getMockStateService(STATE_ID), + getMockProductService(PRODUCT_ID), + getMockCustomObjectService(CUSTOM_OBJECT_ID)); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/ProductTypeReferenceResolverTest.java b/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/ProductTypeReferenceResolverTest.java index 694885b4d0..3a37670a1f 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/ProductTypeReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/ProductTypeReferenceResolverTest.java @@ -1,24 +1,5 @@ package com.commercetools.sync.products.helpers.productreferenceresolver; -import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; -import com.commercetools.sync.products.ProductSyncOptions; -import com.commercetools.sync.products.ProductSyncOptionsBuilder; -import com.commercetools.sync.products.helpers.ProductReferenceResolver; -import com.commercetools.sync.services.CategoryService; -import com.commercetools.sync.services.CustomerGroupService; -import com.commercetools.sync.services.ProductTypeService; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.models.SphereException; -import io.sphere.sdk.products.ProductDraftBuilder; -import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - import static com.commercetools.sync.commons.MockUtils.getMockTypeService; import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockChannelService; @@ -38,118 +19,157 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; +import com.commercetools.sync.products.ProductSyncOptions; +import com.commercetools.sync.products.ProductSyncOptionsBuilder; +import com.commercetools.sync.products.helpers.ProductReferenceResolver; +import com.commercetools.sync.services.CategoryService; +import com.commercetools.sync.services.CustomerGroupService; +import com.commercetools.sync.services.ProductTypeService; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.models.SphereException; +import io.sphere.sdk.products.ProductDraftBuilder; +import io.sphere.sdk.producttypes.ProductType; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class ProductTypeReferenceResolverTest { - private static final String CHANNEL_KEY = "channel-key_1"; - private static final String CHANNEL_ID = "1"; - private static final String PRODUCT_TYPE_ID = "productTypeId"; - private static final String TAX_CATEGORY_ID = "taxCategoryId"; - private static final String STATE_ID = "stateId"; - private static final String PRODUCT_ID = "productId"; - private static final String CUSTOM_OBJECT_ID = "customObjectId"; - - private ProductTypeService productTypeService; - private ProductReferenceResolver referenceResolver; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - productTypeService = getMockProductTypeService(PRODUCT_TYPE_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - referenceResolver = new ProductReferenceResolver(syncOptions, productTypeService, mock(CategoryService.class), - getMockTypeService(), getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)), + private static final String CHANNEL_KEY = "channel-key_1"; + private static final String CHANNEL_ID = "1"; + private static final String PRODUCT_TYPE_ID = "productTypeId"; + private static final String TAX_CATEGORY_ID = "taxCategoryId"; + private static final String STATE_ID = "stateId"; + private static final String PRODUCT_ID = "productId"; + private static final String CUSTOM_OBJECT_ID = "customObjectId"; + + private ProductTypeService productTypeService; + private ProductReferenceResolver referenceResolver; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + productTypeService = getMockProductTypeService(PRODUCT_TYPE_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new ProductReferenceResolver( + syncOptions, + productTypeService, + mock(CategoryService.class), + getMockTypeService(), + getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)), mock(CustomerGroupService.class), - getMockTaxCategoryService(TAX_CATEGORY_ID), getMockStateService(STATE_ID), - getMockProductService(PRODUCT_ID), getMockCustomObjectService(CUSTOM_OBJECT_ID)); - } - - @Test - void resolveProductTypeReference_WithKeys_ShouldResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithProductTypeRefKey("productTypeKey"); - - final ProductDraftBuilder resolvedDraft = referenceResolver.resolveProductTypeReference(productBuilder) - .toCompletableFuture().join(); - - assertThat(resolvedDraft.getProductType()).isNotNull(); - assertThat(resolvedDraft.getProductType().getId()).isEqualTo(PRODUCT_TYPE_ID); - } - - @Test - void resolveProductTypeReference_WithNonExistentProductType_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithProductTypeRefKey("anyKey") - .key("dummyKey"); - - when(productTypeService.fetchCachedProductTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final String expectedMessageWithCause = format(FAILED_TO_RESOLVE_REFERENCE, ProductType.resourceTypeId(), - "dummyKey", format(PRODUCT_TYPE_DOES_NOT_EXIST, "anyKey")); - - referenceResolver.resolveProductTypeReference(productBuilder) - .exceptionally(exception -> { - assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(exception.getCause().getMessage()) - .isEqualTo(expectedMessageWithCause); - return null; - }) - .toCompletableFuture() - .join(); - } - - @Test - void resolveProductTypeReference_WithNullKeyOnProductTypeReference_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithProductTypeRefKey(null) - .key("dummyKey"); - - assertThat(referenceResolver.resolveProductTypeReference(productBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve '%s' resource identifier on ProductDraft" - + " with key:'%s'. Reason: %s", ProductType.referenceTypeId(), - productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveProductTypeReference_WithEmptyKeyOnProductTypeReference_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithProductTypeRefKey("") - .key("dummyKey"); - - assertThat(referenceResolver.resolveProductTypeReference(productBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve '%s' resource identifier on ProductDraft" - + " with key:'%s'. Reason: %s", ProductType.referenceTypeId(), productBuilder.getKey(), + getMockTaxCategoryService(TAX_CATEGORY_ID), + getMockStateService(STATE_ID), + getMockProductService(PRODUCT_ID), + getMockCustomObjectService(CUSTOM_OBJECT_ID)); + } + + @Test + void resolveProductTypeReference_WithKeys_ShouldResolveReference() { + final ProductDraftBuilder productBuilder = getBuilderWithProductTypeRefKey("productTypeKey"); + + final ProductDraftBuilder resolvedDraft = + referenceResolver.resolveProductTypeReference(productBuilder).toCompletableFuture().join(); + + assertThat(resolvedDraft.getProductType()).isNotNull(); + assertThat(resolvedDraft.getProductType().getId()).isEqualTo(PRODUCT_TYPE_ID); + } + + @Test + void resolveProductTypeReference_WithNonExistentProductType_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithProductTypeRefKey("anyKey").key("dummyKey"); + + when(productTypeService.fetchCachedProductTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final String expectedMessageWithCause = + format( + FAILED_TO_RESOLVE_REFERENCE, + ProductType.resourceTypeId(), + "dummyKey", + format(PRODUCT_TYPE_DOES_NOT_EXIST, "anyKey")); + + referenceResolver + .resolveProductTypeReference(productBuilder) + .exceptionally( + exception -> { + assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(exception.getCause().getMessage()).isEqualTo(expectedMessageWithCause); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void resolveProductTypeReference_WithNullKeyOnProductTypeReference_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithProductTypeRefKey(null).key("dummyKey"); + + assertThat(referenceResolver.resolveProductTypeReference(productBuilder).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve '%s' resource identifier on ProductDraft" + + " with key:'%s'. Reason: %s", + ProductType.referenceTypeId(), + productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveProductTypeReference_WithExceptionOnProductTypeFetch_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithProductTypeRefKey("anyKey") - .key("dummyKey"); - - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(productTypeService.fetchCachedProductTypeId(anyString())).thenReturn(futureThrowingSphereException); - - assertThat(referenceResolver.resolveProductTypeReference(productBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveProductTypeReference_WithIdOnProductTypeReference_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + } + + @Test + void resolveProductTypeReference_WithEmptyKeyOnProductTypeReference_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = getBuilderWithProductTypeRefKey("").key("dummyKey"); + + assertThat(referenceResolver.resolveProductTypeReference(productBuilder).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve '%s' resource identifier on ProductDraft" + + " with key:'%s'. Reason: %s", + ProductType.referenceTypeId(), + productBuilder.getKey(), + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveProductTypeReference_WithExceptionOnProductTypeFetch_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithProductTypeRefKey("anyKey").key("dummyKey"); + + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(productTypeService.fetchCachedProductTypeId(anyString())) + .thenReturn(futureThrowingSphereException); + + assertThat(referenceResolver.resolveProductTypeReference(productBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveProductTypeReference_WithIdOnProductTypeReference_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .productType(ResourceIdentifier.ofId("existing-id")) .key("dummyKey"); - assertThat(referenceResolver.resolveProductTypeReference(productBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> Objects.equals(resolvedDraft.getProductType(), - productBuilder.getProductType())); - } + assertThat(referenceResolver.resolveProductTypeReference(productBuilder).toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching( + resolvedDraft -> + Objects.equals(resolvedDraft.getProductType(), productBuilder.getProductType())); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/StateReferenceResolverTest.java b/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/StateReferenceResolverTest.java index 1143ecc7f3..e4af1f0cc4 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/StateReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/StateReferenceResolverTest.java @@ -1,24 +1,5 @@ package com.commercetools.sync.products.helpers.productreferenceresolver; -import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; -import com.commercetools.sync.products.ProductSyncOptions; -import com.commercetools.sync.products.ProductSyncOptionsBuilder; -import com.commercetools.sync.products.helpers.ProductReferenceResolver; -import com.commercetools.sync.services.CategoryService; -import com.commercetools.sync.services.CustomerGroupService; -import com.commercetools.sync.services.StateService; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.models.SphereException; -import io.sphere.sdk.products.ProductDraftBuilder; -import io.sphere.sdk.states.State; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - import static com.commercetools.sync.commons.MockUtils.getMockTypeService; import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockChannelService; @@ -37,132 +18,167 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; +import com.commercetools.sync.products.ProductSyncOptions; +import com.commercetools.sync.products.ProductSyncOptionsBuilder; +import com.commercetools.sync.products.helpers.ProductReferenceResolver; +import com.commercetools.sync.services.CategoryService; +import com.commercetools.sync.services.CustomerGroupService; +import com.commercetools.sync.services.StateService; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.models.SphereException; +import io.sphere.sdk.products.ProductDraftBuilder; +import io.sphere.sdk.states.State; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class StateReferenceResolverTest { - private static final String CHANNEL_KEY = "channel-key_1"; - private static final String CHANNEL_ID = "1"; - private static final String PRODUCT_TYPE_ID = "productTypeId"; - private static final String TAX_CATEGORY_ID = "taxCategoryId"; - private static final String STATE_ID = "stateId"; - private static final String PRODUCT_ID = "productId"; - private static final String CUSTOM_OBJECT_ID = "customObjectId"; - - private StateService stateService; - private ProductReferenceResolver referenceResolver; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - stateService = getMockStateService(STATE_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new ProductReferenceResolver(syncOptions, getMockProductTypeService(PRODUCT_TYPE_ID), - mock(CategoryService.class), getMockTypeService(), - getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)), mock(CustomerGroupService.class), - getMockTaxCategoryService(TAX_CATEGORY_ID), stateService, getMockProductService(PRODUCT_ID), + private static final String CHANNEL_KEY = "channel-key_1"; + private static final String CHANNEL_ID = "1"; + private static final String PRODUCT_TYPE_ID = "productTypeId"; + private static final String TAX_CATEGORY_ID = "taxCategoryId"; + private static final String STATE_ID = "stateId"; + private static final String PRODUCT_ID = "productId"; + private static final String CUSTOM_OBJECT_ID = "customObjectId"; + + private StateService stateService; + private ProductReferenceResolver referenceResolver; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + stateService = getMockStateService(STATE_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new ProductReferenceResolver( + syncOptions, + getMockProductTypeService(PRODUCT_TYPE_ID), + mock(CategoryService.class), + getMockTypeService(), + getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)), + mock(CustomerGroupService.class), + getMockTaxCategoryService(TAX_CATEGORY_ID), + stateService, + getMockProductService(PRODUCT_ID), getMockCustomObjectService(CUSTOM_OBJECT_ID)); - } + } - @Test - void resolveStateReference_WithKeys_ShouldResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .state(ResourceIdentifier.ofKey("stateKey")); + @Test + void resolveStateReference_WithKeys_ShouldResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().state(ResourceIdentifier.ofKey("stateKey")); - final ProductDraftBuilder resolvedDraft = referenceResolver.resolveStateReference(productBuilder) - .toCompletableFuture().join(); + final ProductDraftBuilder resolvedDraft = + referenceResolver.resolveStateReference(productBuilder).toCompletableFuture().join(); - assertThat(resolvedDraft.getState()).isNotNull(); - assertThat(resolvedDraft.getState().getId()).isEqualTo(STATE_ID); - } + assertThat(resolvedDraft.getState()).isNotNull(); + assertThat(resolvedDraft.getState().getId()).isEqualTo(STATE_ID); + } - @Test - void resolveStateReference_WithNullState_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .key("dummyKey"); + @Test + void resolveStateReference_WithNullState_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType().key("dummyKey"); - assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> Objects.isNull(resolvedDraft.getState())); - } + assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching(resolvedDraft -> Objects.isNull(resolvedDraft.getState())); + } - @Test - void resolveStateReference_WithNonExistentState_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + @Test + void resolveStateReference_WithNonExistentState_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .state(ResourceIdentifier.ofKey("nonExistentKey")) .key("dummyKey"); - when(stateService.fetchCachedStateId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final String expectedMessageWithCause = format(FAILED_TO_RESOLVE_REFERENCE, State.resourceTypeId(), - "dummyKey", format(STATE_DOES_NOT_EXIST, "nonExistentKey")); - - referenceResolver - .resolveStateReference(productBuilder) - .exceptionally(exception -> { - assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(exception.getCause().getMessage()) - .isEqualTo(expectedMessageWithCause); - return null; + when(stateService.fetchCachedStateId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final String expectedMessageWithCause = + format( + FAILED_TO_RESOLVE_REFERENCE, + State.resourceTypeId(), + "dummyKey", + format(STATE_DOES_NOT_EXIST, "nonExistentKey")); + + referenceResolver + .resolveStateReference(productBuilder) + .exceptionally( + exception -> { + assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(exception.getCause().getMessage()).isEqualTo(expectedMessageWithCause); + return null; }) - .toCompletableFuture() - .join(); - } - - @Test - void resolveStateReference_WithNullKeyOnStateReference_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .state(ResourceIdentifier.ofKey(null)) - .key("dummyKey"); - - assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'state' resource identifier on ProductDraft with " - + "key:'%s'. Reason: %s", productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveStateReference_WithEmptyKeyOnStateReference_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .state(ResourceIdentifier.ofKey("")) - .key("dummyKey"); - - assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'state' resource identifier on ProductDraft with " - + "key:'%s'. Reason: %s", productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveStateReference_WithExceptionOnFetch_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + .toCompletableFuture() + .join(); + } + + @Test + void resolveStateReference_WithNullKeyOnStateReference_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().state(ResourceIdentifier.ofKey(null)).key("dummyKey"); + + assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'state' resource identifier on ProductDraft with " + + "key:'%s'. Reason: %s", + productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveStateReference_WithEmptyKeyOnStateReference_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().state(ResourceIdentifier.ofKey("")).key("dummyKey"); + + assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'state' resource identifier on ProductDraft with " + + "key:'%s'. Reason: %s", + productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveStateReference_WithExceptionOnFetch_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .state(ResourceIdentifier.ofKey("stateKey")) .key("dummyKey"); - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(stateService.fetchCachedStateId(anyString())).thenReturn(futureThrowingSphereException); - - assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveStateReference_WithIdOnStateReference_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(stateService.fetchCachedStateId(anyString())).thenReturn(futureThrowingSphereException); + + assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveStateReference_WithIdOnStateReference_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .state(ResourceIdentifier.ofId("existing-id")) .key("dummyKey"); - assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> Objects.equals(resolvedDraft.getState(), - productBuilder.getState())); - } + assertThat(referenceResolver.resolveStateReference(productBuilder).toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching( + resolvedDraft -> Objects.equals(resolvedDraft.getState(), productBuilder.getState())); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/TaxCategoryReferenceResolverTest.java b/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/TaxCategoryReferenceResolverTest.java index e607ba97c1..9a406b74e6 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/TaxCategoryReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/productreferenceresolver/TaxCategoryReferenceResolverTest.java @@ -1,24 +1,5 @@ package com.commercetools.sync.products.helpers.productreferenceresolver; -import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; -import com.commercetools.sync.products.ProductSyncOptions; -import com.commercetools.sync.products.ProductSyncOptionsBuilder; -import com.commercetools.sync.products.helpers.ProductReferenceResolver; -import com.commercetools.sync.services.CategoryService; -import com.commercetools.sync.services.CustomerGroupService; -import com.commercetools.sync.services.TaxCategoryService; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.models.SphereException; -import io.sphere.sdk.products.ProductDraftBuilder; -import io.sphere.sdk.taxcategories.TaxCategory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - import static com.commercetools.sync.commons.MockUtils.getMockTypeService; import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; import static com.commercetools.sync.inventories.InventorySyncMockUtils.getMockChannelService; @@ -37,128 +18,169 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; +import com.commercetools.sync.products.ProductSyncOptions; +import com.commercetools.sync.products.ProductSyncOptionsBuilder; +import com.commercetools.sync.products.helpers.ProductReferenceResolver; +import com.commercetools.sync.services.CategoryService; +import com.commercetools.sync.services.CustomerGroupService; +import com.commercetools.sync.services.TaxCategoryService; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.models.SphereException; +import io.sphere.sdk.products.ProductDraftBuilder; +import io.sphere.sdk.taxcategories.TaxCategory; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class TaxCategoryReferenceResolverTest { - private static final String CHANNEL_KEY = "channel-key_1"; - private static final String CHANNEL_ID = "1"; - private static final String PRODUCT_TYPE_ID = "productTypeId"; - private static final String TAX_CATEGORY_ID = "taxCategoryId"; - private static final String STATE_ID = "stateId"; - private static final String PRODUCT_ID = "productId"; - private static final String CUSTOM_OBJECT_ID = "customObjectId"; - - private ProductReferenceResolver referenceResolver; - private TaxCategoryService taxCategoryService; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - taxCategoryService = getMockTaxCategoryService(TAX_CATEGORY_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new ProductReferenceResolver(syncOptions, - getMockProductTypeService(PRODUCT_TYPE_ID), mock(CategoryService.class), getMockTypeService(), - getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)), mock(CustomerGroupService.class), - taxCategoryService, getMockStateService(STATE_ID), getMockProductService(PRODUCT_ID), + private static final String CHANNEL_KEY = "channel-key_1"; + private static final String CHANNEL_ID = "1"; + private static final String PRODUCT_TYPE_ID = "productTypeId"; + private static final String TAX_CATEGORY_ID = "taxCategoryId"; + private static final String STATE_ID = "stateId"; + private static final String PRODUCT_ID = "productId"; + private static final String CUSTOM_OBJECT_ID = "customObjectId"; + + private ProductReferenceResolver referenceResolver; + private TaxCategoryService taxCategoryService; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + taxCategoryService = getMockTaxCategoryService(TAX_CATEGORY_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new ProductReferenceResolver( + syncOptions, + getMockProductTypeService(PRODUCT_TYPE_ID), + mock(CategoryService.class), + getMockTypeService(), + getMockChannelService(getMockSupplyChannel(CHANNEL_ID, CHANNEL_KEY)), + mock(CustomerGroupService.class), + taxCategoryService, + getMockStateService(STATE_ID), + getMockProductService(PRODUCT_ID), getMockCustomObjectService(CUSTOM_OBJECT_ID)); - } - - @Test - void resolveTaxCategoryReference_WithKeys_ShouldResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .taxCategory(ResourceIdentifier.ofKey("taxCategoryKey")); - - final ProductDraftBuilder resolvedDraft = referenceResolver.resolveTaxCategoryReference(productBuilder) - .toCompletableFuture().join(); - - assertThat(resolvedDraft.getTaxCategory()).isNotNull(); - assertThat(resolvedDraft.getTaxCategory().getId()).isEqualTo(TAX_CATEGORY_ID); - } - - @Test - void resolveTaxCategoryReference_WithNullTaxCategory_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .key("dummyKey"); - - assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> Objects.isNull(resolvedDraft.getTaxCategory())); - } - - @Test - void resolveTaxCategoryReference_WithNonExistentTaxCategory_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + } + + @Test + void resolveTaxCategoryReference_WithKeys_ShouldResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().taxCategory(ResourceIdentifier.ofKey("taxCategoryKey")); + + final ProductDraftBuilder resolvedDraft = + referenceResolver.resolveTaxCategoryReference(productBuilder).toCompletableFuture().join(); + + assertThat(resolvedDraft.getTaxCategory()).isNotNull(); + assertThat(resolvedDraft.getTaxCategory().getId()).isEqualTo(TAX_CATEGORY_ID); + } + + @Test + void resolveTaxCategoryReference_WithNullTaxCategory_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType().key("dummyKey"); + + assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder).toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching( + resolvedDraft -> Objects.isNull(resolvedDraft.getTaxCategory())); + } + + @Test + void resolveTaxCategoryReference_WithNonExistentTaxCategory_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .taxCategory(ResourceIdentifier.ofKey("nonExistentKey")) .key("dummyKey"); - when(taxCategoryService.fetchCachedTaxCategoryId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final String expectedMessageWithCause = format(FAILED_TO_RESOLVE_REFERENCE, TaxCategory.resourceTypeId(), - "dummyKey", format(TAX_CATEGORY_DOES_NOT_EXIST, "nonExistentKey")); - - referenceResolver.resolveTaxCategoryReference(productBuilder) - .exceptionally(exception -> { - assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); - assertThat(exception.getCause().getMessage()) - .isEqualTo(expectedMessageWithCause); - return null; - }) - .toCompletableFuture() - .join(); - } - - @Test - void resolveTaxCategoryReference_WithNullIdOnTaxCategoryReference_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + when(taxCategoryService.fetchCachedTaxCategoryId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final String expectedMessageWithCause = + format( + FAILED_TO_RESOLVE_REFERENCE, + TaxCategory.resourceTypeId(), + "dummyKey", + format(TAX_CATEGORY_DOES_NOT_EXIST, "nonExistentKey")); + + referenceResolver + .resolveTaxCategoryReference(productBuilder) + .exceptionally( + exception -> { + assertThat(exception).hasCauseExactlyInstanceOf(ReferenceResolutionException.class); + assertThat(exception.getCause().getMessage()).isEqualTo(expectedMessageWithCause); + return null; + }) + .toCompletableFuture() + .join(); + } + + @Test + void resolveTaxCategoryReference_WithNullIdOnTaxCategoryReference_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .taxCategory(ResourceIdentifier.ofKey(null)) .key("dummyKey"); - assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'tax-category' resource identifier on ProductDraft with " - + "key:'%s'. Reason: %s", productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveTaxCategoryReference_WithEmptyIdOnTaxCategoryReference_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() - .taxCategory(ResourceIdentifier.ofKey("")) - .key("dummyKey"); - - assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'tax-category' resource identifier on ProductDraft with " - + "key:'%s'. Reason: %s", productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveTaxCategoryReference_WithExceptionOnTaxCategoryFetch_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'tax-category' resource identifier on ProductDraft with " + + "key:'%s'. Reason: %s", + productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveTaxCategoryReference_WithEmptyIdOnTaxCategoryReference_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType().taxCategory(ResourceIdentifier.ofKey("")).key("dummyKey"); + + assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'tax-category' resource identifier on ProductDraft with " + + "key:'%s'. Reason: %s", + productBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveTaxCategoryReference_WithExceptionOnTaxCategoryFetch_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .taxCategory(ResourceIdentifier.ofKey("taxCategoryKey")) .key("dummyKey"); - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(taxCategoryService.fetchCachedTaxCategoryId(anyString())).thenReturn(futureThrowingSphereException); - - assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveTaxCategoryReference_WithIdOnTaxCategoryReference_ShouldNotResolveReference() { - final ProductDraftBuilder productBuilder = getBuilderWithRandomProductType() + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(taxCategoryService.fetchCachedTaxCategoryId(anyString())) + .thenReturn(futureThrowingSphereException); + + assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveTaxCategoryReference_WithIdOnTaxCategoryReference_ShouldNotResolveReference() { + final ProductDraftBuilder productBuilder = + getBuilderWithRandomProductType() .taxCategory(ResourceIdentifier.ofId("existing-id")) .key("dummyKey"); - assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> Objects.equals(resolvedDraft.getTaxCategory(), - productBuilder.getTaxCategory())); - } + assertThat(referenceResolver.resolveTaxCategoryReference(productBuilder).toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching( + resolvedDraft -> + Objects.equals(resolvedDraft.getTaxCategory(), productBuilder.getTaxCategory())); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/AssertionUtilsForVariantReferenceResolver.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/AssertionUtilsForVariantReferenceResolver.java index 779170f379..1b70d8e9e3 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/AssertionUtilsForVariantReferenceResolver.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/AssertionUtilsForVariantReferenceResolver.java @@ -1,66 +1,65 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; - -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.Map; - -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static org.assertj.core.api.Assertions.assertThat; +import javax.annotation.Nonnull; public final class AssertionUtilsForVariantReferenceResolver { - public static void assertReferenceAttributeValue( - @Nonnull final Map attributeDraftMap, - @Nonnull final String attributeName, - @Nonnull final String referenceId, - @Nonnull final String referenceTypeId) { + public static void assertReferenceAttributeValue( + @Nonnull final Map attributeDraftMap, + @Nonnull final String attributeName, + @Nonnull final String referenceId, + @Nonnull final String referenceTypeId) { - assertThat(attributeDraftMap.get(attributeName)).isNotNull(); - assertThat(attributeDraftMap.get(attributeName).get("value")).isNotNull(); - assertThat(attributeDraftMap.get(attributeName) - .get("value") - .get(REFERENCE_ID_FIELD).asText()).isEqualTo(referenceId); - assertThat(attributeDraftMap.get(attributeName) - .get("value") - .get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(referenceTypeId); - } + assertThat(attributeDraftMap.get(attributeName)).isNotNull(); + assertThat(attributeDraftMap.get(attributeName).get("value")).isNotNull(); + assertThat(attributeDraftMap.get(attributeName).get("value").get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(referenceId); + assertThat( + attributeDraftMap.get(attributeName).get("value").get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(referenceTypeId); + } - public static void assertReferenceSetAttributeValue( - @Nonnull final Map attributeDraftMap, - @Nonnull final String attributeName, - final int numberOfReferences, - @Nonnull final String referenceId, - @Nonnull final String referenceTypeId) { + public static void assertReferenceSetAttributeValue( + @Nonnull final Map attributeDraftMap, + @Nonnull final String attributeName, + final int numberOfReferences, + @Nonnull final String referenceId, + @Nonnull final String referenceTypeId) { - assertThat(attributeDraftMap.get(attributeName)).isNotNull(); - final JsonNode value = attributeDraftMap.get(attributeName).get("value"); - assertThat(value).isInstanceOf(ArrayNode.class); + assertThat(attributeDraftMap.get(attributeName)).isNotNull(); + final JsonNode value = attributeDraftMap.get(attributeName).get("value"); + assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode valueAsArrayNode = (ArrayNode) value; - assertThat(valueAsArrayNode).hasSize(numberOfReferences); - assertThat(valueAsArrayNode).allSatisfy(jsonNode -> { - assertThat(jsonNode.get(REFERENCE_ID_FIELD).asText()).isEqualTo(referenceId); - assertThat(jsonNode.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(referenceTypeId); - }); - } + final ArrayNode valueAsArrayNode = (ArrayNode) value; + assertThat(valueAsArrayNode).hasSize(numberOfReferences); + assertThat(valueAsArrayNode) + .allSatisfy( + jsonNode -> { + assertThat(jsonNode.get(REFERENCE_ID_FIELD).asText()).isEqualTo(referenceId); + assertThat(jsonNode.get(REFERENCE_TYPE_ID_FIELD).asText()).isEqualTo(referenceTypeId); + }); + } - public static void assertReferenceSetAttributeValue( - @Nonnull final Map attributeDraftMap, - @Nonnull final String attributeName, - @Nonnull final JsonNode... expectedReferences) { + public static void assertReferenceSetAttributeValue( + @Nonnull final Map attributeDraftMap, + @Nonnull final String attributeName, + @Nonnull final JsonNode... expectedReferences) { - assertThat(attributeDraftMap.get(attributeName)).isNotNull(); - final JsonNode value = attributeDraftMap.get(attributeName).get("value"); - assertThat(value).isInstanceOf(ArrayNode.class); + assertThat(attributeDraftMap.get(attributeName)).isNotNull(); + final JsonNode value = attributeDraftMap.get(attributeName).get("value"); + assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode valueAsArrayNode = (ArrayNode) value; - assertThat(valueAsArrayNode).containsAll(Arrays.asList(expectedReferences)); - } + final ArrayNode valueAsArrayNode = (ArrayNode) value; + assertThat(valueAsArrayNode).containsAll(Arrays.asList(expectedReferences)); + } - private AssertionUtilsForVariantReferenceResolver() { - } + private AssertionUtilsForVariantReferenceResolver() {} } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCategoryReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCategoryReferencesTest.java index 70b90fd1f5..f3538eeb78 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCategoryReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCategoryReferencesTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCategoryService; +import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -18,9 +28,6 @@ import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.products.ProductVariantDraftBuilder; import io.sphere.sdk.products.attributes.AttributeDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.Set; @@ -29,27 +36,22 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCategoryService; -import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithCategoryReferencesTest { - private CategoryService categoryService; - private static final String CATEGORY_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - @BeforeEach - void setup() { - categoryService = getMockCategoryService(CATEGORY_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private CategoryService categoryService; + private static final String CATEGORY_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + @BeforeEach + void setup() { + categoryService = getMockCategoryService(CATEGORY_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -57,210 +59,203 @@ void setup() { mock(ProductTypeService.class), categoryService, mock(CustomObjectService.class)); - } - - @Test - void resolveReferences_WithNonExistingCategoryReferenceAttribute_ShouldNotResolveReferences() { - // preparation - when(categoryService.fetchCachedCategoryId("nonExistingCatKey")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode attributeValue = createReferenceObject("nonExistingCatKey", Category.referenceTypeId()); - final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNullIdFieldInCategoryReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); - final AttributeDraft categoryReferenceAttribute = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(categoryReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNullNodeIdFieldInCategoryReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); - attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); - - final AttributeDraft categoryReferenceAttribute = - AttributeDraft.of("attributeName", attributeValue); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(categoryReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithExistingCategoryReferenceAttribute_ShouldResolveReferences() { - // preparation - final ObjectNode attributeValue = createReferenceObject("foo", Category.referenceTypeId()); - final AttributeDraft categoryReferenceAttribute = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(categoryReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - final AttributeDraft resolvedAttribute = resolvedAttributeDraft.getAttributes().get(0); - assertThat(resolvedAttribute).isNotNull(); - assertThat(resolvedAttribute.getValue().get(REFERENCE_ID_FIELD).asText()).isEqualTo(CATEGORY_ID); - assertThat(resolvedAttribute.getValue().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Category.referenceTypeId()); - } - - @Test - void resolveReferences_WithCategoryReferenceSetAttribute_ShouldResolveReferences() { - final AttributeDraft categoryReferenceSetAttributeDraft = getReferenceSetAttributeDraft("foo", + } + + @Test + void resolveReferences_WithNonExistingCategoryReferenceAttribute_ShouldNotResolveReferences() { + // preparation + when(categoryService.fetchCachedCategoryId("nonExistingCatKey")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode attributeValue = + createReferenceObject("nonExistingCatKey", Category.referenceTypeId()); + final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithNullIdFieldInCategoryReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(categoryReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void + resolveReferences_WithNullNodeIdFieldInCategoryReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); + attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); + + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(categoryReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithExistingCategoryReferenceAttribute_ShouldResolveReferences() { + // preparation + final ObjectNode attributeValue = createReferenceObject("foo", Category.referenceTypeId()); + final AttributeDraft categoryReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(categoryReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + final AttributeDraft resolvedAttribute = resolvedAttributeDraft.getAttributes().get(0); + assertThat(resolvedAttribute).isNotNull(); + assertThat(resolvedAttribute.getValue().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(CATEGORY_ID); + assertThat(resolvedAttribute.getValue().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Category.referenceTypeId()); + } + + @Test + void resolveReferences_WithCategoryReferenceSetAttribute_ShouldResolveReferences() { + final AttributeDraft categoryReferenceSetAttributeDraft = + getReferenceSetAttributeDraft( + "foo", createReferenceObject(UUID.randomUUID().toString(), Category.referenceTypeId()), createReferenceObject(UUID.randomUUID().toString(), Category.referenceTypeId())); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(categoryReferenceSetAttributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final List resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toList()); - assertThat(resolvedSet).isNotEmpty(); - final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); - resolvedReference.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); - resolvedReference.put(REFERENCE_ID_FIELD, CATEGORY_ID); - assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference, resolvedReference); - } - - @Test - void resolveReferences_WithNonExistingCategoryReferenceSetAttribute_ShouldNotResolveReferences() { - // preparation - when(categoryService.fetchCachedCategoryId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode categoryReference = - createReferenceObject(UUID.randomUUID().toString(), Category.referenceTypeId()); - final AttributeDraft categoryReferenceAttribute = - getReferenceSetAttributeDraft("foo", categoryReference); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(categoryReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final Set resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toSet()); - assertThat(resolvedSet).containsExactly(categoryReference); - } - - @Test - void resolveReferences_WithSomeExistingCategoryReferenceSetAttribute_ShouldResolveExistingReferences() { - // preparation - when(categoryService.fetchCachedCategoryId("existingKey")) - .thenReturn(CompletableFuture.completedFuture(Optional.of("existingId"))); - when(categoryService.fetchCachedCategoryId("randomKey")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode categoryReference1 = createReferenceObject("existingKey", Category.referenceTypeId()); - final ObjectNode categoryReference2 = createReferenceObject("randomKey", Category.referenceTypeId()); - - final AttributeDraft categoryReferenceAttribute = - getReferenceSetAttributeDraft("foo", categoryReference1, categoryReference2); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(categoryReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final Set resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toSet()); - - final ObjectNode resolvedReference1 = createReferenceObject("existingId", Category.referenceTypeId()); - final ObjectNode resolvedReference2 = createReferenceObject("randomKey", Category.referenceTypeId()); - assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference1, resolvedReference2); - } + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(categoryReferenceSetAttributeDraft).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final List resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toList()); + assertThat(resolvedSet).isNotEmpty(); + final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); + resolvedReference.put(REFERENCE_TYPE_ID_FIELD, Category.referenceTypeId()); + resolvedReference.put(REFERENCE_ID_FIELD, CATEGORY_ID); + assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference, resolvedReference); + } + + @Test + void resolveReferences_WithNonExistingCategoryReferenceSetAttribute_ShouldNotResolveReferences() { + // preparation + when(categoryService.fetchCachedCategoryId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode categoryReference = + createReferenceObject(UUID.randomUUID().toString(), Category.referenceTypeId()); + final AttributeDraft categoryReferenceAttribute = + getReferenceSetAttributeDraft("foo", categoryReference); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(categoryReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final Set resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toSet()); + assertThat(resolvedSet).containsExactly(categoryReference); + } + + @Test + void + resolveReferences_WithSomeExistingCategoryReferenceSetAttribute_ShouldResolveExistingReferences() { + // preparation + when(categoryService.fetchCachedCategoryId("existingKey")) + .thenReturn(CompletableFuture.completedFuture(Optional.of("existingId"))); + when(categoryService.fetchCachedCategoryId("randomKey")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode categoryReference1 = + createReferenceObject("existingKey", Category.referenceTypeId()); + final ObjectNode categoryReference2 = + createReferenceObject("randomKey", Category.referenceTypeId()); + + final AttributeDraft categoryReferenceAttribute = + getReferenceSetAttributeDraft("foo", categoryReference1, categoryReference2); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(categoryReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final Set resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toSet()); + + final ObjectNode resolvedReference1 = + createReferenceObject("existingId", Category.referenceTypeId()); + final ObjectNode resolvedReference2 = + createReferenceObject("randomKey", Category.referenceTypeId()); + assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference1, resolvedReference2); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCustomObjectReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCustomObjectReferencesTest.java index 7a998052bb..bd562d29d5 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCustomObjectReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCustomObjectReferencesTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomObjectService; +import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -19,9 +29,6 @@ import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.products.ProductVariantDraftBuilder; import io.sphere.sdk.products.attributes.AttributeDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.Set; @@ -30,27 +37,22 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomObjectService; -import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithCustomObjectReferencesTest { - private CustomObjectService customObjectService; - private static final String CUSTOM_OBJECT_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - @BeforeEach - void setup() { - customObjectService = getMockCustomObjectService(CUSTOM_OBJECT_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private CustomObjectService customObjectService; + private static final String CUSTOM_OBJECT_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + @BeforeEach + void setup() { + customObjectService = getMockCustomObjectService(CUSTOM_OBJECT_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -58,268 +60,255 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), customObjectService); - } - - @Test - void resolveReferences_WithNonExistingCustomObjectReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final CustomObjectCompositeIdentifier nonExistingCustomObjectId = - CustomObjectCompositeIdentifier.of("non-existing-key", "non-existing-container"); - - when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObjectId)) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode attributeValue = - createReferenceObject("non-existing-container|non-existing-key", CustomObject.referenceTypeId()); - final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNullIdFieldInCustomObjectReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); - - final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNullNodeIdFieldInCustomObjectReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); - attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); - - final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithInvalidCustomObjectIdentifier_ShouldNotResolveReferences() { - // preparation - final String invalidCustomObjectIdentifier = "container-key"; - final ObjectNode attributeValue = - createReferenceObject(invalidCustomObjectIdentifier, CustomObject.referenceTypeId()); - final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - assertThat(referenceResolver.resolveReferences(productVariantDraft)) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(IllegalArgumentException.class) - .hasMessage("The custom object identifier value: \"container-key\" does not have the correct format. " + } + + @Test + void + resolveReferences_WithNonExistingCustomObjectReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final CustomObjectCompositeIdentifier nonExistingCustomObjectId = + CustomObjectCompositeIdentifier.of("non-existing-key", "non-existing-container"); + + when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObjectId)) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode attributeValue = + createReferenceObject( + "non-existing-container|non-existing-key", CustomObject.referenceTypeId()); + final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void + resolveReferences_WithNullIdFieldInCustomObjectReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); + + final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void + resolveReferences_WithNullNodeIdFieldInCustomObjectReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); + attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); + + final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithInvalidCustomObjectIdentifier_ShouldNotResolveReferences() { + // preparation + final String invalidCustomObjectIdentifier = "container-key"; + final ObjectNode attributeValue = + createReferenceObject(invalidCustomObjectIdentifier, CustomObject.referenceTypeId()); + final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + assertThat(referenceResolver.resolveReferences(productVariantDraft)) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage( + "The custom object identifier value: \"container-key\" does not have the correct format. " + "The correct format must have a vertical bar \"|\" character between the container and key."); - } - - @Test - void resolveReferences_WithUuidCustomObjectIdentifier_ShouldNotResolveReferences() { - // preparation - final String uuid = UUID.randomUUID().toString(); - final ObjectNode attributeValue = - createReferenceObject(uuid, CustomObject.referenceTypeId()); - final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithExistingCustomObjectReferenceAttribute_ShouldResolveReferences() { - // preparation - final ObjectNode attributeValue = createReferenceObject("container|key", CustomObject.referenceTypeId()); - final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - final AttributeDraft resolvedAttribute = resolvedAttributeDraft.getAttributes().get(0); - assertThat(resolvedAttribute).isNotNull(); - assertThat(resolvedAttribute.getValue().get(REFERENCE_ID_FIELD).asText()).isEqualTo(CUSTOM_OBJECT_ID); - assertThat(resolvedAttribute.getValue().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(CustomObject.referenceTypeId()); - } - - @Test - void resolveReferences_WithCustomObjectReferenceSetAttribute_ShouldResolveReferences() { - final AttributeDraft attributeDraft = getReferenceSetAttributeDraft("attributeName", + } + + @Test + void resolveReferences_WithUuidCustomObjectIdentifier_ShouldNotResolveReferences() { + // preparation + final String uuid = UUID.randomUUID().toString(); + final ObjectNode attributeValue = createReferenceObject(uuid, CustomObject.referenceTypeId()); + final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithExistingCustomObjectReferenceAttribute_ShouldResolveReferences() { + // preparation + final ObjectNode attributeValue = + createReferenceObject("container|key", CustomObject.referenceTypeId()); + final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + final AttributeDraft resolvedAttribute = resolvedAttributeDraft.getAttributes().get(0); + assertThat(resolvedAttribute).isNotNull(); + assertThat(resolvedAttribute.getValue().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(CUSTOM_OBJECT_ID); + assertThat(resolvedAttribute.getValue().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(CustomObject.referenceTypeId()); + } + + @Test + void resolveReferences_WithCustomObjectReferenceSetAttribute_ShouldResolveReferences() { + final AttributeDraft attributeDraft = + getReferenceSetAttributeDraft( + "attributeName", createReferenceObject("container|key1", CustomObject.referenceTypeId()), createReferenceObject("container|key2", CustomObject.referenceTypeId())); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final List resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toList()); - assertThat(resolvedSet).isNotEmpty(); - final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); - resolvedReference.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); - resolvedReference.put(REFERENCE_ID_FIELD, CUSTOM_OBJECT_ID); - assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference, resolvedReference); - } - - @Test - void resolveReferences_WithNonExistingCustomObjectReferenceSetAttribute_ShouldNotResolveReferences() { - // preparation - when(customObjectService.fetchCachedCustomObjectId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode customObjectReference = - createReferenceObject("container|key", CustomObject.referenceTypeId()); - final AttributeDraft attributeDraft = - getReferenceSetAttributeDraft("attributeName", customObjectReference); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final Set resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toSet()); - assertThat(resolvedSet).containsExactly(customObjectReference); - } - - @Test - void resolveReferences_WithSomeExistingCustomObjectReferenceSetAttribute_ShouldResolveExistingReferences() { - // preparation - final CustomObjectCompositeIdentifier existingCustomObjectId = - CustomObjectCompositeIdentifier.of("existing-key", "existing-container"); - - final CustomObjectCompositeIdentifier randomCustomObjectId = - CustomObjectCompositeIdentifier.of("random-key", "random-container"); - - when(customObjectService.fetchCachedCustomObjectId(existingCustomObjectId)) - .thenReturn(CompletableFuture.completedFuture(Optional.of("existingId"))); - - when(customObjectService.fetchCachedCustomObjectId(randomCustomObjectId)) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode customObjectReference1 = - createReferenceObject("existing-container|existing-key", CustomObject.referenceTypeId()); - final ObjectNode customObjectReference2 = - createReferenceObject("random-container|random-key", CustomObject.referenceTypeId()); - - final AttributeDraft attributeDraft = - getReferenceSetAttributeDraft("attributeName", customObjectReference1, customObjectReference2); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final Set resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toSet()); - - final ObjectNode resolvedReference1 = createReferenceObject("existingId", CustomObject.referenceTypeId()); - final ObjectNode resolvedReference2 = - createReferenceObject("random-container|random-key", CustomObject.referenceTypeId()); - assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference1, resolvedReference2); - } + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final List resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toList()); + assertThat(resolvedSet).isNotEmpty(); + final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); + resolvedReference.put(REFERENCE_TYPE_ID_FIELD, CustomObject.referenceTypeId()); + resolvedReference.put(REFERENCE_ID_FIELD, CUSTOM_OBJECT_ID); + assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference, resolvedReference); + } + + @Test + void + resolveReferences_WithNonExistingCustomObjectReferenceSetAttribute_ShouldNotResolveReferences() { + // preparation + when(customObjectService.fetchCachedCustomObjectId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode customObjectReference = + createReferenceObject("container|key", CustomObject.referenceTypeId()); + final AttributeDraft attributeDraft = + getReferenceSetAttributeDraft("attributeName", customObjectReference); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final Set resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toSet()); + assertThat(resolvedSet).containsExactly(customObjectReference); + } + + @Test + void + resolveReferences_WithSomeExistingCustomObjectReferenceSetAttribute_ShouldResolveExistingReferences() { + // preparation + final CustomObjectCompositeIdentifier existingCustomObjectId = + CustomObjectCompositeIdentifier.of("existing-key", "existing-container"); + + final CustomObjectCompositeIdentifier randomCustomObjectId = + CustomObjectCompositeIdentifier.of("random-key", "random-container"); + + when(customObjectService.fetchCachedCustomObjectId(existingCustomObjectId)) + .thenReturn(CompletableFuture.completedFuture(Optional.of("existingId"))); + + when(customObjectService.fetchCachedCustomObjectId(randomCustomObjectId)) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode customObjectReference1 = + createReferenceObject("existing-container|existing-key", CustomObject.referenceTypeId()); + final ObjectNode customObjectReference2 = + createReferenceObject("random-container|random-key", CustomObject.referenceTypeId()); + + final AttributeDraft attributeDraft = + getReferenceSetAttributeDraft( + "attributeName", customObjectReference1, customObjectReference2); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final Set resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toSet()); + + final ObjectNode resolvedReference1 = + createReferenceObject("existingId", CustomObject.referenceTypeId()); + final ObjectNode resolvedReference2 = + createReferenceObject("random-container|random-key", CustomObject.referenceTypeId()); + assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference1, resolvedReference2); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCustomerReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCustomerReferencesTest.java index d78be96d13..684c74ca46 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCustomerReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithCustomerReferencesTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -17,25 +24,20 @@ import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.products.ProductVariantDraftBuilder; import io.sphere.sdk.products.attributes.AttributeDraft; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.UUID; - -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - class WithCustomerReferencesTest { - private VariantReferenceResolver referenceResolver; + private VariantReferenceResolver referenceResolver; - @BeforeEach - void setup() { - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + @BeforeEach + void setup() { + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -43,68 +45,60 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), mock(CustomObjectService.class)); - } - + } - @Test - void resolveReferences_WithNullIdFieldInCustomerReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, Customer.referenceTypeId()); - final AttributeDraft customerReferenceAttribute = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(customerReferenceAttribute) - .build(); + @Test + void resolveReferences_WithNullIdFieldInCustomerReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, Customer.referenceTypeId()); + final AttributeDraft customerReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(customerReferenceAttribute).build(); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } - @Test - void resolveReferences_WithNullNodeIdFieldInCustomerReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, Customer.referenceTypeId()); - attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); - final AttributeDraft customerReferenceAttribute = - AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(customerReferenceAttribute) - .build(); + @Test + void + resolveReferences_WithNullNodeIdFieldInCustomerReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, Customer.referenceTypeId()); + attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); + final AttributeDraft customerReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(customerReferenceAttribute).build(); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } - @Test - void resolveReferences_WithCustomerReferenceSetAttribute_ShouldNotResolveReferences() { - final AttributeDraft customerReferenceSetAttributeDraft = getReferenceSetAttributeDraft("foo", + @Test + void resolveReferences_WithCustomerReferenceSetAttribute_ShouldNotResolveReferences() { + final AttributeDraft customerReferenceSetAttributeDraft = + getReferenceSetAttributeDraft( + "foo", createReferenceObject(UUID.randomUUID().toString(), Customer.referenceTypeId()), createReferenceObject(UUID.randomUUID().toString(), Customer.referenceTypeId())); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(customerReferenceSetAttributeDraft) - .build(); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(customerReferenceSetAttributeDraft).build(); - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); - // assertions - assertThat(productVariantDraft).isEqualTo(resolvedProductVariantDraft); - } + // assertions + assertThat(productVariantDraft).isEqualTo(resolvedProductVariantDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithProductReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithProductReferencesTest.java index c107ebe335..6be5d881b7 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithProductReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithProductReferencesTest.java @@ -1,5 +1,16 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductService; +import static com.commercetools.sync.products.ProductSyncMockUtils.getProductReferenceWithId; +import static com.commercetools.sync.products.ProductSyncMockUtils.getProductReferenceWithRandomId; +import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -18,9 +29,6 @@ import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.products.ProductVariantDraftBuilder; import io.sphere.sdk.products.attributes.AttributeDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.Set; @@ -29,235 +37,216 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductService; -import static com.commercetools.sync.products.ProductSyncMockUtils.getProductReferenceWithId; -import static com.commercetools.sync.products.ProductSyncMockUtils.getProductReferenceWithRandomId; -import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithProductReferencesTest { - private ProductService productService; - private static final String PRODUCT_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - @BeforeEach - void setup() { - productService = getMockProductService(PRODUCT_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, mock(TypeService.class), + private ProductService productService; + private static final String PRODUCT_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + @BeforeEach + void setup() { + productService = getMockProductService(PRODUCT_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, + mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), productService, mock(ProductTypeService.class), mock(CategoryService.class), mock(CustomObjectService.class)); - } - - @Test - void resolveReferences_WithNonExistingProductReferenceAttribute_ShouldNotResolveReferences() { - // preparation - when(productService.getIdFromCacheOrFetch(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode attributeValue = getProductReferenceWithRandomId(); - final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNullIdFieldInProductReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, Product.referenceTypeId()); - final AttributeDraft productReferenceAttribute = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNullNodeIdFieldInProductReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, Product.referenceTypeId()); - attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); - - final AttributeDraft productReferenceAttribute = - AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithExistingProductReferenceAttribute_ShouldResolveReferences() { - // preparation - final ObjectNode attributeValue = getProductReferenceWithRandomId(); - final AttributeDraft productReferenceAttribute = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - final AttributeDraft resolvedAttribute = resolvedAttributeDraft.getAttributes().get(0); - assertThat(resolvedAttribute).isNotNull(); - assertThat(resolvedAttribute.getValue().get(REFERENCE_ID_FIELD).asText()).isEqualTo(PRODUCT_ID); - assertThat(resolvedAttribute.getValue().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(Product.referenceTypeId()); - } - - @Test - void resolveReferences_WithProductReferenceSetAttribute_ShouldResolveReferences() { - final AttributeDraft productReferenceSetAttributeDraft = getReferenceSetAttributeDraft("foo", - getProductReferenceWithRandomId(), getProductReferenceWithRandomId()); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceSetAttributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final List resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toList()); - assertThat(resolvedSet).isNotEmpty(); - final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); - resolvedReference.put(REFERENCE_TYPE_ID_FIELD, Product.referenceTypeId()); - resolvedReference.put(REFERENCE_ID_FIELD, PRODUCT_ID); - assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference, resolvedReference); - } - - @Test - void resolveReferences_WithNonExistingProductReferenceSetAttribute_ShouldNotResolveReferences() { - // preparation - when(productService.getIdFromCacheOrFetch(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode productReference = getProductReferenceWithRandomId(); - final AttributeDraft productReferenceAttribute = - getReferenceSetAttributeDraft("foo", productReference); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final Set resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toSet()); - assertThat(resolvedSet).containsExactly(productReference); - } - - @Test - void resolveReferences_WithSomeExistingProductReferenceSetAttribute_ShouldResolveExistingReferences() { - // preparation - when(productService.getIdFromCacheOrFetch("existingKey")) - .thenReturn(CompletableFuture.completedFuture(Optional.of("existingId"))); - when(productService.getIdFromCacheOrFetch("randomKey")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode productReference1 = getProductReferenceWithId("existingKey"); - final ObjectNode productReference2 = getProductReferenceWithId("randomKey"); - - final AttributeDraft productReferenceAttribute = - getReferenceSetAttributeDraft("foo", productReference1, productReference2); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final Set resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toSet()); - - final ObjectNode resolvedReference1 = getProductReferenceWithId("existingId"); - final ObjectNode resolvedReference2 = getProductReferenceWithId("randomKey"); - assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference1, resolvedReference2); - } + } + + @Test + void resolveReferences_WithNonExistingProductReferenceAttribute_ShouldNotResolveReferences() { + // preparation + when(productService.getIdFromCacheOrFetch(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode attributeValue = getProductReferenceWithRandomId(); + final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithNullIdFieldInProductReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, Product.referenceTypeId()); + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void + resolveReferences_WithNullNodeIdFieldInProductReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, Product.referenceTypeId()); + attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); + + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithExistingProductReferenceAttribute_ShouldResolveReferences() { + // preparation + final ObjectNode attributeValue = getProductReferenceWithRandomId(); + final AttributeDraft productReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + final AttributeDraft resolvedAttribute = resolvedAttributeDraft.getAttributes().get(0); + assertThat(resolvedAttribute).isNotNull(); + assertThat(resolvedAttribute.getValue().get(REFERENCE_ID_FIELD).asText()).isEqualTo(PRODUCT_ID); + assertThat(resolvedAttribute.getValue().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(Product.referenceTypeId()); + } + + @Test + void resolveReferences_WithProductReferenceSetAttribute_ShouldResolveReferences() { + final AttributeDraft productReferenceSetAttributeDraft = + getReferenceSetAttributeDraft( + "foo", getProductReferenceWithRandomId(), getProductReferenceWithRandomId()); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceSetAttributeDraft).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final List resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toList()); + assertThat(resolvedSet).isNotEmpty(); + final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); + resolvedReference.put(REFERENCE_TYPE_ID_FIELD, Product.referenceTypeId()); + resolvedReference.put(REFERENCE_ID_FIELD, PRODUCT_ID); + assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference, resolvedReference); + } + + @Test + void resolveReferences_WithNonExistingProductReferenceSetAttribute_ShouldNotResolveReferences() { + // preparation + when(productService.getIdFromCacheOrFetch(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode productReference = getProductReferenceWithRandomId(); + final AttributeDraft productReferenceAttribute = + getReferenceSetAttributeDraft("foo", productReference); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final Set resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toSet()); + assertThat(resolvedSet).containsExactly(productReference); + } + + @Test + void + resolveReferences_WithSomeExistingProductReferenceSetAttribute_ShouldResolveExistingReferences() { + // preparation + when(productService.getIdFromCacheOrFetch("existingKey")) + .thenReturn(CompletableFuture.completedFuture(Optional.of("existingId"))); + when(productService.getIdFromCacheOrFetch("randomKey")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode productReference1 = getProductReferenceWithId("existingKey"); + final ObjectNode productReference2 = getProductReferenceWithId("randomKey"); + + final AttributeDraft productReferenceAttribute = + getReferenceSetAttributeDraft("foo", productReference1, productReference2); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final Set resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toSet()); + + final ObjectNode resolvedReference1 = getProductReferenceWithId("existingId"); + final ObjectNode resolvedReference2 = getProductReferenceWithId("randomKey"); + assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference1, resolvedReference2); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithProductTypeReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithProductTypeReferencesTest.java index 1c6c4ebcd0..a9b7234fdd 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithProductTypeReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/WithProductTypeReferencesTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; +import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductTypeService; +import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -18,9 +28,6 @@ import io.sphere.sdk.products.ProductVariantDraftBuilder; import io.sphere.sdk.products.attributes.AttributeDraft; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.Set; @@ -29,27 +36,22 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_ID_FIELD; -import static com.commercetools.sync.commons.utils.ResourceIdentifierUtils.REFERENCE_TYPE_ID_FIELD; -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductTypeService; -import static com.commercetools.sync.products.ProductSyncMockUtils.getReferenceSetAttributeDraft; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithProductTypeReferencesTest { - private ProductTypeService productTypeService; - private static final String PRODUCT_TYPE_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - @BeforeEach - void setup() { - productTypeService = getMockProductTypeService(PRODUCT_TYPE_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private ProductTypeService productTypeService; + private static final String PRODUCT_TYPE_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + @BeforeEach + void setup() { + productTypeService = getMockProductTypeService(PRODUCT_TYPE_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -57,211 +59,205 @@ void setup() { productTypeService, mock(CategoryService.class), mock(CustomObjectService.class)); - } - - @Test - void resolveReferences_WithNonExistingProductTypeReferenceAttribute_ShouldNotResolveReferences() { - // preparation - when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode attributeValue = createReferenceObject("nonExistingProductTypeKey", - ProductType.referenceTypeId()); - final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(attributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNullIdFieldInProductTypeReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, ProductType.referenceTypeId()); - final AttributeDraft productTypeReferenceAttribute = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productTypeReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithNullNodeIdFieldInProductTypeReferenceAttribute_ShouldNotResolveReferences() { - // preparation - final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); - attributeValue.put(REFERENCE_TYPE_ID_FIELD, ProductType.referenceTypeId()); - attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); - - final AttributeDraft productTypeReferenceAttribute = - AttributeDraft.of("attributeName", attributeValue); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productTypeReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); - } - - @Test - void resolveReferences_WithExistingProductTypeReferenceAttribute_ShouldResolveReferences() { - // preparation - final ObjectNode attributeValue = createReferenceObject("foo", ProductType.referenceTypeId()); - final AttributeDraft productTypeReferenceAttribute = AttributeDraft.of("attributeName", attributeValue); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productTypeReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - final AttributeDraft resolvedAttribute = resolvedAttributeDraft.getAttributes().get(0); - assertThat(resolvedAttribute).isNotNull(); - assertThat(resolvedAttribute.getValue().get(REFERENCE_ID_FIELD).asText()).isEqualTo(PRODUCT_TYPE_ID); - assertThat(resolvedAttribute.getValue().get(REFERENCE_TYPE_ID_FIELD).asText()) - .isEqualTo(ProductType.referenceTypeId()); - } - - @Test - void resolveReferences_WithProductTypeReferenceSetAttribute_ShouldResolveReferences() { - final AttributeDraft productTypeReferenceSetAttributeDraft = getReferenceSetAttributeDraft("foo", + } + + @Test + void resolveReferences_WithNonExistingProductTypeReferenceAttribute_ShouldNotResolveReferences() { + // preparation + when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode attributeValue = + createReferenceObject("nonExistingProductTypeKey", ProductType.referenceTypeId()); + final AttributeDraft attributeDraft = AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(attributeDraft).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void + resolveReferences_WithNullIdFieldInProductTypeReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, ProductType.referenceTypeId()); + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productTypeReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void + resolveReferences_WithNullNodeIdFieldInProductTypeReferenceAttribute_ShouldNotResolveReferences() { + // preparation + final ObjectNode attributeValue = JsonNodeFactory.instance.objectNode(); + attributeValue.put(REFERENCE_TYPE_ID_FIELD, ProductType.referenceTypeId()); + attributeValue.set(REFERENCE_ID_FIELD, JsonNodeFactory.instance.nullNode()); + + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productTypeReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(productVariantDraft); + } + + @Test + void resolveReferences_WithExistingProductTypeReferenceAttribute_ShouldResolveReferences() { + // preparation + final ObjectNode attributeValue = createReferenceObject("foo", ProductType.referenceTypeId()); + final AttributeDraft productTypeReferenceAttribute = + AttributeDraft.of("attributeName", attributeValue); + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productTypeReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + final AttributeDraft resolvedAttribute = resolvedAttributeDraft.getAttributes().get(0); + assertThat(resolvedAttribute).isNotNull(); + assertThat(resolvedAttribute.getValue().get(REFERENCE_ID_FIELD).asText()) + .isEqualTo(PRODUCT_TYPE_ID); + assertThat(resolvedAttribute.getValue().get(REFERENCE_TYPE_ID_FIELD).asText()) + .isEqualTo(ProductType.referenceTypeId()); + } + + @Test + void resolveReferences_WithProductTypeReferenceSetAttribute_ShouldResolveReferences() { + final AttributeDraft productTypeReferenceSetAttributeDraft = + getReferenceSetAttributeDraft( + "foo", createReferenceObject(UUID.randomUUID().toString(), ProductType.referenceTypeId()), createReferenceObject(UUID.randomUUID().toString(), ProductType.referenceTypeId())); - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productTypeReferenceSetAttributeDraft) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final List resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toList()); - assertThat(resolvedSet).isNotEmpty(); - final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); - resolvedReference.put(REFERENCE_TYPE_ID_FIELD, ProductType.referenceTypeId()); - resolvedReference.put(REFERENCE_ID_FIELD, PRODUCT_TYPE_ID); - assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference, resolvedReference); - } - - @Test - void resolveReferences_WithNonExistingProductTypeReferenceSetAttribute_ShouldNotResolveReferences() { - // preparation - when(productTypeService.fetchCachedProductTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode productTypeReference = - createReferenceObject(UUID.randomUUID().toString(), ProductType.referenceTypeId()); - final AttributeDraft productTypeReferenceAttribute = - getReferenceSetAttributeDraft("foo", productTypeReference); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productTypeReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final Set resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toSet()); - assertThat(resolvedSet).containsExactly(productTypeReference); - } - - @Test - void resolveReferences_WithSomeExistingProductTypeReferenceSetAttribute_ShouldResolveExistingReferences() { - // preparation - when(productTypeService.fetchCachedProductTypeId("existingKey")) - .thenReturn(CompletableFuture.completedFuture(Optional.of("existingId"))); - when(productTypeService.fetchCachedProductTypeId("randomKey")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ObjectNode productTypeReference1 = createReferenceObject("existingKey", ProductType.referenceTypeId()); - final ObjectNode productTypeReference2 = createReferenceObject("randomKey", ProductType.referenceTypeId()); - - final AttributeDraft productTypeReferenceAttribute = - getReferenceSetAttributeDraft("foo", productTypeReference1, productTypeReference2); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .attributes(productTypeReferenceAttribute) - .build(); - - // test - final ProductVariantDraft resolvedProductVariantDraft = - referenceResolver.resolveReferences(productVariantDraft) - .toCompletableFuture().join(); - - // assertions - assertThat(resolvedProductVariantDraft).isNotNull(); - assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); - - final AttributeDraft resolvedAttributeDraft = resolvedProductVariantDraft.getAttributes().get(0); - - assertThat(resolvedAttributeDraft).isNotNull(); - assertThat(resolvedAttributeDraft.getValue()).isNotNull(); - - final Spliterator attributeReferencesIterator = resolvedAttributeDraft.getValue().spliterator(); - assertThat(attributeReferencesIterator).isNotNull(); - final Set resolvedSet = StreamSupport.stream(attributeReferencesIterator, false) - .collect(Collectors.toSet()); - - final ObjectNode resolvedReference1 = createReferenceObject("existingId", ProductType.referenceTypeId()); - final ObjectNode resolvedReference2 = createReferenceObject("randomKey", ProductType.referenceTypeId()); - assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference1, resolvedReference2); - } + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productTypeReferenceSetAttributeDraft).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final List resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toList()); + assertThat(resolvedSet).isNotEmpty(); + final ObjectNode resolvedReference = JsonNodeFactory.instance.objectNode(); + resolvedReference.put(REFERENCE_TYPE_ID_FIELD, ProductType.referenceTypeId()); + resolvedReference.put(REFERENCE_ID_FIELD, PRODUCT_TYPE_ID); + assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference, resolvedReference); + } + + @Test + void + resolveReferences_WithNonExistingProductTypeReferenceSetAttribute_ShouldNotResolveReferences() { + // preparation + when(productTypeService.fetchCachedProductTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode productTypeReference = + createReferenceObject(UUID.randomUUID().toString(), ProductType.referenceTypeId()); + final AttributeDraft productTypeReferenceAttribute = + getReferenceSetAttributeDraft("foo", productTypeReference); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productTypeReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final Set resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toSet()); + assertThat(resolvedSet).containsExactly(productTypeReference); + } + + @Test + void + resolveReferences_WithSomeExistingProductTypeReferenceSetAttribute_ShouldResolveExistingReferences() { + // preparation + when(productTypeService.fetchCachedProductTypeId("existingKey")) + .thenReturn(CompletableFuture.completedFuture(Optional.of("existingId"))); + when(productTypeService.fetchCachedProductTypeId("randomKey")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ObjectNode productTypeReference1 = + createReferenceObject("existingKey", ProductType.referenceTypeId()); + final ObjectNode productTypeReference2 = + createReferenceObject("randomKey", ProductType.referenceTypeId()); + + final AttributeDraft productTypeReferenceAttribute = + getReferenceSetAttributeDraft("foo", productTypeReference1, productTypeReference2); + + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().attributes(productTypeReferenceAttribute).build(); + + // test + final ProductVariantDraft resolvedProductVariantDraft = + referenceResolver.resolveReferences(productVariantDraft).toCompletableFuture().join(); + + // assertions + assertThat(resolvedProductVariantDraft).isNotNull(); + assertThat(resolvedProductVariantDraft.getAttributes()).isNotNull(); + + final AttributeDraft resolvedAttributeDraft = + resolvedProductVariantDraft.getAttributes().get(0); + + assertThat(resolvedAttributeDraft).isNotNull(); + assertThat(resolvedAttributeDraft.getValue()).isNotNull(); + + final Spliterator attributeReferencesIterator = + resolvedAttributeDraft.getValue().spliterator(); + assertThat(attributeReferencesIterator).isNotNull(); + final Set resolvedSet = + StreamSupport.stream(attributeReferencesIterator, false).collect(Collectors.toSet()); + + final ObjectNode resolvedReference1 = + createReferenceObject("existingId", ProductType.referenceTypeId()); + final ObjectNode resolvedReference2 = + createReferenceObject("randomKey", ProductType.referenceTypeId()); + assertThat(resolvedSet).containsExactlyInAnyOrder(resolvedReference1, resolvedReference2); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCategoryReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCategoryReferencesTest.java index 05e7d7f53e..c8f5eab6b1 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCategoryReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCategoryReferencesTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCategoryService; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,45 +25,38 @@ import io.sphere.sdk.categories.Category; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.products.ProductVariantDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCategoryService; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithCategoryReferencesTest { - private CategoryService categoryService; - - private static final String CATEGORY_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - private static final String RES_SUB_ROOT = "withcategoryreferences/"; - private static final String NESTED_ATTRIBUTE_WITH_CATEGORY_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CATEGORY_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; - private static final String NESTED_ATTRIBUTE_WITH_SET_OF_CATEGORY_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - - @BeforeEach - void setup() { - categoryService = getMockCategoryService(CATEGORY_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private CategoryService categoryService; + + private static final String CATEGORY_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + private static final String RES_SUB_ROOT = "withcategoryreferences/"; + private static final String NESTED_ATTRIBUTE_WITH_CATEGORY_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String + NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CATEGORY_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; + private static final String NESTED_ATTRIBUTE_WITH_SET_OF_CATEGORY_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + + @BeforeEach + void setup() { + categoryService = getMockCategoryService(CATEGORY_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -61,105 +64,141 @@ void setup() { mock(ProductTypeService.class), categoryService, mock(CustomObjectService.class)); - } - - @Test - void resolveReferences_WithNestedCategoryReferenceAttributes_ShouldResolveReferences() { - // preparation - final ProductVariantDraft withNestedCategoryReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_CATEGORY_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedCategoryReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-1-name", CATEGORY_ID, - Category.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-2-name", CATEGORY_ID, - Category.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-3-name", CATEGORY_ID, - Category.referenceTypeId()); - } - - @Test - void resolveReferences_WithNestedSetOfCategoryReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - final ProductVariantDraft withNestedSetOfCategoryReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SET_OF_CATEGORY_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedSetOfCategoryReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", 2, CATEGORY_ID, Category.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", CATEGORY_ID, Category.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", CATEGORY_ID, Category.referenceTypeId()); - } - - @Test - void resolveReferences_WithSomeNonExistingNestedCategoryReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - when(categoryService.fetchCachedCategoryId("nonExistingCategoryKey1")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - when(categoryService.fetchCachedCategoryId("nonExistingCategoryKey3")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ProductVariantDraft withSomeNonExistingNestedCategoryReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CATEGORY_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSomeNonExistingNestedCategoryReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", - createReferenceObject("nonExistingCategoryKey1", Category.referenceTypeId()), - createReferenceObject(CATEGORY_ID, Category.referenceTypeId())); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", CATEGORY_ID, Category.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", "nonExistingCategoryKey3", Category.referenceTypeId()); - } + } + + @Test + void resolveReferences_WithNestedCategoryReferenceAttributes_ShouldResolveReferences() { + // preparation + final ProductVariantDraft withNestedCategoryReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_CATEGORY_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedCategoryReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + CATEGORY_ID, + Category.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CATEGORY_ID, + Category.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + CATEGORY_ID, + Category.referenceTypeId()); + } + + @Test + void + resolveReferences_WithNestedSetOfCategoryReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + final ProductVariantDraft withNestedSetOfCategoryReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SET_OF_CATEGORY_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedSetOfCategoryReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + 2, + CATEGORY_ID, + Category.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CATEGORY_ID, + Category.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + CATEGORY_ID, + Category.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSomeNonExistingNestedCategoryReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + when(categoryService.fetchCachedCategoryId("nonExistingCategoryKey1")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + when(categoryService.fetchCachedCategoryId("nonExistingCategoryKey3")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ProductVariantDraft withSomeNonExistingNestedCategoryReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CATEGORY_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSomeNonExistingNestedCategoryReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + createReferenceObject("nonExistingCategoryKey1", Category.referenceTypeId()), + createReferenceObject(CATEGORY_ID, Category.referenceTypeId())); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CATEGORY_ID, + Category.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + "nonExistingCategoryKey3", + Category.referenceTypeId()); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCustomObjectReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCustomObjectReferencesTest.java index 5c15b980ce..545106efb7 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCustomObjectReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCustomObjectReferencesTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomObjectService; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -16,45 +26,38 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.products.ProductVariantDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomObjectService; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithCustomObjectReferencesTest { - private CustomObjectService customObjectService; - - private static final String CUSTOM_OBJECT_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - private static final String RES_SUB_ROOT = "withcustomobjectreferences/"; - private static final String NESTED_ATTRIBUTE_WITH_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; - private static final String NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - - @BeforeEach - void setup() { - customObjectService = getMockCustomObjectService(CUSTOM_OBJECT_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private CustomObjectService customObjectService; + + private static final String CUSTOM_OBJECT_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + private static final String RES_SUB_ROOT = "withcustomobjectreferences/"; + private static final String NESTED_ATTRIBUTE_WITH_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String + NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; + private static final String NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + + @BeforeEach + void setup() { + customObjectService = getMockCustomObjectService(CUSTOM_OBJECT_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -62,113 +65,150 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), customObjectService); - } - - @Test - void resolveReferences_WithNestedCustomObjectReferenceAttributes_ShouldResolveReferences() { - // preparation - final ProductVariantDraft withNestedCustomObjReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedCustomObjReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-1-name", CUSTOM_OBJECT_ID, - CustomObject.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-2-name", CUSTOM_OBJECT_ID, - CustomObject.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-3-name", CUSTOM_OBJECT_ID, - CustomObject.referenceTypeId()); - } - - @Test - void resolveReferences_WithNestedSetOfCustomObjectReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - final ProductVariantDraft withNestedSetOfCustomObjReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedSetOfCustomObjReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", 2, CUSTOM_OBJECT_ID, CustomObject.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", CUSTOM_OBJECT_ID, CustomObject.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", CUSTOM_OBJECT_ID, CustomObject.referenceTypeId()); - } - - @Test - void resolveReferences_WithSomeNonExistingNestedCustomObjReferenceAttrs_ShouldOnlyResolveExistingReferences() { - // preparation - final CustomObjectCompositeIdentifier nonExistingCustomObject1Id = - CustomObjectCompositeIdentifier.of("non-existing-key-1", "non-existing-container"); - - when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObject1Id)) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final CustomObjectCompositeIdentifier nonExistingCustomObject3Id = - CustomObjectCompositeIdentifier.of("non-existing-key-3", "non-existing-container"); - - when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObject3Id)) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ProductVariantDraft withSomeNonExistingNestedCustomObjReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSomeNonExistingNestedCustomObjReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", - createReferenceObject("non-existing-container|non-existing-key-1", CustomObject.referenceTypeId()), - createReferenceObject(CUSTOM_OBJECT_ID, CustomObject.referenceTypeId())); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", CUSTOM_OBJECT_ID, CustomObject.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", "non-existing-container|non-existing-key-3", - CustomObject.referenceTypeId()); - } + } + + @Test + void resolveReferences_WithNestedCustomObjectReferenceAttributes_ShouldResolveReferences() { + // preparation + final ProductVariantDraft withNestedCustomObjReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedCustomObjReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + } + + @Test + void + resolveReferences_WithNestedSetOfCustomObjectReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + final ProductVariantDraft withNestedSetOfCustomObjReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedSetOfCustomObjReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + 2, + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSomeNonExistingNestedCustomObjReferenceAttrs_ShouldOnlyResolveExistingReferences() { + // preparation + final CustomObjectCompositeIdentifier nonExistingCustomObject1Id = + CustomObjectCompositeIdentifier.of("non-existing-key-1", "non-existing-container"); + + when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObject1Id)) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final CustomObjectCompositeIdentifier nonExistingCustomObject3Id = + CustomObjectCompositeIdentifier.of("non-existing-key-3", "non-existing-container"); + + when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObject3Id)) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ProductVariantDraft withSomeNonExistingNestedCustomObjReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSomeNonExistingNestedCustomObjReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + createReferenceObject( + "non-existing-container|non-existing-key-1", CustomObject.referenceTypeId()), + createReferenceObject(CUSTOM_OBJECT_ID, CustomObject.referenceTypeId())); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + "non-existing-container|non-existing-key-3", + CustomObject.referenceTypeId()); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCustomerReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCustomerReferencesTest.java index fade74f2a3..4ff3954245 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCustomerReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithCustomerReferencesTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,24 +20,22 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - class WithCustomerReferencesTest { - private VariantReferenceResolver referenceResolver; + private VariantReferenceResolver referenceResolver; - private static final String RES_SUB_ROOT = "withcustomerreferences/"; - private static final String NESTED_ATTRIBUTE_WITH_CUSTOMER_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOMER_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + private static final String RES_SUB_ROOT = "withcustomerreferences/"; + private static final String NESTED_ATTRIBUTE_WITH_CUSTOMER_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOMER_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - @BeforeEach - void setup() { - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + @BeforeEach + void setup() { + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -40,36 +43,39 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), mock(CustomObjectService.class)); - } + } - @Test - void resolveReferences_WithNestedCustomerReferenceAttributes_ShouldNotResolveReferences() { - // preparation - final ProductVariantDraft withNestedCustomerReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_CUSTOMER_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); + @Test + void resolveReferences_WithNestedCustomerReferenceAttributes_ShouldNotResolveReferences() { + // preparation + final ProductVariantDraft withNestedCustomerReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_CUSTOMER_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedCustomerReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(withNestedCustomerReferenceAttributes).isEqualTo(resolvedAttributeDraft); - } + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedCustomerReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(withNestedCustomerReferenceAttributes).isEqualTo(resolvedAttributeDraft); + } - @Test - void resolveReferences_WithNestedSetOfCustomerReferenceAttributes_ShouldNotResolveReferences() { - // preparation - final ProductVariantDraft withNestedSetOfCustomerReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOMER_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); + @Test + void resolveReferences_WithNestedSetOfCustomerReferenceAttributes_ShouldNotResolveReferences() { + // preparation + final ProductVariantDraft withNestedSetOfCustomerReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOMER_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedSetOfCustomerReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(withNestedSetOfCustomerReferenceAttributes).isEqualTo(resolvedAttributeDraft); - } + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedSetOfCustomerReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(withNestedSetOfCustomerReferenceAttributes).isEqualTo(resolvedAttributeDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithNoReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithNoReferencesTest.java index 405adc25ef..9f5c320c5b 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithNoReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithNoReferencesTest.java @@ -1,5 +1,9 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,25 +19,24 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - class WithNoReferencesTest { - private VariantReferenceResolver referenceResolver; + private VariantReferenceResolver referenceResolver; - static final String RES_ROOT = - "com/commercetools/sync/products/helpers/variantReferenceResolver/withnestedattributes/"; - private static final String RES_SUB_ROOT = "withnoreferences/"; - private static final String NESTED_ATTRIBUTE_WITH_TEXT_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-text-attributes.json"; - private static final String NESTED_ATTRIBUTE_WITH_SET_OF_TEXT_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-set-of-text-attributes.json"; + static final String RES_ROOT = + "com/commercetools/sync/products/helpers/variantReferenceResolver/withnestedattributes/"; + private static final String RES_SUB_ROOT = "withnoreferences/"; + private static final String NESTED_ATTRIBUTE_WITH_TEXT_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-text-attributes.json"; + private static final String NESTED_ATTRIBUTE_WITH_SET_OF_TEXT_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-set-of-text-attributes.json"; - @BeforeEach - void setup() { - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + @BeforeEach + void setup() { + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -41,35 +44,35 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), mock(CustomObjectService.class)); - } + } - @Test - void resolveReferences_WithNestedTextAttributes_ShouldReturnEqualDraft() { - // preparation - final ProductVariantDraft withNestedTextAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_TEXT_ATTRIBUTES, ProductVariantDraft.class); + @Test + void resolveReferences_WithNestedTextAttributes_ShouldReturnEqualDraft() { + // preparation + final ProductVariantDraft withNestedTextAttributes = + readObjectFromResource(NESTED_ATTRIBUTE_WITH_TEXT_ATTRIBUTES, ProductVariantDraft.class); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedTextAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(withNestedTextAttributes); - } + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver.resolveReferences(withNestedTextAttributes).toCompletableFuture().join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(withNestedTextAttributes); + } - @Test - void resolveReferences_WithNestedSetOfTextAttributes_ShouldReturnEqualDraft() { - // preparation - final ProductVariantDraft withNestedSetOfTextAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SET_OF_TEXT_ATTRIBUTES, ProductVariantDraft.class); + @Test + void resolveReferences_WithNestedSetOfTextAttributes_ShouldReturnEqualDraft() { + // preparation + final ProductVariantDraft withNestedSetOfTextAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SET_OF_TEXT_ATTRIBUTES, ProductVariantDraft.class); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedSetOfTextAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(withNestedSetOfTextAttributes); - } + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedSetOfTextAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(withNestedSetOfTextAttributes); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithProductReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithProductReferencesTest.java index 7727ca0c63..8bdf7c2d16 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithProductReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithProductReferencesTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductService; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,45 +25,37 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductVariantDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductService; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithProductReferencesTest { - private ProductService productService; - - private static final String PRODUCT_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - private static final String RES_SUB_ROOT = "withproductreferences/"; - private static final String NESTED_ATTRIBUTE_WITH_PRODUCT_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; - private static final String NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - - @BeforeEach - void setup() { - productService = getMockProductService(PRODUCT_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private ProductService productService; + + private static final String PRODUCT_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + private static final String RES_SUB_ROOT = "withproductreferences/"; + private static final String NESTED_ATTRIBUTE_WITH_PRODUCT_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; + private static final String NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + + @BeforeEach + void setup() { + productService = getMockProductService(PRODUCT_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -61,105 +63,141 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), mock(CustomObjectService.class)); - } - - @Test - void resolveReferences_WithNestedProductReferenceAttributes_ShouldResolveReferences() { - // preparation - final ProductVariantDraft withNestedProductReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_PRODUCT_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedProductReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-1-name", PRODUCT_ID, - Product.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-2-name", PRODUCT_ID, - Product.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-3-name", PRODUCT_ID, - Product.referenceTypeId()); - } - - @Test - void resolveReferences_WithNestedSetOfProductReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - final ProductVariantDraft withNestedSetOfProductReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedSetOfProductReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", 2, PRODUCT_ID, Product.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", PRODUCT_ID, Product.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", PRODUCT_ID, Product.referenceTypeId()); - } - - @Test - void resolveReferences_WithSomeNonExistingNestedProductReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - when(productService.getIdFromCacheOrFetch("nonExistingProductKey1")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - when(productService.getIdFromCacheOrFetch("nonExistingProductKey3")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ProductVariantDraft withSomeNonExistingNestedProductReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSomeNonExistingNestedProductReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", - createReferenceObject("nonExistingProductKey1", Product.referenceTypeId()), - createReferenceObject(PRODUCT_ID, Product.referenceTypeId())); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", PRODUCT_ID, Product.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", "nonExistingProductKey3", Product.referenceTypeId()); - } + } + + @Test + void resolveReferences_WithNestedProductReferenceAttributes_ShouldResolveReferences() { + // preparation + final ProductVariantDraft withNestedProductReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_PRODUCT_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedProductReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + PRODUCT_ID, + Product.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_ID, + Product.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + PRODUCT_ID, + Product.referenceTypeId()); + } + + @Test + void + resolveReferences_WithNestedSetOfProductReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + final ProductVariantDraft withNestedSetOfProductReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedSetOfProductReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + 2, + PRODUCT_ID, + Product.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_ID, + Product.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + PRODUCT_ID, + Product.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSomeNonExistingNestedProductReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + when(productService.getIdFromCacheOrFetch("nonExistingProductKey1")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + when(productService.getIdFromCacheOrFetch("nonExistingProductKey3")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ProductVariantDraft withSomeNonExistingNestedProductReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSomeNonExistingNestedProductReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + createReferenceObject("nonExistingProductKey1", Product.referenceTypeId()), + createReferenceObject(PRODUCT_ID, Product.referenceTypeId())); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_ID, + Product.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + "nonExistingProductKey3", + Product.referenceTypeId()); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithProductTypeReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithProductTypeReferencesTest.java index b3da5ce6e1..08dca2e858 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithProductTypeReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withnestedattributes/WithProductTypeReferencesTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductTypeService; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,45 +25,38 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductTypeService; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.withnestedattributes.WithNoReferencesTest.RES_ROOT; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithProductTypeReferencesTest { - private ProductTypeService productTypeService; - - private static final String PRODUCT_TYPE_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - private static final String RES_SUB_ROOT = "withproducttypereferences/"; - private static final String NESTED_ATTRIBUTE_WITH_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; - private static final String NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - - @BeforeEach - void setup() { - productTypeService = getMockProductTypeService(PRODUCT_TYPE_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private ProductTypeService productTypeService; + + private static final String PRODUCT_TYPE_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + private static final String RES_SUB_ROOT = "withproducttypereferences/"; + private static final String NESTED_ATTRIBUTE_WITH_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String + NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; + private static final String NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + + @BeforeEach + void setup() { + productTypeService = getMockProductTypeService(PRODUCT_TYPE_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -61,105 +64,142 @@ void setup() { productTypeService, mock(CategoryService.class), mock(CustomObjectService.class)); - } - - @Test - void resolveReferences_WithNestedProductTypeReferenceAttributes_ShouldResolveReferences() { - // preparation - final ProductVariantDraft withNestedProductTypeReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedProductTypeReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-1-name", PRODUCT_TYPE_ID, - ProductType.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-2-name", PRODUCT_TYPE_ID, - ProductType.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-3-name", PRODUCT_TYPE_ID, - ProductType.referenceTypeId()); - } - - @Test - void resolveReferences_WithNestedSetOfProductTypeReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - final ProductVariantDraft withNestedSetOfProductTypeReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withNestedSetOfProductTypeReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", 2, PRODUCT_TYPE_ID, ProductType.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", PRODUCT_TYPE_ID, ProductType.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", PRODUCT_TYPE_ID, ProductType.referenceTypeId()); - } - - @Test - void resolveReferences_WithSomeNonExistingNestedProdTypeReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey1")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey3")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ProductVariantDraft withSomeNonExistingNestedProductTypeReferenceAttributes = - readObjectFromResource(NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSomeNonExistingNestedProductTypeReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributes = (ArrayNode) value; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributes.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", - createReferenceObject("nonExistingProductTypeKey1", ProductType.referenceTypeId()), - createReferenceObject(PRODUCT_TYPE_ID, ProductType.referenceTypeId())); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", PRODUCT_TYPE_ID, ProductType.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", "nonExistingProductTypeKey3", ProductType.referenceTypeId()); - } + } + + @Test + void resolveReferences_WithNestedProductTypeReferenceAttributes_ShouldResolveReferences() { + // preparation + final ProductVariantDraft withNestedProductTypeReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedProductTypeReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + } + + @Test + void + resolveReferences_WithNestedSetOfProductTypeReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + final ProductVariantDraft withNestedSetOfProductTypeReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withNestedSetOfProductTypeReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + 2, + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSomeNonExistingNestedProdTypeReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey1")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey3")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ProductVariantDraft withSomeNonExistingNestedProductTypeReferenceAttributes = + readObjectFromResource( + NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSomeNonExistingNestedProductTypeReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributes = (ArrayNode) value; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributes.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + createReferenceObject("nonExistingProductTypeKey1", ProductType.referenceTypeId()), + createReferenceObject(PRODUCT_TYPE_ID, ProductType.referenceTypeId())); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + "nonExistingProductTypeKey3", + ProductType.referenceTypeId()); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCategoryReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCategoryReferencesTest.java index c73d1d9edd..98999e2a15 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCategoryReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCategoryReferencesTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withsetofnestedattributes; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCategoryService; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,44 +24,38 @@ import io.sphere.sdk.categories.Category; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.products.ProductVariantDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCategoryService; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithCategoryReferencesTest { - private CategoryService categoryService; - - private static final String CATEGORY_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - private static final String RES_SUB_ROOT = "withcategoryreferences/"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_CATEGORY_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CATEGORY_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CATEGORY_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - - @BeforeEach - void setup() { - categoryService = getMockCategoryService(CATEGORY_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private CategoryService categoryService; + + private static final String CATEGORY_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + private static final String RES_SUB_ROOT = "withcategoryreferences/"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_CATEGORY_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String + SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CATEGORY_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CATEGORY_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + + @BeforeEach + void setup() { + categoryService = getMockCategoryService(CATEGORY_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -60,119 +63,155 @@ void setup() { mock(ProductTypeService.class), categoryService, mock(CustomObjectService.class)); - } - - @Test - void resolveReferences_WithSetOfNestedCategoryReferenceAttributes_ShouldResolveReferences() { - // preparation - final ProductVariantDraft withSetOfNestedCategoryReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_CATEGORY_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedCategoryReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-1-name", CATEGORY_ID, - Category.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-2-name", CATEGORY_ID, - Category.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-3-name", CATEGORY_ID, - Category.referenceTypeId()); - } - - @Test - void resolveReferences_WithSetOfNestedSetOfCategoryReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - final ProductVariantDraft withSetOfNestedSetOfCategoryReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CATEGORY_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedSetOfCategoryReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", 2, CATEGORY_ID, Category.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", CATEGORY_ID, Category.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", CATEGORY_ID, Category.referenceTypeId()); - } - - @Test - void resolveReferences_WithSetOfNestedCategoryReferenceSetOfSomeNonExisting_ShouldOnlyResolveExistingReferences() { - // preparation - when(categoryService.fetchCachedCategoryId("nonExistingCategoryKey1")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - when(categoryService.fetchCachedCategoryId("nonExistingCategoryKey3")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ProductVariantDraft withSetOfNestedCategoryReferenceSetWithSomeNonExisting = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CATEGORY_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedCategoryReferenceSetWithSomeNonExisting) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", - createReferenceObject("nonExistingCategoryKey1", Category.referenceTypeId()), - createReferenceObject(CATEGORY_ID, Category.referenceTypeId())); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", CATEGORY_ID, Category.referenceTypeId()); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", "nonExistingCategoryKey3", Category.referenceTypeId()); - } + } + + @Test + void resolveReferences_WithSetOfNestedCategoryReferenceAttributes_ShouldResolveReferences() { + // preparation + final ProductVariantDraft withSetOfNestedCategoryReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_CATEGORY_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedCategoryReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + CATEGORY_ID, + Category.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CATEGORY_ID, + Category.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + CATEGORY_ID, + Category.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSetOfNestedSetOfCategoryReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + final ProductVariantDraft withSetOfNestedSetOfCategoryReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CATEGORY_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedSetOfCategoryReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + 2, + CATEGORY_ID, + Category.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CATEGORY_ID, + Category.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + CATEGORY_ID, + Category.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSetOfNestedCategoryReferenceSetOfSomeNonExisting_ShouldOnlyResolveExistingReferences() { + // preparation + when(categoryService.fetchCachedCategoryId("nonExistingCategoryKey1")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + when(categoryService.fetchCachedCategoryId("nonExistingCategoryKey3")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ProductVariantDraft withSetOfNestedCategoryReferenceSetWithSomeNonExisting = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CATEGORY_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedCategoryReferenceSetWithSomeNonExisting) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + createReferenceObject("nonExistingCategoryKey1", Category.referenceTypeId()), + createReferenceObject(CATEGORY_ID, Category.referenceTypeId())); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CATEGORY_ID, + Category.referenceTypeId()); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + "nonExistingCategoryKey3", + Category.referenceTypeId()); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCustomObjectReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCustomObjectReferencesTest.java index 452124634b..adc3204b92 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCustomObjectReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCustomObjectReferencesTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withsetofnestedattributes; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomObjectService; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -16,44 +25,39 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.customobjects.CustomObject; import io.sphere.sdk.products.ProductVariantDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockCustomObjectService; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithCustomObjectReferencesTest { - private CustomObjectService customObjectService; - - private static final String CUSTOM_OBJECT_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - private static final String RES_SUB_ROOT = "withcustomobjectreferences/"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - - @BeforeEach - void setup() { - customObjectService = getMockCustomObjectService(CUSTOM_OBJECT_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private CustomObjectService customObjectService; + + private static final String CUSTOM_OBJECT_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + private static final String RES_SUB_ROOT = "withcustomobjectreferences/"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String + SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; + private static final String + SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + + @BeforeEach + void setup() { + customObjectService = getMockCustomObjectService(CUSTOM_OBJECT_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -61,127 +65,164 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), customObjectService); - } - - @Test - void resolveReferences_WithSetOfNestedCustomObjectReferenceAttributes_ShouldResolveReferences() { - // preparation - final ProductVariantDraft withSetOfNestedCustomObjectReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedCustomObjectReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-1-name", CUSTOM_OBJECT_ID, - CustomObject.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-2-name", CUSTOM_OBJECT_ID, - CustomObject.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-3-name", CUSTOM_OBJECT_ID, - CustomObject.referenceTypeId()); - } - - @Test - void resolveReferences_WithSetOfNestedSetOfCustomObjectReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - final ProductVariantDraft withSetOfNestedSetOfCustomObjectReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedSetOfCustomObjectReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", 2, CUSTOM_OBJECT_ID, CustomObject.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", CUSTOM_OBJECT_ID, CustomObject.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", CUSTOM_OBJECT_ID, CustomObject.referenceTypeId()); - } - - @Test - void resolveReferences_WithSetOfNestedProdTypeReferenceSetOfSomeNonExisting_ShouldOnlyResolveExistingReferences() { - // preparation - final CustomObjectCompositeIdentifier nonExistingCustomObject1Id = - CustomObjectCompositeIdentifier.of("non-existing-key-1", "non-existing-container"); - - when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObject1Id)) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final CustomObjectCompositeIdentifier nonExistingCustomObject3Id = - CustomObjectCompositeIdentifier.of("non-existing-key-3", "non-existing-container"); - - when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObject3Id)) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - - final ProductVariantDraft withSetOfNestedCustomObjectReferenceSetWithSomeNonExisting = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedCustomObjectReferenceSetWithSomeNonExisting) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", - createReferenceObject("non-existing-container|non-existing-key-1", CustomObject.referenceTypeId()), - createReferenceObject(CUSTOM_OBJECT_ID, CustomObject.referenceTypeId())); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", CUSTOM_OBJECT_ID, CustomObject.referenceTypeId()); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", "non-existing-container|non-existing-key-3", CustomObject.referenceTypeId()); - } + } + + @Test + void resolveReferences_WithSetOfNestedCustomObjectReferenceAttributes_ShouldResolveReferences() { + // preparation + final ProductVariantDraft withSetOfNestedCustomObjectReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedCustomObjectReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSetOfNestedSetOfCustomObjectReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + final ProductVariantDraft withSetOfNestedSetOfCustomObjectReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedSetOfCustomObjectReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + 2, + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSetOfNestedProdTypeReferenceSetOfSomeNonExisting_ShouldOnlyResolveExistingReferences() { + // preparation + final CustomObjectCompositeIdentifier nonExistingCustomObject1Id = + CustomObjectCompositeIdentifier.of("non-existing-key-1", "non-existing-container"); + + when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObject1Id)) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final CustomObjectCompositeIdentifier nonExistingCustomObject3Id = + CustomObjectCompositeIdentifier.of("non-existing-key-3", "non-existing-container"); + + when(customObjectService.fetchCachedCustomObjectId(nonExistingCustomObject3Id)) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ProductVariantDraft withSetOfNestedCustomObjectReferenceSetWithSomeNonExisting = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_CUSTOM_OBJECT_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedCustomObjectReferenceSetWithSomeNonExisting) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + createReferenceObject( + "non-existing-container|non-existing-key-1", CustomObject.referenceTypeId()), + createReferenceObject(CUSTOM_OBJECT_ID, CustomObject.referenceTypeId())); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + CUSTOM_OBJECT_ID, + CustomObject.referenceTypeId()); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + "non-existing-container|non-existing-key-3", + CustomObject.referenceTypeId()); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCustomerReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCustomerReferencesTest.java index c2b8d586af..d8d43bf5dc 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCustomerReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithCustomerReferencesTest.java @@ -1,5 +1,9 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withsetofnestedattributes; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,24 +19,23 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - class WithCustomerReferencesTest { - private VariantReferenceResolver referenceResolver; + private VariantReferenceResolver referenceResolver; - private static final String RES_SUB_ROOT = "withcustomerreferences/"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_CUSTOMER_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOMER_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + private static final String RES_SUB_ROOT = "withcustomerreferences/"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_CUSTOMER_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOMER_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - @BeforeEach - void setup() { - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + @BeforeEach + void setup() { + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -40,39 +43,43 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), mock(CustomObjectService.class)); - } + } - @Test - void resolveReferences_WithSetOfNestedCustomerReferenceAttributes_ShouldNotResolveReferences() { - // preparation - final ProductVariantDraft withSetOfNestedCustomerReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_CUSTOMER_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); + @Test + void resolveReferences_WithSetOfNestedCustomerReferenceAttributes_ShouldNotResolveReferences() { + // preparation + final ProductVariantDraft withSetOfNestedCustomerReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_CUSTOMER_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedCustomerReferenceAttributes) - .toCompletableFuture() - .join(); + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedCustomerReferenceAttributes) + .toCompletableFuture() + .join(); - // assertions - assertThat(withSetOfNestedCustomerReferenceAttributes).isEqualTo(resolvedAttributeDraft); - } + // assertions + assertThat(withSetOfNestedCustomerReferenceAttributes).isEqualTo(resolvedAttributeDraft); + } - @Test - void resolveReferences_WithSetOfNestedSetOfCustomerReferenceAttributes_ShouldNotResolveReferences() { - // preparation - final ProductVariantDraft withSetOfNestedSetOfCustomerReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOMER_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); + @Test + void + resolveReferences_WithSetOfNestedSetOfCustomerReferenceAttributes_ShouldNotResolveReferences() { + // preparation + final ProductVariantDraft withSetOfNestedSetOfCustomerReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_CUSTOMER_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedSetOfCustomerReferenceAttributes) - .toCompletableFuture() - .join(); + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedSetOfCustomerReferenceAttributes) + .toCompletableFuture() + .join(); - // assertions - assertThat(withSetOfNestedSetOfCustomerReferenceAttributes).isEqualTo(resolvedAttributeDraft); - } + // assertions + assertThat(withSetOfNestedSetOfCustomerReferenceAttributes).isEqualTo(resolvedAttributeDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithNoReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithNoReferencesTest.java index 37bddc59f7..947d6214fe 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithNoReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithNoReferencesTest.java @@ -1,5 +1,9 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withsetofnestedattributes; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,25 +19,24 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - class WithNoReferencesTest { - private VariantReferenceResolver referenceResolver; + private VariantReferenceResolver referenceResolver; - static final String RES_ROOT = - "com/commercetools/sync/products/helpers/variantReferenceResolver/withsetofnestedattributes/"; - private static final String RES_SUB_ROOT = "withnoreferences/"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_TEXT_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-text-attributes.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_TEXT_ATTRIBUTES = - RES_ROOT + RES_SUB_ROOT + "with-set-of-text-attributes.json"; + static final String RES_ROOT = + "com/commercetools/sync/products/helpers/variantReferenceResolver/withsetofnestedattributes/"; + private static final String RES_SUB_ROOT = "withnoreferences/"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_TEXT_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-text-attributes.json"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_TEXT_ATTRIBUTES = + RES_ROOT + RES_SUB_ROOT + "with-set-of-text-attributes.json"; - @BeforeEach - void setup() { - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + @BeforeEach + void setup() { + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -41,35 +44,39 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), mock(CustomObjectService.class)); - } + } - @Test - void resolveReferences_WithSetOfNestedTextAttributes_ShouldReturnEqualDraft() { - // preparation - final ProductVariantDraft withSetOfNestedTextAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_TEXT_ATTRIBUTES, ProductVariantDraft.class); + @Test + void resolveReferences_WithSetOfNestedTextAttributes_ShouldReturnEqualDraft() { + // preparation + final ProductVariantDraft withSetOfNestedTextAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_TEXT_ATTRIBUTES, ProductVariantDraft.class); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedTextAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(withSetOfNestedTextAttributes); - } + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedTextAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(withSetOfNestedTextAttributes); + } - @Test - void resolveReferences_WithSetOfNestedSetOfTextAttributes_ShouldReturnEqualDraft() { - // preparation - final ProductVariantDraft withSetOfNestedSetOfTextAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_TEXT_ATTRIBUTES, ProductVariantDraft.class); + @Test + void resolveReferences_WithSetOfNestedSetOfTextAttributes_ShouldReturnEqualDraft() { + // preparation + final ProductVariantDraft withSetOfNestedSetOfTextAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_TEXT_ATTRIBUTES, ProductVariantDraft.class); - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedSetOfTextAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft).isEqualTo(withSetOfNestedSetOfTextAttributes); - } + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedSetOfTextAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft).isEqualTo(withSetOfNestedSetOfTextAttributes); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithProductReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithProductReferencesTest.java index cfab6f87f9..22b35a352b 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithProductReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithProductReferencesTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withsetofnestedattributes; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductService; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,44 +24,38 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductVariantDraft; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductService; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithProductReferencesTest { - private ProductService productService; - - private static final String PRODUCT_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - private static final String RES_SUB_ROOT = "withproductreferences/"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_PRODUCT_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - - @BeforeEach - void setup() { - productService = getMockProductService(PRODUCT_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private ProductService productService; + + private static final String PRODUCT_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + private static final String RES_SUB_ROOT = "withproductreferences/"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_PRODUCT_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String + SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + + @BeforeEach + void setup() { + productService = getMockProductService(PRODUCT_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -60,119 +63,155 @@ void setup() { mock(ProductTypeService.class), mock(CategoryService.class), mock(CustomObjectService.class)); - } - - @Test - void resolveReferences_WithSetOfNestedProductReferenceAttributes_ShouldResolveReferences() { - // preparation - final ProductVariantDraft withSetOfNestedProductReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_PRODUCT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedProductReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-1-name", PRODUCT_ID, - Product.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-2-name", PRODUCT_ID, - Product.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-3-name", PRODUCT_ID, - Product.referenceTypeId()); - } - - @Test - void resolveReferences_WithSetOfNestedSetOfProductReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - final ProductVariantDraft withSetOfNestedSetOfProductReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedSetOfProductReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", 2, PRODUCT_ID, Product.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", PRODUCT_ID, Product.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", PRODUCT_ID, Product.referenceTypeId()); - } - - @Test - void resolveReferences_WithSetOfNestedProductReferenceSetWithSomeNonExisting_ShouldOnlyResolveExistingReferences() { - // preparation - when(productService.getIdFromCacheOrFetch("nonExistingProductKey1")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - when(productService.getIdFromCacheOrFetch("nonExistingProductKey3")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ProductVariantDraft withSetOfNestedProductReferenceSetWithSomeNonExisting = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedProductReferenceSetWithSomeNonExisting) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", - createReferenceObject("nonExistingProductKey1", Product.referenceTypeId()), - createReferenceObject(PRODUCT_ID, Product.referenceTypeId())); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", PRODUCT_ID, Product.referenceTypeId()); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", "nonExistingProductKey3", Product.referenceTypeId()); - } + } + + @Test + void resolveReferences_WithSetOfNestedProductReferenceAttributes_ShouldResolveReferences() { + // preparation + final ProductVariantDraft withSetOfNestedProductReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_PRODUCT_REFERENCE_ATTRIBUTES, ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedProductReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + PRODUCT_ID, + Product.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_ID, + Product.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + PRODUCT_ID, + Product.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSetOfNestedSetOfProductReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + final ProductVariantDraft withSetOfNestedSetOfProductReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedSetOfProductReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + 2, + PRODUCT_ID, + Product.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_ID, + Product.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + PRODUCT_ID, + Product.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSetOfNestedProductReferenceSetWithSomeNonExisting_ShouldOnlyResolveExistingReferences() { + // preparation + when(productService.getIdFromCacheOrFetch("nonExistingProductKey1")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + when(productService.getIdFromCacheOrFetch("nonExistingProductKey3")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ProductVariantDraft withSetOfNestedProductReferenceSetWithSomeNonExisting = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedProductReferenceSetWithSomeNonExisting) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + createReferenceObject("nonExistingProductKey1", Product.referenceTypeId()), + createReferenceObject(PRODUCT_ID, Product.referenceTypeId())); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_ID, + Product.referenceTypeId()); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + "nonExistingProductKey3", + Product.referenceTypeId()); + } } diff --git a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithProductTypeReferencesTest.java b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithProductTypeReferencesTest.java index 20029b3921..25e9b2351c 100644 --- a/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithProductTypeReferencesTest.java +++ b/src/test/java/com/commercetools/sync/products/helpers/variantreferenceresolver/withsetofnestedattributes/WithProductTypeReferencesTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.products.helpers.variantreferenceresolver.withsetofnestedattributes; +import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; +import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductTypeService; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; +import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.commercetools.sync.products.helpers.VariantReferenceResolver; @@ -15,44 +24,39 @@ import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.StreamSupport; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createReferenceObject; -import static com.commercetools.sync.products.ProductSyncMockUtils.getMockProductTypeService; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceAttributeValue; -import static com.commercetools.sync.products.helpers.variantreferenceresolver.AssertionUtilsForVariantReferenceResolver.assertReferenceSetAttributeValue; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class WithProductTypeReferencesTest { - private ProductTypeService productTypeService; - - private static final String PRODUCT_TYPE_ID = UUID.randomUUID().toString(); - private VariantReferenceResolver referenceResolver; - - private static final String RES_SUB_ROOT = "withproducttypereferences/"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; - private static final String SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = - WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; - - @BeforeEach - void setup() { - productTypeService = getMockProductTypeService(PRODUCT_TYPE_ID); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new VariantReferenceResolver(syncOptions, + private ProductTypeService productTypeService; + + private static final String PRODUCT_TYPE_ID = UUID.randomUUID().toString(); + private VariantReferenceResolver referenceResolver; + + private static final String RES_SUB_ROOT = "withproducttypereferences/"; + private static final String SET_OF_NESTED_ATTRIBUTE_WITH_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-reference.json"; + private static final String + SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-non-existing-references.json"; + private static final String + SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_TYPE_REFERENCE_ATTRIBUTES = + WithNoReferencesTest.RES_ROOT + RES_SUB_ROOT + "with-set-of-references.json"; + + @BeforeEach + void setup() { + productTypeService = getMockProductTypeService(PRODUCT_TYPE_ID); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new VariantReferenceResolver( + syncOptions, mock(TypeService.class), mock(ChannelService.class), mock(CustomerGroupService.class), @@ -60,119 +64,156 @@ void setup() { productTypeService, mock(CategoryService.class), mock(CustomObjectService.class)); - } - - @Test - void resolveReferences_WithSetOfNestedProductTypeReferenceAttributes_ShouldResolveReferences() { - // preparation - final ProductVariantDraft withSetOfNestedProductTypeReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedProductTypeReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-1-name", PRODUCT_TYPE_ID, - ProductType.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-2-name", PRODUCT_TYPE_ID, - ProductType.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, "nested-attribute-3-name", PRODUCT_TYPE_ID, - ProductType.referenceTypeId()); - } - - @Test - void resolveReferences_WithSetOfNestedSetOfProductTypeReferenceAttributes_ShouldOnlyResolveExistingReferences() { - // preparation - final ProductVariantDraft withSetOfNestedSetOfProductTypeReferenceAttributes = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedSetOfProductTypeReferenceAttributes) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", 2, PRODUCT_TYPE_ID, ProductType.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", PRODUCT_TYPE_ID, ProductType.referenceTypeId()); - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", PRODUCT_TYPE_ID, ProductType.referenceTypeId()); - } - - @Test - void resolveReferences_WithSetOfNestedProdTypeReferenceSetOfSomeNonExisting_ShouldOnlyResolveExistingReferences() { - // preparation - when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey1")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey3")) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final ProductVariantDraft withSetOfNestedProductTypeReferenceSetWithSomeNonExisting = - readObjectFromResource(SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, - ProductVariantDraft.class); - - // test - final ProductVariantDraft resolvedAttributeDraft = - referenceResolver.resolveReferences(withSetOfNestedProductTypeReferenceSetWithSomeNonExisting) - .toCompletableFuture() - .join(); - // assertions - assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); - - final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); - assertThat(value).isInstanceOf(ArrayNode.class); - final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; - - final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); - assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); - final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; - - final Map resolvedNestedAttributesMap = StreamSupport - .stream(resolvedNestedAttributeAsArray.spliterator(), false) - .collect(Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); - - assertReferenceSetAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-1-name", - createReferenceObject("nonExistingProductTypeKey1", ProductType.referenceTypeId()), - createReferenceObject(PRODUCT_TYPE_ID, ProductType.referenceTypeId())); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-2-name", PRODUCT_TYPE_ID, ProductType.referenceTypeId()); - - assertReferenceAttributeValue(resolvedNestedAttributesMap, - "nested-attribute-3-name", "nonExistingProductTypeKey3", ProductType.referenceTypeId()); - } + } + + @Test + void resolveReferences_WithSetOfNestedProductTypeReferenceAttributes_ShouldResolveReferences() { + // preparation + final ProductVariantDraft withSetOfNestedProductTypeReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedProductTypeReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSetOfNestedSetOfProductTypeReferenceAttributes_ShouldOnlyResolveExistingReferences() { + // preparation + final ProductVariantDraft withSetOfNestedSetOfProductTypeReferenceAttributes = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SET_OF_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedSetOfProductTypeReferenceAttributes) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + 2, + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + } + + @Test + void + resolveReferences_WithSetOfNestedProdTypeReferenceSetOfSomeNonExisting_ShouldOnlyResolveExistingReferences() { + // preparation + when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey1")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + when(productTypeService.fetchCachedProductTypeId("nonExistingProductTypeKey3")) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final ProductVariantDraft withSetOfNestedProductTypeReferenceSetWithSomeNonExisting = + readObjectFromResource( + SET_OF_NESTED_ATTRIBUTE_WITH_SOME_NOT_EXISTING_PRODUCT_TYPE_REFERENCE_ATTRIBUTES, + ProductVariantDraft.class); + + // test + final ProductVariantDraft resolvedAttributeDraft = + referenceResolver + .resolveReferences(withSetOfNestedProductTypeReferenceSetWithSomeNonExisting) + .toCompletableFuture() + .join(); + // assertions + assertThat(resolvedAttributeDraft.getAttributes()).isNotNull(); + + final JsonNode value = resolvedAttributeDraft.getAttributes().get(0).getValue(); + assertThat(value).isInstanceOf(ArrayNode.class); + final ArrayNode setOfResolvedNestedAttributes = (ArrayNode) value; + + final JsonNode resolvedNestedAttribute = setOfResolvedNestedAttributes.get(0); + assertThat(resolvedNestedAttribute).isInstanceOf(ArrayNode.class); + final ArrayNode resolvedNestedAttributeAsArray = (ArrayNode) resolvedNestedAttribute; + + final Map resolvedNestedAttributesMap = + StreamSupport.stream(resolvedNestedAttributeAsArray.spliterator(), false) + .collect( + Collectors.toMap(jsonNode -> jsonNode.get("name").asText(), jsonNode -> jsonNode)); + + assertReferenceSetAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-1-name", + createReferenceObject("nonExistingProductTypeKey1", ProductType.referenceTypeId()), + createReferenceObject(PRODUCT_TYPE_ID, ProductType.referenceTypeId())); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-2-name", + PRODUCT_TYPE_ID, + ProductType.referenceTypeId()); + + assertReferenceAttributeValue( + resolvedNestedAttributesMap, + "nested-attribute-3-name", + "nonExistingProductTypeKey3", + ProductType.referenceTypeId()); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/ProductReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/products/utils/ProductReferenceResolutionUtilsTest.java index 7543985f68..7464333991 100644 --- a/src/test/java/com/commercetools/sync/products/utils/ProductReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/ProductReferenceResolutionUtilsTest.java @@ -1,5 +1,18 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.commons.MockUtils.getAssetMockWithCustomFields; +import static com.commercetools.sync.commons.MockUtils.getTypeMock; +import static com.commercetools.sync.products.ProductSyncMockUtils.getChannelMock; +import static com.commercetools.sync.products.ProductSyncMockUtils.getPriceMockWithReferences; +import static com.commercetools.sync.products.ProductSyncMockUtils.getProductVariantMock; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.data.MapEntry.entry; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.helpers.CategoryReferencePair; import io.sphere.sdk.categories.Category; import io.sphere.sdk.channels.Channel; @@ -23,11 +36,6 @@ import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -35,471 +43,534 @@ import java.util.Map; import java.util.Set; import java.util.UUID; - -import static com.commercetools.sync.commons.MockUtils.getAssetMockWithCustomFields; -import static com.commercetools.sync.commons.MockUtils.getTypeMock; -import static com.commercetools.sync.products.ProductSyncMockUtils.getChannelMock; -import static com.commercetools.sync.products.ProductSyncMockUtils.getPriceMockWithReferences; -import static com.commercetools.sync.products.ProductSyncMockUtils.getProductVariantMock; -import static java.util.Arrays.asList; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.data.MapEntry.entry; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; class ProductReferenceResolutionUtilsTest { - @Test - void mapToProductDrafts_WithSomeExpandedReferences_ShouldReplaceReferencesWhereExpanded() { - final String resourceKey = "key"; - final ProductType productType = getProductTypeMock(resourceKey); - final Reference productTypeReference = - Reference.ofResourceTypeIdAndIdAndObj(ProductType.referenceTypeId(), productType.getId(), productType); - final Reference nonExpandedProductTypeReference = ProductType.referenceOfId(productType.getId()); - - final TaxCategory taxCategory = getTaxCategoryMock(resourceKey); - final Reference taxCategoryReference = - Reference.ofResourceTypeIdAndIdAndObj(TaxCategory.referenceTypeId(), taxCategory.getId(), taxCategory); - final Reference nonExpandedTaxCategoryReference = TaxCategory.referenceOfId(taxCategory.getId()); - - final State state = getStateMock(resourceKey); - final Reference stateReference = - Reference.ofResourceTypeIdAndIdAndObj(State.referenceTypeId(), state.getId(), state); - final Reference nonExpandedStateReference = State.referenceOfId(state.getId()); - - final Channel channel = getChannelMock(resourceKey); - - final Reference channelReference = Reference - .ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel.getId(), channel); - final Price price = getPriceMockWithReferences(channelReference, null, null); - - final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - final Asset asset1 = - getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType)); - final Asset asset2 = - getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), - UUID.randomUUID().toString())); - - final ProductVariant productVariant = getProductVariantMock(singletonList(price), asList(asset1, asset2)); - - final Product productWithNonExpandedProductType = getProductMock(singletonList(productVariant)); - - when(productWithNonExpandedProductType.getProductType()) - .thenReturn(nonExpandedProductTypeReference); - when(productWithNonExpandedProductType.getTaxCategory()).thenReturn(taxCategoryReference); - when(productWithNonExpandedProductType.getState()).thenReturn(stateReference); - - final Product productWithNonExpandedTaxCategory = getProductMock(singletonList(productVariant)); - - when(productWithNonExpandedTaxCategory.getProductType()).thenReturn(productTypeReference); - when(productWithNonExpandedTaxCategory.getTaxCategory()) - .thenReturn(nonExpandedTaxCategoryReference); - when(productWithNonExpandedTaxCategory.getState()).thenReturn(stateReference); - - final Product productWithNonExpandedSate = getProductMock(singletonList(productVariant)); - - when(productWithNonExpandedSate.getProductType()).thenReturn(productTypeReference); - when(productWithNonExpandedSate.getTaxCategory()).thenReturn(taxCategoryReference); - when(productWithNonExpandedSate.getState()).thenReturn(nonExpandedStateReference); - - - final List products = - asList(productWithNonExpandedProductType, productWithNonExpandedTaxCategory, productWithNonExpandedSate); - - - final List productDraftsWithKeysOnReferences = ProductReferenceResolutionUtils - .mapToProductDrafts(products); - - assertThat(productDraftsWithKeysOnReferences).extracting(ProductDraft::getProductType) - .asList() - .containsExactly( - ResourceIdentifier.ofId(productType.getId()), - ResourceIdentifier.ofKey(productType.getKey()), - ResourceIdentifier.ofKey(productType.getKey())); - - assertThat(productDraftsWithKeysOnReferences).extracting(ProductDraft::getTaxCategory) - .asList() - .containsExactly( - ResourceIdentifier.ofKey(taxCategory.getKey()), - ResourceIdentifier.ofId(taxCategory.getId()), - ResourceIdentifier.ofKey(taxCategory.getKey())); - - assertThat(productDraftsWithKeysOnReferences).extracting(ProductDraft::getState) - .asList() - .containsExactly( - ResourceIdentifier.ofKey(state.getKey()), - ResourceIdentifier.ofKey(state.getKey()), - ResourceIdentifier.ofId(state.getId())); - - final String asset2CustomTypeId = asset2.getCustom().getType().getId(); - final String assetCustomTypeKey = customType.getKey(); - - assertThat(productDraftsWithKeysOnReferences).extracting(ProductDraft::getMasterVariant) - .flatExtracting(ProductVariantDraft::getAssets) - .extracting(AssetDraft::getCustom) - .extracting(CustomFieldsDraft::getType) - .extracting(type -> StringUtils.isEmpty(type.getId()) - ? type.getKey() : type.getId()) - .containsExactly(assetCustomTypeKey, asset2CustomTypeId, - assetCustomTypeKey, asset2CustomTypeId, assetCustomTypeKey, - asset2CustomTypeId); - } - - @Test - void mapToProductDrafts_WithNullProducts_ShouldSkipNullProducts() { - final String resourceKey = "key"; - final ProductType productType = getProductTypeMock(resourceKey); - final Reference productTypeReference = - Reference.ofResourceTypeIdAndIdAndObj(ProductType.referenceTypeId(), productType.getId(), productType); - final Reference nonExpandedProductTypeReference = ProductType.referenceOfId(productType.getId()); - - final TaxCategory taxCategory = getTaxCategoryMock(resourceKey); - final Reference taxCategoryReference = - Reference.ofResourceTypeIdAndIdAndObj(TaxCategory.referenceTypeId(), taxCategory.getId(), taxCategory); - final Reference nonExpandedTaxCategoryReference = TaxCategory.referenceOfId(taxCategory.getId()); - - final State state = getStateMock(resourceKey); - final Reference stateReference = - Reference.ofResourceTypeIdAndIdAndObj(State.referenceTypeId(), state.getId(), state); - final Reference nonExpandedStateReference = State.referenceOfId(state.getId()); - - final Channel channel = getChannelMock(resourceKey); - - final Reference channelReference = Reference - .ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel.getId(), channel); - final Price price = getPriceMockWithReferences(channelReference, null, null); - final ProductVariant productVariant = getProductVariantMock(singletonList(price)); - - final Category category = getCategoryMock(resourceKey); - final Reference categoryReference = - Reference.ofResourceTypeIdAndIdAndObj(Category.referenceTypeId(), category.getId(), category); - - final Product productWithNonExpandedProductType = - getProductMock(singleton(categoryReference), null, singletonList(productVariant)); - - when(productWithNonExpandedProductType.getProductType()) - .thenReturn(nonExpandedProductTypeReference); - when(productWithNonExpandedProductType.getTaxCategory()).thenReturn(taxCategoryReference); - when(productWithNonExpandedProductType.getState()).thenReturn(stateReference); - - final Product productWithNonExpandedTaxCategoryAndState = getProductMock(singletonList(productVariant)); - - when(productWithNonExpandedTaxCategoryAndState.getProductType()).thenReturn(productTypeReference); - when(productWithNonExpandedTaxCategoryAndState.getTaxCategory()).thenReturn(nonExpandedTaxCategoryReference); - when(productWithNonExpandedTaxCategoryAndState.getState()).thenReturn(nonExpandedStateReference); - - final List products = - asList(productWithNonExpandedProductType, productWithNonExpandedTaxCategoryAndState, null); - - - final List productDraftsWithKeysOnReferences = ProductReferenceResolutionUtils - .mapToProductDrafts(products); - - assertThat(productDraftsWithKeysOnReferences).extracting(ProductDraft::getProductType) - .asList() - .containsExactly( - ResourceIdentifier.ofId(productType.getId()), - ResourceIdentifier.ofKey(productType.getKey())); - - assertThat(productDraftsWithKeysOnReferences).flatExtracting(ProductDraft::getCategories) - .extracting(ResourceIdentifier::getKey) - .containsExactly(category.getKey()); - - assertThat(productDraftsWithKeysOnReferences).extracting(ProductDraft::getTaxCategory) - .asList() - .containsExactly( - ResourceIdentifier.ofKey(taxCategory.getKey()), - ResourceIdentifier.ofId(taxCategory.getId())); - - assertThat(productDraftsWithKeysOnReferences).extracting(ProductDraft::getState) - .asList() - .containsExactly( - ResourceIdentifier.ofKey(state.getKey()), - ResourceIdentifier.ofId(state.getId())); - - assertThat(productDraftsWithKeysOnReferences).extracting(ProductDraft::getMasterVariant) - .flatExtracting(ProductVariantDraft::getPrices) - .extracting(PriceDraft::getChannel) - .extracting(ResourceIdentifier::getKey) - .containsExactly(channel.getKey(), channel.getKey()); - } - - @Test - void buildProductQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final ProductQuery productQuery = ProductReferenceResolutionUtils.buildProductQuery(); - assertThat(productQuery.expansionPaths()) - .containsExactly(ExpansionPath.of("productType"), ExpansionPath.of("taxCategory"), - ExpansionPath.of("state"), ExpansionPath.of("masterData.staged.categories[*]"), - ExpansionPath.of("masterData.staged.masterVariant.prices[*].channel"), - ExpansionPath.of("masterData.staged.variants[*].prices[*].channel"), - ExpansionPath.of("masterData.staged.masterVariant.prices[*].customerGroup"), - ExpansionPath.of("masterData.staged.variants[*].prices[*].customerGroup"), - ExpansionPath.of("masterData.staged.masterVariant.prices[*].custom.type"), - ExpansionPath.of("masterData.staged.variants[*].prices[*].custom.type"), - ExpansionPath.of("masterData.staged.masterVariant.attributes[*].value"), - ExpansionPath.of("masterData.staged.variants[*].attributes[*].value"), - ExpansionPath.of("masterData.staged.masterVariant.attributes[*].value[*]"), - ExpansionPath.of("masterData.staged.variants[*].attributes[*].value[*]"), - ExpansionPath.of("masterData.staged.masterVariant.assets[*].custom.type"), - ExpansionPath.of("masterData.staged.variants[*].assets[*].custom.type")); - } - - @Test - void mapToCategoryReferencePair_WithNonExpandedReferences_ShouldReturnReferencesWithoutReplacedKeys() { - final String categoryId = UUID.randomUUID().toString(); - final Set> categoryReferences = singleton(Category.referenceOfId(categoryId)); - final CategoryOrderHints categoryOrderHints = getCategoryOrderHintsMock(categoryReferences); - - final Product product = getProductMock(categoryReferences, categoryOrderHints); - - final CategoryReferencePair categoryReferencePair = - ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); - - assertThat(categoryReferencePair).isNotNull(); - - final Set> categoryReferencesWithKeys = - categoryReferencePair.getCategoryResourceIdentifiers(); - final CategoryOrderHints categoryOrderHintsWithKeys = categoryReferencePair.getCategoryOrderHints(); - - assertThat(categoryReferencesWithKeys).extracting(ResourceIdentifier::getId) - .containsExactlyInAnyOrder(categoryId); - assertThat(categoryOrderHintsWithKeys).isEqualTo(product.getMasterData().getStaged().getCategoryOrderHints()); - } - - @Test - void mapToCategoryReferencePair_WithNonExpandedReferencesAndNoCategoryOrderHints_ShouldNotReplaceIds() { - final String categoryId = UUID.randomUUID().toString(); - final Set> categoryReferences = singleton(Category.referenceOfId(categoryId)); - final Product product = getProductMock(categoryReferences, null); - - final CategoryReferencePair categoryReferencePair = - ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); - - assertThat(categoryReferencePair).isNotNull(); - - final Set> categoryReferencesWithKeys = - categoryReferencePair.getCategoryResourceIdentifiers(); - final CategoryOrderHints categoryOrderHintsWithKeys = categoryReferencePair.getCategoryOrderHints(); - - assertThat(categoryReferencesWithKeys).extracting(ResourceIdentifier::getId) - .containsExactlyInAnyOrder(categoryId); - assertThat(categoryOrderHintsWithKeys).isEqualTo(product.getMasterData().getStaged().getCategoryOrderHints()); - } - - @Test - void mapToCategoryReferencePair_WithExpandedReferences_ShouldReturnReferencesWithReplacedKeys() { - final String categoryKey = "categoryKey"; - final Category category = getCategoryMock(categoryKey); - final Reference categoryReference = - Reference.ofResourceTypeIdAndIdAndObj(Category.referenceTypeId(), category.getId(), category); - - final Set> categoryReferences = singleton(categoryReference); - final CategoryOrderHints categoryOrderHints = getCategoryOrderHintsMock(categoryReferences); - - final Product product = getProductMock(categoryReferences, categoryOrderHints); - - final CategoryReferencePair categoryReferencePair = - ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); - - assertThat(categoryReferencePair).isNotNull(); - - final Set> categoryReferencesWithKeys = - categoryReferencePair.getCategoryResourceIdentifiers(); - final CategoryOrderHints categoryOrderHintsWithKeys = categoryReferencePair.getCategoryOrderHints(); - - assertThat(categoryReferencesWithKeys).containsExactlyInAnyOrderElementsOf( - singleton(ResourceIdentifier.ofKey(categoryKey))); - - assertThat(categoryOrderHintsWithKeys).isNotNull(); - assertThat(categoryOrderHintsWithKeys.getAsMap()).containsOnly(entry(categoryKey, - product.getMasterData().getStaged().getCategoryOrderHints().getAsMap().get(category.getId()))); - } - - @Test - void mapToCategoryReferencePair_WithExpandedReferencesAndNoCategoryOrderHints_ShouldReplaceIds() { - final String categoryKey = "categoryKey"; - final Category category = getCategoryMock(categoryKey); - final Reference categoryReference = - Reference.ofResourceTypeIdAndIdAndObj(Category.referenceTypeId(), category.getId(), category); - final Product product = getProductMock(singleton(categoryReference), null); - - final CategoryReferencePair categoryReferencePair = - ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); - - assertThat(categoryReferencePair).isNotNull(); - - final Set> categoryReferencesWithKeys = - categoryReferencePair.getCategoryResourceIdentifiers(); - final CategoryOrderHints categoryOrderHintsWithKeys = categoryReferencePair.getCategoryOrderHints(); - - assertThat(categoryReferencesWithKeys).extracting(ResourceIdentifier::getKey) - .containsExactlyInAnyOrder(categoryKey); - assertThat(categoryOrderHintsWithKeys).isNull(); - } - - @Test - void mapToCategoryReferencePair_WithExpandedReferencesAndSomeCategoryOrderHintsSet_ShouldReplaceIds() { - final String categoryKey1 = "categoryKey1"; - final String categoryKey2 = "categoryKey2"; - - final Category category1 = getCategoryMock(categoryKey1); - final Category category2 = getCategoryMock(categoryKey2); - - final Reference categoryReference1 = - Reference.ofResourceTypeIdAndIdAndObj(Category.referenceTypeId(), category1.getId(), category1); - final Reference categoryReference2 = - Reference.ofResourceTypeIdAndIdAndObj(Category.referenceTypeId(), category2.getId(), category2); - - final Set> categoryReferences = new HashSet<>(); - categoryReferences.add(categoryReference1); - categoryReferences.add(categoryReference2); - - final CategoryOrderHints categoryOrderHints = getCategoryOrderHintsMock(singleton(categoryReference1)); - - final Product product = getProductMock(categoryReferences, categoryOrderHints); - - final CategoryReferencePair categoryReferencePair = - ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); - - assertThat(categoryReferencePair).isNotNull(); - - final Set> categoryReferencesWithKeys = - categoryReferencePair.getCategoryResourceIdentifiers(); - final CategoryOrderHints categoryOrderHintsWithKeys = categoryReferencePair.getCategoryOrderHints(); - - - assertThat(categoryReferencesWithKeys).extracting(ResourceIdentifier::getKey) - .containsExactlyInAnyOrder(categoryKey1, categoryKey2); - - assertThat(categoryOrderHintsWithKeys).isNotNull(); - assertThat(categoryOrderHintsWithKeys.getAsMap()).containsOnly(entry(categoryKey1, - product.getMasterData().getStaged().getCategoryOrderHints().getAsMap().get(category1.getId()))); - } - - @Test - void mapToCategoryReferencePair_WithNoReferences_ShouldNotReplaceIds() { - final Product product = getProductMock(Collections.emptySet(), null); - - final CategoryReferencePair categoryReferencePair = - ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); - - assertThat(categoryReferencePair).isNotNull(); - - final Set> categoryReferencesWithKeys = - categoryReferencePair.getCategoryResourceIdentifiers(); - final CategoryOrderHints categoryOrderHintsWithKeys = categoryReferencePair.getCategoryOrderHints(); - assertThat(categoryReferencesWithKeys).isEmpty(); - assertThat(categoryOrderHintsWithKeys).isNull(); - } - - @Test - void mapToCategoryReferencePair_WithNullReferences_ShouldNotReplaceIds() { - final Product product = getProductMock(singleton(null), null); - - final CategoryReferencePair categoryReferencePair = - ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); - - assertThat(categoryReferencePair).isNotNull(); - - final Set> categoryReferencesWithKeys = - categoryReferencePair.getCategoryResourceIdentifiers(); - final CategoryOrderHints categoryOrderHintsWithKeys = categoryReferencePair.getCategoryOrderHints(); - assertThat(categoryReferencesWithKeys).isEmpty(); - assertThat(categoryOrderHintsWithKeys).isNull(); - } - - @Nonnull - private static Product getProductMock(@Nonnull final Set> references, - @Nullable final CategoryOrderHints categoryOrderHints, - @Nonnull final List productVariants) { - final ProductData productData = mock(ProductData.class); - mockProductDataCategories(references, categoryOrderHints, productData); - mockProductDataVariants(productVariants, productData); - return mockStagedProductData(productData); - } - - @Nonnull - private static Product getProductMock(@Nonnull final Set> references, - @Nullable final CategoryOrderHints categoryOrderHints) { - final ProductData productData = mock(ProductData.class); - mockProductDataCategories(references, categoryOrderHints, productData); - return mockStagedProductData(productData); - } - - @Nonnull - private static Product getProductMock(@Nonnull final List productVariants) { - final ProductData productData = mock(ProductData.class); - mockProductDataVariants(productVariants, productData); - return mockStagedProductData(productData); - } - - private static void mockProductDataCategories(@Nonnull final Set> references, - @Nullable final CategoryOrderHints categoryOrderHints, - @Nonnull final ProductData productData) { - when(productData.getCategories()).thenReturn(references); - when(productData.getCategoryOrderHints()).thenReturn(categoryOrderHints); - } - - private static void mockProductDataVariants(@Nonnull final List productVariants, - @Nonnull final ProductData productData) { - if (!productVariants.isEmpty()) { - final ProductVariant masterVariant = productVariants.get(0); - final List variants = productVariants.subList(1, productVariants.size()); - - when(productData.getMasterVariant()).thenReturn(masterVariant); - when(productData.getVariants()).thenReturn(variants); - when(productData.getAllVariants()).thenReturn(productVariants); - } - } - - @Nonnull - private static CategoryOrderHints getCategoryOrderHintsMock(@Nonnull final Set> references) { - final Map categoryOrderHintMap = new HashMap<>(); - references.forEach(categoryReference -> categoryOrderHintMap.put(categoryReference.getId(), "0.1")); - return CategoryOrderHints.of(categoryOrderHintMap); - } - - @Nonnull - private static Category getCategoryMock(@Nonnull final String key) { - final Category category = mock(Category.class); - when(category.getKey()).thenReturn(key); - when(category.getId()).thenReturn(UUID.randomUUID().toString()); - return category; - } - - @Nonnull - private static ProductType getProductTypeMock(@Nonnull final String key) { - final ProductType productType = mock(ProductType.class); - when(productType.getKey()).thenReturn(key); - when(productType.getId()).thenReturn(UUID.randomUUID().toString()); - return productType; - } - - @Nonnull - private static TaxCategory getTaxCategoryMock(@Nonnull final String key) { - final TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn(key); - when(taxCategory.getId()).thenReturn(UUID.randomUUID().toString()); - return taxCategory; - } - - @Nonnull - private static State getStateMock(@Nonnull final String key) { - final State state = mock(State.class); - when(state.getKey()).thenReturn(key); - when(state.getId()).thenReturn(UUID.randomUUID().toString()); - return state; - } - - @Nonnull - private static Product mockStagedProductData(@Nonnull final ProductData productData) { - final ProductCatalogData productCatalogData = mock(ProductCatalogData.class); - when(productCatalogData.getStaged()).thenReturn(productData); - - final Product product = mock(Product.class); - when(product.getMasterData()).thenReturn(productCatalogData); - return product; + @Test + void mapToProductDrafts_WithSomeExpandedReferences_ShouldReplaceReferencesWhereExpanded() { + final String resourceKey = "key"; + final ProductType productType = getProductTypeMock(resourceKey); + final Reference productTypeReference = + Reference.ofResourceTypeIdAndIdAndObj( + ProductType.referenceTypeId(), productType.getId(), productType); + final Reference nonExpandedProductTypeReference = + ProductType.referenceOfId(productType.getId()); + + final TaxCategory taxCategory = getTaxCategoryMock(resourceKey); + final Reference taxCategoryReference = + Reference.ofResourceTypeIdAndIdAndObj( + TaxCategory.referenceTypeId(), taxCategory.getId(), taxCategory); + final Reference nonExpandedTaxCategoryReference = + TaxCategory.referenceOfId(taxCategory.getId()); + + final State state = getStateMock(resourceKey); + final Reference stateReference = + Reference.ofResourceTypeIdAndIdAndObj(State.referenceTypeId(), state.getId(), state); + final Reference nonExpandedStateReference = State.referenceOfId(state.getId()); + + final Channel channel = getChannelMock(resourceKey); + + final Reference channelReference = + Reference.ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel.getId(), channel); + final Price price = getPriceMockWithReferences(channelReference, null, null); + + final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + final Asset asset1 = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType)); + final Asset asset2 = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), UUID.randomUUID().toString())); + + final ProductVariant productVariant = + getProductVariantMock(singletonList(price), asList(asset1, asset2)); + + final Product productWithNonExpandedProductType = getProductMock(singletonList(productVariant)); + + when(productWithNonExpandedProductType.getProductType()) + .thenReturn(nonExpandedProductTypeReference); + when(productWithNonExpandedProductType.getTaxCategory()).thenReturn(taxCategoryReference); + when(productWithNonExpandedProductType.getState()).thenReturn(stateReference); + + final Product productWithNonExpandedTaxCategory = getProductMock(singletonList(productVariant)); + + when(productWithNonExpandedTaxCategory.getProductType()).thenReturn(productTypeReference); + when(productWithNonExpandedTaxCategory.getTaxCategory()) + .thenReturn(nonExpandedTaxCategoryReference); + when(productWithNonExpandedTaxCategory.getState()).thenReturn(stateReference); + + final Product productWithNonExpandedSate = getProductMock(singletonList(productVariant)); + + when(productWithNonExpandedSate.getProductType()).thenReturn(productTypeReference); + when(productWithNonExpandedSate.getTaxCategory()).thenReturn(taxCategoryReference); + when(productWithNonExpandedSate.getState()).thenReturn(nonExpandedStateReference); + + final List products = + asList( + productWithNonExpandedProductType, + productWithNonExpandedTaxCategory, + productWithNonExpandedSate); + + final List productDraftsWithKeysOnReferences = + ProductReferenceResolutionUtils.mapToProductDrafts(products); + + assertThat(productDraftsWithKeysOnReferences) + .extracting(ProductDraft::getProductType) + .asList() + .containsExactly( + ResourceIdentifier.ofId(productType.getId()), + ResourceIdentifier.ofKey(productType.getKey()), + ResourceIdentifier.ofKey(productType.getKey())); + + assertThat(productDraftsWithKeysOnReferences) + .extracting(ProductDraft::getTaxCategory) + .asList() + .containsExactly( + ResourceIdentifier.ofKey(taxCategory.getKey()), + ResourceIdentifier.ofId(taxCategory.getId()), + ResourceIdentifier.ofKey(taxCategory.getKey())); + + assertThat(productDraftsWithKeysOnReferences) + .extracting(ProductDraft::getState) + .asList() + .containsExactly( + ResourceIdentifier.ofKey(state.getKey()), + ResourceIdentifier.ofKey(state.getKey()), + ResourceIdentifier.ofId(state.getId())); + + final String asset2CustomTypeId = asset2.getCustom().getType().getId(); + final String assetCustomTypeKey = customType.getKey(); + + assertThat(productDraftsWithKeysOnReferences) + .extracting(ProductDraft::getMasterVariant) + .flatExtracting(ProductVariantDraft::getAssets) + .extracting(AssetDraft::getCustom) + .extracting(CustomFieldsDraft::getType) + .extracting(type -> StringUtils.isEmpty(type.getId()) ? type.getKey() : type.getId()) + .containsExactly( + assetCustomTypeKey, + asset2CustomTypeId, + assetCustomTypeKey, + asset2CustomTypeId, + assetCustomTypeKey, + asset2CustomTypeId); + } + + @Test + void mapToProductDrafts_WithNullProducts_ShouldSkipNullProducts() { + final String resourceKey = "key"; + final ProductType productType = getProductTypeMock(resourceKey); + final Reference productTypeReference = + Reference.ofResourceTypeIdAndIdAndObj( + ProductType.referenceTypeId(), productType.getId(), productType); + final Reference nonExpandedProductTypeReference = + ProductType.referenceOfId(productType.getId()); + + final TaxCategory taxCategory = getTaxCategoryMock(resourceKey); + final Reference taxCategoryReference = + Reference.ofResourceTypeIdAndIdAndObj( + TaxCategory.referenceTypeId(), taxCategory.getId(), taxCategory); + final Reference nonExpandedTaxCategoryReference = + TaxCategory.referenceOfId(taxCategory.getId()); + + final State state = getStateMock(resourceKey); + final Reference stateReference = + Reference.ofResourceTypeIdAndIdAndObj(State.referenceTypeId(), state.getId(), state); + final Reference nonExpandedStateReference = State.referenceOfId(state.getId()); + + final Channel channel = getChannelMock(resourceKey); + + final Reference channelReference = + Reference.ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel.getId(), channel); + final Price price = getPriceMockWithReferences(channelReference, null, null); + final ProductVariant productVariant = getProductVariantMock(singletonList(price)); + + final Category category = getCategoryMock(resourceKey); + final Reference categoryReference = + Reference.ofResourceTypeIdAndIdAndObj( + Category.referenceTypeId(), category.getId(), category); + + final Product productWithNonExpandedProductType = + getProductMock(singleton(categoryReference), null, singletonList(productVariant)); + + when(productWithNonExpandedProductType.getProductType()) + .thenReturn(nonExpandedProductTypeReference); + when(productWithNonExpandedProductType.getTaxCategory()).thenReturn(taxCategoryReference); + when(productWithNonExpandedProductType.getState()).thenReturn(stateReference); + + final Product productWithNonExpandedTaxCategoryAndState = + getProductMock(singletonList(productVariant)); + + when(productWithNonExpandedTaxCategoryAndState.getProductType()) + .thenReturn(productTypeReference); + when(productWithNonExpandedTaxCategoryAndState.getTaxCategory()) + .thenReturn(nonExpandedTaxCategoryReference); + when(productWithNonExpandedTaxCategoryAndState.getState()) + .thenReturn(nonExpandedStateReference); + + final List products = + asList(productWithNonExpandedProductType, productWithNonExpandedTaxCategoryAndState, null); + + final List productDraftsWithKeysOnReferences = + ProductReferenceResolutionUtils.mapToProductDrafts(products); + + assertThat(productDraftsWithKeysOnReferences) + .extracting(ProductDraft::getProductType) + .asList() + .containsExactly( + ResourceIdentifier.ofId(productType.getId()), + ResourceIdentifier.ofKey(productType.getKey())); + + assertThat(productDraftsWithKeysOnReferences) + .flatExtracting(ProductDraft::getCategories) + .extracting(ResourceIdentifier::getKey) + .containsExactly(category.getKey()); + + assertThat(productDraftsWithKeysOnReferences) + .extracting(ProductDraft::getTaxCategory) + .asList() + .containsExactly( + ResourceIdentifier.ofKey(taxCategory.getKey()), + ResourceIdentifier.ofId(taxCategory.getId())); + + assertThat(productDraftsWithKeysOnReferences) + .extracting(ProductDraft::getState) + .asList() + .containsExactly( + ResourceIdentifier.ofKey(state.getKey()), ResourceIdentifier.ofId(state.getId())); + + assertThat(productDraftsWithKeysOnReferences) + .extracting(ProductDraft::getMasterVariant) + .flatExtracting(ProductVariantDraft::getPrices) + .extracting(PriceDraft::getChannel) + .extracting(ResourceIdentifier::getKey) + .containsExactly(channel.getKey(), channel.getKey()); + } + + @Test + void buildProductQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final ProductQuery productQuery = ProductReferenceResolutionUtils.buildProductQuery(); + assertThat(productQuery.expansionPaths()) + .containsExactly( + ExpansionPath.of("productType"), + ExpansionPath.of("taxCategory"), + ExpansionPath.of("state"), + ExpansionPath.of("masterData.staged.categories[*]"), + ExpansionPath.of("masterData.staged.masterVariant.prices[*].channel"), + ExpansionPath.of("masterData.staged.variants[*].prices[*].channel"), + ExpansionPath.of("masterData.staged.masterVariant.prices[*].customerGroup"), + ExpansionPath.of("masterData.staged.variants[*].prices[*].customerGroup"), + ExpansionPath.of("masterData.staged.masterVariant.prices[*].custom.type"), + ExpansionPath.of("masterData.staged.variants[*].prices[*].custom.type"), + ExpansionPath.of("masterData.staged.masterVariant.attributes[*].value"), + ExpansionPath.of("masterData.staged.variants[*].attributes[*].value"), + ExpansionPath.of("masterData.staged.masterVariant.attributes[*].value[*]"), + ExpansionPath.of("masterData.staged.variants[*].attributes[*].value[*]"), + ExpansionPath.of("masterData.staged.masterVariant.assets[*].custom.type"), + ExpansionPath.of("masterData.staged.variants[*].assets[*].custom.type")); + } + + @Test + void + mapToCategoryReferencePair_WithNonExpandedReferences_ShouldReturnReferencesWithoutReplacedKeys() { + final String categoryId = UUID.randomUUID().toString(); + final Set> categoryReferences = + singleton(Category.referenceOfId(categoryId)); + final CategoryOrderHints categoryOrderHints = getCategoryOrderHintsMock(categoryReferences); + + final Product product = getProductMock(categoryReferences, categoryOrderHints); + + final CategoryReferencePair categoryReferencePair = + ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); + + assertThat(categoryReferencePair).isNotNull(); + + final Set> categoryReferencesWithKeys = + categoryReferencePair.getCategoryResourceIdentifiers(); + final CategoryOrderHints categoryOrderHintsWithKeys = + categoryReferencePair.getCategoryOrderHints(); + + assertThat(categoryReferencesWithKeys) + .extracting(ResourceIdentifier::getId) + .containsExactlyInAnyOrder(categoryId); + assertThat(categoryOrderHintsWithKeys) + .isEqualTo(product.getMasterData().getStaged().getCategoryOrderHints()); + } + + @Test + void + mapToCategoryReferencePair_WithNonExpandedReferencesAndNoCategoryOrderHints_ShouldNotReplaceIds() { + final String categoryId = UUID.randomUUID().toString(); + final Set> categoryReferences = + singleton(Category.referenceOfId(categoryId)); + final Product product = getProductMock(categoryReferences, null); + + final CategoryReferencePair categoryReferencePair = + ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); + + assertThat(categoryReferencePair).isNotNull(); + + final Set> categoryReferencesWithKeys = + categoryReferencePair.getCategoryResourceIdentifiers(); + final CategoryOrderHints categoryOrderHintsWithKeys = + categoryReferencePair.getCategoryOrderHints(); + + assertThat(categoryReferencesWithKeys) + .extracting(ResourceIdentifier::getId) + .containsExactlyInAnyOrder(categoryId); + assertThat(categoryOrderHintsWithKeys) + .isEqualTo(product.getMasterData().getStaged().getCategoryOrderHints()); + } + + @Test + void mapToCategoryReferencePair_WithExpandedReferences_ShouldReturnReferencesWithReplacedKeys() { + final String categoryKey = "categoryKey"; + final Category category = getCategoryMock(categoryKey); + final Reference categoryReference = + Reference.ofResourceTypeIdAndIdAndObj( + Category.referenceTypeId(), category.getId(), category); + + final Set> categoryReferences = singleton(categoryReference); + final CategoryOrderHints categoryOrderHints = getCategoryOrderHintsMock(categoryReferences); + + final Product product = getProductMock(categoryReferences, categoryOrderHints); + + final CategoryReferencePair categoryReferencePair = + ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); + + assertThat(categoryReferencePair).isNotNull(); + + final Set> categoryReferencesWithKeys = + categoryReferencePair.getCategoryResourceIdentifiers(); + final CategoryOrderHints categoryOrderHintsWithKeys = + categoryReferencePair.getCategoryOrderHints(); + + assertThat(categoryReferencesWithKeys) + .containsExactlyInAnyOrderElementsOf(singleton(ResourceIdentifier.ofKey(categoryKey))); + + assertThat(categoryOrderHintsWithKeys).isNotNull(); + assertThat(categoryOrderHintsWithKeys.getAsMap()) + .containsOnly( + entry( + categoryKey, + product + .getMasterData() + .getStaged() + .getCategoryOrderHints() + .getAsMap() + .get(category.getId()))); + } + + @Test + void mapToCategoryReferencePair_WithExpandedReferencesAndNoCategoryOrderHints_ShouldReplaceIds() { + final String categoryKey = "categoryKey"; + final Category category = getCategoryMock(categoryKey); + final Reference categoryReference = + Reference.ofResourceTypeIdAndIdAndObj( + Category.referenceTypeId(), category.getId(), category); + final Product product = getProductMock(singleton(categoryReference), null); + + final CategoryReferencePair categoryReferencePair = + ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); + + assertThat(categoryReferencePair).isNotNull(); + + final Set> categoryReferencesWithKeys = + categoryReferencePair.getCategoryResourceIdentifiers(); + final CategoryOrderHints categoryOrderHintsWithKeys = + categoryReferencePair.getCategoryOrderHints(); + + assertThat(categoryReferencesWithKeys) + .extracting(ResourceIdentifier::getKey) + .containsExactlyInAnyOrder(categoryKey); + assertThat(categoryOrderHintsWithKeys).isNull(); + } + + @Test + void + mapToCategoryReferencePair_WithExpandedReferencesAndSomeCategoryOrderHintsSet_ShouldReplaceIds() { + final String categoryKey1 = "categoryKey1"; + final String categoryKey2 = "categoryKey2"; + + final Category category1 = getCategoryMock(categoryKey1); + final Category category2 = getCategoryMock(categoryKey2); + + final Reference categoryReference1 = + Reference.ofResourceTypeIdAndIdAndObj( + Category.referenceTypeId(), category1.getId(), category1); + final Reference categoryReference2 = + Reference.ofResourceTypeIdAndIdAndObj( + Category.referenceTypeId(), category2.getId(), category2); + + final Set> categoryReferences = new HashSet<>(); + categoryReferences.add(categoryReference1); + categoryReferences.add(categoryReference2); + + final CategoryOrderHints categoryOrderHints = + getCategoryOrderHintsMock(singleton(categoryReference1)); + + final Product product = getProductMock(categoryReferences, categoryOrderHints); + + final CategoryReferencePair categoryReferencePair = + ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); + + assertThat(categoryReferencePair).isNotNull(); + + final Set> categoryReferencesWithKeys = + categoryReferencePair.getCategoryResourceIdentifiers(); + final CategoryOrderHints categoryOrderHintsWithKeys = + categoryReferencePair.getCategoryOrderHints(); + + assertThat(categoryReferencesWithKeys) + .extracting(ResourceIdentifier::getKey) + .containsExactlyInAnyOrder(categoryKey1, categoryKey2); + + assertThat(categoryOrderHintsWithKeys).isNotNull(); + assertThat(categoryOrderHintsWithKeys.getAsMap()) + .containsOnly( + entry( + categoryKey1, + product + .getMasterData() + .getStaged() + .getCategoryOrderHints() + .getAsMap() + .get(category1.getId()))); + } + + @Test + void mapToCategoryReferencePair_WithNoReferences_ShouldNotReplaceIds() { + final Product product = getProductMock(Collections.emptySet(), null); + + final CategoryReferencePair categoryReferencePair = + ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); + + assertThat(categoryReferencePair).isNotNull(); + + final Set> categoryReferencesWithKeys = + categoryReferencePair.getCategoryResourceIdentifiers(); + final CategoryOrderHints categoryOrderHintsWithKeys = + categoryReferencePair.getCategoryOrderHints(); + assertThat(categoryReferencesWithKeys).isEmpty(); + assertThat(categoryOrderHintsWithKeys).isNull(); + } + + @Test + void mapToCategoryReferencePair_WithNullReferences_ShouldNotReplaceIds() { + final Product product = getProductMock(singleton(null), null); + + final CategoryReferencePair categoryReferencePair = + ProductReferenceResolutionUtils.mapToCategoryReferencePair(product); + + assertThat(categoryReferencePair).isNotNull(); + + final Set> categoryReferencesWithKeys = + categoryReferencePair.getCategoryResourceIdentifiers(); + final CategoryOrderHints categoryOrderHintsWithKeys = + categoryReferencePair.getCategoryOrderHints(); + assertThat(categoryReferencesWithKeys).isEmpty(); + assertThat(categoryOrderHintsWithKeys).isNull(); + } + + @Nonnull + private static Product getProductMock( + @Nonnull final Set> references, + @Nullable final CategoryOrderHints categoryOrderHints, + @Nonnull final List productVariants) { + final ProductData productData = mock(ProductData.class); + mockProductDataCategories(references, categoryOrderHints, productData); + mockProductDataVariants(productVariants, productData); + return mockStagedProductData(productData); + } + + @Nonnull + private static Product getProductMock( + @Nonnull final Set> references, + @Nullable final CategoryOrderHints categoryOrderHints) { + final ProductData productData = mock(ProductData.class); + mockProductDataCategories(references, categoryOrderHints, productData); + return mockStagedProductData(productData); + } + + @Nonnull + private static Product getProductMock(@Nonnull final List productVariants) { + final ProductData productData = mock(ProductData.class); + mockProductDataVariants(productVariants, productData); + return mockStagedProductData(productData); + } + + private static void mockProductDataCategories( + @Nonnull final Set> references, + @Nullable final CategoryOrderHints categoryOrderHints, + @Nonnull final ProductData productData) { + when(productData.getCategories()).thenReturn(references); + when(productData.getCategoryOrderHints()).thenReturn(categoryOrderHints); + } + + private static void mockProductDataVariants( + @Nonnull final List productVariants, @Nonnull final ProductData productData) { + if (!productVariants.isEmpty()) { + final ProductVariant masterVariant = productVariants.get(0); + final List variants = productVariants.subList(1, productVariants.size()); + + when(productData.getMasterVariant()).thenReturn(masterVariant); + when(productData.getVariants()).thenReturn(variants); + when(productData.getAllVariants()).thenReturn(productVariants); } + } + + @Nonnull + private static CategoryOrderHints getCategoryOrderHintsMock( + @Nonnull final Set> references) { + final Map categoryOrderHintMap = new HashMap<>(); + references.forEach( + categoryReference -> categoryOrderHintMap.put(categoryReference.getId(), "0.1")); + return CategoryOrderHints.of(categoryOrderHintMap); + } + + @Nonnull + private static Category getCategoryMock(@Nonnull final String key) { + final Category category = mock(Category.class); + when(category.getKey()).thenReturn(key); + when(category.getId()).thenReturn(UUID.randomUUID().toString()); + return category; + } + + @Nonnull + private static ProductType getProductTypeMock(@Nonnull final String key) { + final ProductType productType = mock(ProductType.class); + when(productType.getKey()).thenReturn(key); + when(productType.getId()).thenReturn(UUID.randomUUID().toString()); + return productType; + } + + @Nonnull + private static TaxCategory getTaxCategoryMock(@Nonnull final String key) { + final TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn(key); + when(taxCategory.getId()).thenReturn(UUID.randomUUID().toString()); + return taxCategory; + } + + @Nonnull + private static State getStateMock(@Nonnull final String key) { + final State state = mock(State.class); + when(state.getKey()).thenReturn(key); + when(state.getId()).thenReturn(UUID.randomUUID().toString()); + return state; + } + + @Nonnull + private static Product mockStagedProductData(@Nonnull final ProductData productData) { + final ProductCatalogData productCatalogData = mock(ProductCatalogData.class); + when(productCatalogData.getStaged()).thenReturn(productData); + + final Product product = mock(Product.class); + when(product.getMasterData()).thenReturn(productCatalogData); + return product; + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/ProductSyncUtilsTest.java b/src/test/java/com/commercetools/sync/products/utils/ProductSyncUtilsTest.java index d73d7b5044..d425e8ba54 100644 --- a/src/test/java/com/commercetools/sync/products/utils/ProductSyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/ProductSyncUtilsTest.java @@ -1,5 +1,20 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.SIMPLE_PRODUCT_WITH_MASTER_VARIANT_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.SIMPLE_PRODUCT_WITH_MULTIPLE_VARIANTS_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.utils.MoneyImpl.createCurrencyByCode; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.AttributeMetaData; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -18,8 +33,8 @@ import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.ProductDraftBuilder; -import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.products.ProductVariant; +import io.sphere.sdk.products.ProductVariantDraft; import io.sphere.sdk.products.ProductVariantDraftBuilder; import io.sphere.sdk.products.attributes.AttributeConstraint; import io.sphere.sdk.products.attributes.AttributeDefinitionBuilder; @@ -50,101 +65,93 @@ import io.sphere.sdk.search.SearchKeyword; import io.sphere.sdk.search.SearchKeywords; import io.sphere.sdk.utils.MoneyImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.SIMPLE_PRODUCT_WITH_MULTIPLE_VARIANTS_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.SIMPLE_PRODUCT_WITH_MASTER_VARIANT_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftBuilder; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.utils.MoneyImpl.createCurrencyByCode; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductSyncUtilsTest { - private Product oldProduct; - private ProductSyncOptions productSyncOptions; - - /** - * Initializes an instance of {@link ProductSyncOptions} and {@link Product}. - */ - @BeforeEach - void setup() { - productSyncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - - oldProduct = readObjectFromResource(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, Product.class); - } - - @Test - void buildActions_FromDraftsWithDifferentNameValues_ShouldBuildUpdateActions() { - final LocalizedString newName = LocalizedString.of(Locale.ENGLISH, "newName"); - final ProductDraft newProductDraft = - createProductDraftBuilder(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, + private Product oldProduct; + private ProductSyncOptions productSyncOptions; + + /** Initializes an instance of {@link ProductSyncOptions} and {@link Product}. */ + @BeforeEach + void setup() { + productSyncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + oldProduct = readObjectFromResource(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, Product.class); + } + + @Test + void buildActions_FromDraftsWithDifferentNameValues_ShouldBuildUpdateActions() { + final LocalizedString newName = LocalizedString.of(Locale.ENGLISH, "newName"); + final ProductDraft newProductDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, ProductType.referenceOfId("anyProductType")) - .name(newName) - .build(); - - final List> updateActions = - ProductSyncUtils.buildActions(oldProduct, newProductDraft, productSyncOptions, new HashMap<>()); - - - assertThat(updateActions).containsExactly(ChangeName.of(newName, true), Publish.of()); - } - - @Test - void buildActions_FromDraftsWithMultipleDifferentValues_ShouldBuildUpdateActions() { - // preparation - final ProductDraftBuilder draftBuilder = createProductDraftBuilder( - PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH, ProductType.referenceOfId("anyProductType")); - - final AssetDraft assetDraft = AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), - ofEnglish("assetName")).build().withKey("anyKey"); - - final ProductVariantDraft masterVariantDraftWithAssets = - ProductVariantDraftBuilder.of(draftBuilder.getMasterVariant()) - .assets(singletonList(assetDraft)) - .build(); - - final ProductDraft newProductDraft = ProductDraftBuilder.of(draftBuilder.build()) - .masterVariant(masterVariantDraftWithAssets) - .build(); - - final Category expectedCategoryToAdd = mock(Category.class); - when(expectedCategoryToAdd.getId()).thenReturn("5dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"); - when(expectedCategoryToAdd.toReference()) - .thenReturn(Category.referenceOfId("5dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); - - final Category expectedCategoryToRemove = mock(Category.class); - when(expectedCategoryToRemove.getId()).thenReturn("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"); - when(expectedCategoryToRemove.toReference()) - .thenReturn(Category.referenceOfId("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); - - // test - final List> updateActions = - ProductSyncUtils.buildActions(oldProduct, newProductDraft, productSyncOptions, new HashMap<>()); - - // asserts - assertThat(updateActions).containsExactlyInAnyOrder( + .name(newName) + .build(); + + final List> updateActions = + ProductSyncUtils.buildActions( + oldProduct, newProductDraft, productSyncOptions, new HashMap<>()); + + assertThat(updateActions).containsExactly(ChangeName.of(newName, true), Publish.of()); + } + + @Test + void buildActions_FromDraftsWithMultipleDifferentValues_ShouldBuildUpdateActions() { + // preparation + final ProductDraftBuilder draftBuilder = + createProductDraftBuilder( + PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH, + ProductType.referenceOfId("anyProductType")); + + final AssetDraft assetDraft = + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("assetName")) + .build() + .withKey("anyKey"); + + final ProductVariantDraft masterVariantDraftWithAssets = + ProductVariantDraftBuilder.of(draftBuilder.getMasterVariant()) + .assets(singletonList(assetDraft)) + .build(); + + final ProductDraft newProductDraft = + ProductDraftBuilder.of(draftBuilder.build()) + .masterVariant(masterVariantDraftWithAssets) + .build(); + + final Category expectedCategoryToAdd = mock(Category.class); + when(expectedCategoryToAdd.getId()).thenReturn("5dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"); + when(expectedCategoryToAdd.toReference()) + .thenReturn(Category.referenceOfId("5dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); + + final Category expectedCategoryToRemove = mock(Category.class); + when(expectedCategoryToRemove.getId()).thenReturn("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"); + when(expectedCategoryToRemove.toReference()) + .thenReturn(Category.referenceOfId("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); + + // test + final List> updateActions = + ProductSyncUtils.buildActions( + oldProduct, newProductDraft, productSyncOptions, new HashMap<>()); + + // asserts + assertThat(updateActions) + .containsExactlyInAnyOrder( ChangeName.of(ofEnglish("new name"), true), SetDescription.of(ofEnglish("new description"), true), ChangeSlug.of(ofEnglish("rehruecken-o-k1"), true), - SetSearchKeywords.of(SearchKeywords.of(Locale.ENGLISH, asList(SearchKeyword.of("key1"), - SearchKeyword.of("key2"))), true), + SetSearchKeywords.of( + SearchKeywords.of( + Locale.ENGLISH, asList(SearchKeyword.of("key1"), SearchKeyword.of("key2"))), + true), SetMetaTitle.of(ofEnglish("new title")), SetMetaDescription.of(ofEnglish("new Meta description")), SetMetaKeywords.of(ofEnglish("key1,key2")), @@ -153,200 +160,275 @@ void buildActions_FromDraftsWithMultipleDifferentValues_ShouldBuildUpdateActions SetCategoryOrderHint.of("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.83", true), SetCategoryOrderHint.of("5dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.93", true), RemoveFromCategory.of(expectedCategoryToRemove, true), - RemoveImage.ofVariantId(1, - Image.of("https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" - + "old-image.png", ImageDimensions.of(0, 0), null), true), - AddExternalImage.ofVariantId(1, - Image.of("https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" - + "new-image.png", ImageDimensions.of(0, 0), null), true), - AddExternalImage.ofVariantId(1, - Image.of("https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" - + "new-image-2.png", ImageDimensions.of(0, 0), null), true), - RemovePrice.of(Price.of(MoneyImpl.ofCents(108, EUR)) - .withId("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"), true), - RemovePrice.of(Price.of(MoneyImpl.ofCents(10, createCurrencyByCode("EGP"))) - .withId("4dfc8bea-84f2-45bc-b3c2-r9e7wv99vfb"), true), - AddPrice.ofVariantId(1, + RemoveImage.ofVariantId( + 1, + Image.of( + "https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" + + "old-image.png", + ImageDimensions.of(0, 0), + null), + true), + AddExternalImage.ofVariantId( + 1, + Image.of( + "https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" + + "new-image.png", + ImageDimensions.of(0, 0), + null), + true), + AddExternalImage.ofVariantId( + 1, + Image.of( + "https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" + + "new-image-2.png", + ImageDimensions.of(0, 0), + null), + true), + RemovePrice.of( + Price.of(MoneyImpl.ofCents(108, EUR)) + .withId("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"), + true), + RemovePrice.of( + Price.of(MoneyImpl.ofCents(10, createCurrencyByCode("EGP"))) + .withId("4dfc8bea-84f2-45bc-b3c2-r9e7wv99vfb"), + true), + AddPrice.ofVariantId( + 1, PriceDraft.of(MoneyImpl.ofCents(118, EUR)) - .withChannel(Channel.referenceOfId("channel-key_1")), true), - AddPrice.ofVariantId(1, + .withChannel(Channel.referenceOfId("channel-key_1")), + true), + AddPrice.ofVariantId( + 1, PriceDraft.of(MoneyImpl.ofCents(100, createCurrencyByCode("EGP"))) - .withChannel(Channel.referenceOfId("channel-key_1")), true), + .withChannel(Channel.referenceOfId("channel-key_1")), + true), AddAsset.ofVariantId(1, assetDraft).withPosition(0).withStaged(true), SetSku.of(1, "3065831", true), - Publish.of() - ); - } - - @Test - void buildActions_FromDraftsWithMultipleDifferentValues_ShouldBuildUpdateActionsInCorrectOrder() { - final ProductDraftBuilder draftBuilder = createProductDraftBuilder( - PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH, ProductType.referenceOfId("anyProductType")); - - final ProductDraft newProductDraft = ProductDraftBuilder.of(draftBuilder.build()) - .build(); - - final List> updateActions = - ProductSyncUtils.buildActions(oldProduct, newProductDraft, productSyncOptions, new HashMap<>()); - - //Assert that "removeImage" actions are always before "addExternalImage" - assertThat(updateActions).containsSubsequence( - RemoveImage.ofVariantId(1, - Image.of("https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" - + "old-image.png", ImageDimensions.of(0, 0), null), true), - AddExternalImage.ofVariantId(1, - Image.of("https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" - + "new-image.png", ImageDimensions.of(0, 0), null), true), - AddExternalImage.ofVariantId(1, - Image.of("https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" - + "new-image-2.png", ImageDimensions.of(0, 0), null), true) - ); - - //Assert that "removePrice" actions are always before "addPrice" - assertThat(updateActions).containsSubsequence( - RemovePrice.of(Price.of(MoneyImpl.ofCents(108, EUR)).withId("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"), + Publish.of()); + } + + @Test + void buildActions_FromDraftsWithMultipleDifferentValues_ShouldBuildUpdateActionsInCorrectOrder() { + final ProductDraftBuilder draftBuilder = + createProductDraftBuilder( + PRODUCT_KEY_1_CHANGED_WITH_PRICES_RESOURCE_PATH, + ProductType.referenceOfId("anyProductType")); + + final ProductDraft newProductDraft = ProductDraftBuilder.of(draftBuilder.build()).build(); + + final List> updateActions = + ProductSyncUtils.buildActions( + oldProduct, newProductDraft, productSyncOptions, new HashMap<>()); + + // Assert that "removeImage" actions are always before "addExternalImage" + assertThat(updateActions) + .containsSubsequence( + RemoveImage.ofVariantId( + 1, + Image.of( + "https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" + + "old-image.png", + ImageDimensions.of(0, 0), + null), + true), + AddExternalImage.ofVariantId( + 1, + Image.of( + "https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" + + "new-image.png", + ImageDimensions.of(0, 0), + null), true), - AddPrice.ofVariantId(1, + AddExternalImage.ofVariantId( + 1, + Image.of( + "https://53346cfbf3c7e017ed3d-6de74c3efa80f1c837c6a988b57abe66.ssl.cf3.rackcdn.com/" + + "new-image-2.png", + ImageDimensions.of(0, 0), + null), + true)); + + // Assert that "removePrice" actions are always before "addPrice" + assertThat(updateActions) + .containsSubsequence( + RemovePrice.of( + Price.of(MoneyImpl.ofCents(108, EUR)) + .withId("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"), + true), + AddPrice.ofVariantId( + 1, PriceDraft.of(MoneyImpl.ofCents(100, createCurrencyByCode("EGP"))) - .withChannel(Channel.referenceOfId("channel-key_1")), true) - ); - - assertThat(updateActions).containsSubsequence( - RemovePrice.of(Price.of(MoneyImpl.ofCents(108, EUR)) - .withId("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"), true), - AddPrice.ofVariantId(1, + .withChannel(Channel.referenceOfId("channel-key_1")), + true)); + + assertThat(updateActions) + .containsSubsequence( + RemovePrice.of( + Price.of(MoneyImpl.ofCents(108, EUR)) + .withId("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f"), + true), + AddPrice.ofVariantId( + 1, PriceDraft.of(MoneyImpl.ofCents(118, EUR)) - .withChannel(Channel.referenceOfId("channel-key_1")), true) - ); - - assertThat(updateActions).containsSubsequence( - RemovePrice.of(Price.of(MoneyImpl.ofCents(10, createCurrencyByCode("EGP"))) - .withId("4dfc8bea-84f2-45bc-b3c2-r9e7wv99vfb"), true), - AddPrice.ofVariantId(1, + .withChannel(Channel.referenceOfId("channel-key_1")), + true)); + + assertThat(updateActions) + .containsSubsequence( + RemovePrice.of( + Price.of(MoneyImpl.ofCents(10, createCurrencyByCode("EGP"))) + .withId("4dfc8bea-84f2-45bc-b3c2-r9e7wv99vfb"), + true), + AddPrice.ofVariantId( + 1, PriceDraft.of(MoneyImpl.ofCents(100, createCurrencyByCode("EGP"))) - .withChannel(Channel.referenceOfId("channel-key_1")), true) - ); - - assertThat(updateActions).containsSubsequence( - RemovePrice.of(Price.of(MoneyImpl.ofCents(10, createCurrencyByCode("EGP"))) - .withId("4dfc8bea-84f2-45bc-b3c2-r9e7wv99vfb"), true), - AddPrice.ofVariantId(1, + .withChannel(Channel.referenceOfId("channel-key_1")), + true)); + + assertThat(updateActions) + .containsSubsequence( + RemovePrice.of( + Price.of(MoneyImpl.ofCents(10, createCurrencyByCode("EGP"))) + .withId("4dfc8bea-84f2-45bc-b3c2-r9e7wv99vfb"), + true), + AddPrice.ofVariantId( + 1, PriceDraft.of(MoneyImpl.ofCents(118, EUR)) - .withChannel(Channel.referenceOfId("channel-key_1")), true) - ); - } - - @Test - void buildActions_FromDraftsWithSameNameValues_ShouldNotBuildUpdateActions() { - final ProductDraft newProductDraft = - createProductDraftBuilder(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, - ProductType.referenceOfId("anyProductType")).build(); - - final List> updateActions = - ProductSyncUtils.buildActions(oldProduct, newProductDraft, productSyncOptions, new HashMap<>()); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildActions_FromDraftsWithSameForAllAttribute_ShouldBuildUpdateActions() { - - final ProductVariant masterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final AttributeDraft brandNameAttribute = AttributeDraft.of("brandName", "sameForAllBrand"); - final ProductVariantDraft newMasterVariant = ProductVariantDraftBuilder.of(masterVariant) - .plusAttribute(brandNameAttribute) - .build(); - final ProductVariantDraft variant = ProductVariantDraftBuilder.of() - .key("v2") - .sku("3065834") - .plusAttribute(brandNameAttribute) - .build(); - - final ProductDraft newProductDraft = - createProductDraftBuilder(PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, - ProductType.referenceOfId("anyProductType")) - .masterVariant(newMasterVariant) - .plusVariants(variant) - .build(); - - final Map attributesMetaData = new HashMap<>(); - final AttributeMetaData brandName = AttributeMetaData.of( - AttributeDefinitionBuilder.of("brandName", null, null) - .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) - .build()); - attributesMetaData.put("brandName", brandName); - - final List> updateActions = - ProductSyncUtils.buildActions(oldProduct, newProductDraft, productSyncOptions, attributesMetaData); - - // check that we only have one generated action for all the variants and no duplicates - // and is ordered correctly before addVariant action - assertThat(updateActions.size()).isEqualTo(3); - assertThat(updateActions).containsOnlyOnce(SetAttributeInAllVariants.of( - AttributeDraft.of("brandName", "sameForAllBrand"), true)); - assertThat(updateActions.get(0)).isEqualTo(SetAttributeInAllVariants.of( - AttributeDraft.of("brandName", "sameForAllBrand"), true)); - assertThat(updateActions.get(1)).isEqualTo(AddVariant.of(Arrays.asList( - AttributeDraft.of("brandName", "sameForAllBrand")), - null, "3065834", true).withKey("v2")); - assertThat(updateActions.get(2)).isEqualTo(Publish.of()); - } - - @Test - void buildActions_FromDraftsWithDifferentAttributes_ShouldBuildUpdateActions() { - // Reloading the oldProduct object with a specific file for this test - oldProduct = readObjectFromResource(SIMPLE_PRODUCT_WITH_MULTIPLE_VARIANTS_RESOURCE_PATH, Product.class); - final AttributeDraft brandNameAttribute = AttributeDraft.of("brandName", "myBrand"); - final AttributeDraft orderLimitAttribute = AttributeDraft.of("orderLimit", "5"); - final AttributeDraft priceInfoAttribute = AttributeDraft.of("priceInfo", "80,20/kg"); - final ProductVariantDraft variant = ProductVariantDraftBuilder.of() - .key("v3") - .sku("1065834") - .plusAttribute(orderLimitAttribute) - .plusAttribute(priceInfoAttribute) - .plusAttribute(brandNameAttribute) - .build(); - - final ProductDraft newProductDraft = - createProductDraftBuilder(SIMPLE_PRODUCT_WITH_MASTER_VARIANT_RESOURCE_PATH, - ProductType.referenceOfId("anyProductType")) - .plusVariants(variant) - .build(); - - final Map attributesMetaData = new HashMap<>(); - final AttributeMetaData brandName = AttributeMetaData.of( - AttributeDefinitionBuilder.of("brandName", null, null) - .build()); - final AttributeMetaData orderLimit = AttributeMetaData.of( - AttributeDefinitionBuilder.of("orderLimit", null, null) - .build()); - final AttributeMetaData priceInfo = AttributeMetaData.of( - AttributeDefinitionBuilder.of("priceInfo", null, null) - .build()); - final AttributeMetaData size = AttributeMetaData.of( - AttributeDefinitionBuilder.of("size", null, null) - .build()); - attributesMetaData.put("brandName", brandName); - attributesMetaData.put("orderLimit", orderLimit); - attributesMetaData.put("priceInfo", priceInfo); - attributesMetaData.put("size", size); - - final List> updateActions = - ProductSyncUtils.buildActions(oldProduct, newProductDraft, productSyncOptions, attributesMetaData); - - // check the generated attribute update actions - assertThat(updateActions.size()).isEqualTo(9); - assertThat(updateActions).containsSequence( - RemoveVariant.ofVariantId(5, true), - AddVariant.of(Arrays.asList(AttributeDraft.of("priceInfo", "64,90/kg"), - AttributeDraft.of("size", "ca. 1 x 1000 g")), Collections.emptyList(), - "1065833", true).withKey("v2").withImages(Collections.emptyList()), - ChangeMasterVariant.ofSku("1065833", true), - RemoveVariant.ofVariantId(1), - SetAttribute.of(2, AttributeDraft.of("size", null), true), - SetAttribute.of(2, AttributeDraft.of("orderLimit", "5"), true), - SetAttribute.of(2, AttributeDraft.of("priceInfo", "80,20/kg"), true), - SetAttribute.of(2, AttributeDraft.of("brandName", "myBrand"), true), - Publish.of()); - } + .withChannel(Channel.referenceOfId("channel-key_1")), + true)); + } + + @Test + void buildActions_FromDraftsWithSameNameValues_ShouldNotBuildUpdateActions() { + final ProductDraft newProductDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, + ProductType.referenceOfId("anyProductType")) + .build(); + + final List> updateActions = + ProductSyncUtils.buildActions( + oldProduct, newProductDraft, productSyncOptions, new HashMap<>()); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildActions_FromDraftsWithSameForAllAttribute_ShouldBuildUpdateActions() { + + final ProductVariant masterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); + final AttributeDraft brandNameAttribute = AttributeDraft.of("brandName", "sameForAllBrand"); + final ProductVariantDraft newMasterVariant = + ProductVariantDraftBuilder.of(masterVariant).plusAttribute(brandNameAttribute).build(); + final ProductVariantDraft variant = + ProductVariantDraftBuilder.of() + .key("v2") + .sku("3065834") + .plusAttribute(brandNameAttribute) + .build(); + + final ProductDraft newProductDraft = + createProductDraftBuilder( + PRODUCT_KEY_1_WITH_PRICES_RESOURCE_PATH, + ProductType.referenceOfId("anyProductType")) + .masterVariant(newMasterVariant) + .plusVariants(variant) + .build(); + + final Map attributesMetaData = new HashMap<>(); + final AttributeMetaData brandName = + AttributeMetaData.of( + AttributeDefinitionBuilder.of("brandName", null, null) + .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) + .build()); + attributesMetaData.put("brandName", brandName); + + final List> updateActions = + ProductSyncUtils.buildActions( + oldProduct, newProductDraft, productSyncOptions, attributesMetaData); + + // check that we only have one generated action for all the variants and no duplicates + // and is ordered correctly before addVariant action + assertThat(updateActions.size()).isEqualTo(3); + assertThat(updateActions) + .containsOnlyOnce( + SetAttributeInAllVariants.of(AttributeDraft.of("brandName", "sameForAllBrand"), true)); + assertThat(updateActions.get(0)) + .isEqualTo( + SetAttributeInAllVariants.of(AttributeDraft.of("brandName", "sameForAllBrand"), true)); + assertThat(updateActions.get(1)) + .isEqualTo( + AddVariant.of( + Arrays.asList(AttributeDraft.of("brandName", "sameForAllBrand")), + null, + "3065834", + true) + .withKey("v2")); + assertThat(updateActions.get(2)).isEqualTo(Publish.of()); + } + + @Test + void buildActions_FromDraftsWithDifferentAttributes_ShouldBuildUpdateActions() { + // Reloading the oldProduct object with a specific file for this test + oldProduct = + readObjectFromResource(SIMPLE_PRODUCT_WITH_MULTIPLE_VARIANTS_RESOURCE_PATH, Product.class); + final AttributeDraft brandNameAttribute = AttributeDraft.of("brandName", "myBrand"); + final AttributeDraft orderLimitAttribute = AttributeDraft.of("orderLimit", "5"); + final AttributeDraft priceInfoAttribute = AttributeDraft.of("priceInfo", "80,20/kg"); + final ProductVariantDraft variant = + ProductVariantDraftBuilder.of() + .key("v3") + .sku("1065834") + .plusAttribute(orderLimitAttribute) + .plusAttribute(priceInfoAttribute) + .plusAttribute(brandNameAttribute) + .build(); + + final ProductDraft newProductDraft = + createProductDraftBuilder( + SIMPLE_PRODUCT_WITH_MASTER_VARIANT_RESOURCE_PATH, + ProductType.referenceOfId("anyProductType")) + .plusVariants(variant) + .build(); + + final Map attributesMetaData = new HashMap<>(); + final AttributeMetaData brandName = + AttributeMetaData.of(AttributeDefinitionBuilder.of("brandName", null, null).build()); + final AttributeMetaData orderLimit = + AttributeMetaData.of(AttributeDefinitionBuilder.of("orderLimit", null, null).build()); + final AttributeMetaData priceInfo = + AttributeMetaData.of(AttributeDefinitionBuilder.of("priceInfo", null, null).build()); + final AttributeMetaData size = + AttributeMetaData.of(AttributeDefinitionBuilder.of("size", null, null).build()); + attributesMetaData.put("brandName", brandName); + attributesMetaData.put("orderLimit", orderLimit); + attributesMetaData.put("priceInfo", priceInfo); + attributesMetaData.put("size", size); + + final List> updateActions = + ProductSyncUtils.buildActions( + oldProduct, newProductDraft, productSyncOptions, attributesMetaData); + + // check the generated attribute update actions + assertThat(updateActions.size()).isEqualTo(9); + assertThat(updateActions) + .containsSequence( + RemoveVariant.ofVariantId(5, true), + AddVariant.of( + Arrays.asList( + AttributeDraft.of("priceInfo", "64,90/kg"), + AttributeDraft.of("size", "ca. 1 x 1000 g")), + Collections.emptyList(), + "1065833", + true) + .withKey("v2") + .withImages(Collections.emptyList()), + ChangeMasterVariant.ofSku("1065833", true), + RemoveVariant.ofVariantId(1), + SetAttribute.of(2, AttributeDraft.of("size", null), true), + SetAttribute.of(2, AttributeDraft.of("orderLimit", "5"), true), + SetAttribute.of(2, AttributeDraft.of("priceInfo", "80,20/kg"), true), + SetAttribute.of(2, AttributeDraft.of("brandName", "myBrand"), true), + Publish.of()); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/ProductUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/products/utils/ProductUpdateActionUtilsTest.java index 2d30841f2e..79fe6f4321 100644 --- a/src/test/java/com/commercetools/sync/products/utils/ProductUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/ProductUpdateActionUtilsTest.java @@ -1,5 +1,23 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftFromJson; +import static com.commercetools.sync.products.ProductSyncMockUtils.createProductFromJson; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.BLANK_NEW_MASTER_VARIANT_KEY; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.BLANK_NEW_MASTER_VARIANT_SKU; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.BLANK_OLD_MASTER_VARIANT_KEY; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildAddVariantUpdateActionFromDraft; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildChangeMasterVariantUpdateAction; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildVariantsUpdateActions; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.getAllVariants; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.products.AttributeMetaData; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -27,438 +45,464 @@ import io.sphere.sdk.products.commands.updateactions.RemoveImage; import io.sphere.sdk.products.commands.updateactions.RemoveVariant; import io.sphere.sdk.products.commands.updateactions.SetAttribute; -import io.sphere.sdk.products.commands.updateactions.SetSku; import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; +import io.sphere.sdk.products.commands.updateactions.SetSku; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.IntStream; - -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductDraftFromJson; -import static com.commercetools.sync.products.ProductSyncMockUtils.createProductFromJson; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.BLANK_NEW_MASTER_VARIANT_KEY; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.BLANK_NEW_MASTER_VARIANT_SKU; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.BLANK_OLD_MASTER_VARIANT_KEY; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildAddVariantUpdateActionFromDraft; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildChangeMasterVariantUpdateAction; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildVariantsUpdateActions; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.getAllVariants; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - +import org.junit.jupiter.api.Test; class ProductUpdateActionUtilsTest { - private static final String RES_ROOT = "com/commercetools/sync/products/utils/productVariantUpdateActionUtils/"; - private static final String OLD_PROD_WITH_VARIANTS = RES_ROOT + "productOld.json"; - private static final String OLD_PROD_WITHOUT_MV_KEY_SKU = RES_ROOT + "productOld_noMasterVariantKeySku.json"; - - // this product's variants don't contain old master variant - private static final String NEW_PROD_DRAFT_WITH_VARIANTS_REMOVE_MASTER = - RES_ROOT + "productDraftNew_changeRemoveMasterVariant.json"; - - // this product's variants contain only attribute update - private static final String NEW_PROD_DRAFT_WITH_MATCHING_VARIANTS_WITH_UPDATED_ATTR_VALUES = - RES_ROOT + "productDraftNew_matchingVariants.json"; - - // this product's variants contain old master variant, but not as master any more - private static final String NEW_PROD_DRAFT_WITH_VARIANTS_MOVE_MASTER = - RES_ROOT + "productDraftNew_moveMasterVariant.json"; - - private static final String NEW_PROD_DRAFT_WITHOUT_MV = RES_ROOT + "productDraftNew_noMasterVariant.json"; - - private static final String NEW_PROD_DRAFT_WITHOUT_MV_KEY = RES_ROOT + "productDraftNew_noMasterVariantKey.json"; - - private static final String NEW_PROD_DRAFT_WITHOUT_MV_SKU = RES_ROOT + "productDraftNew_noMasterVariantSku.json"; - - @Test - void buildVariantsUpdateActions_updatesVariants() { - // preparation - final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); - final ProductDraft productDraftNew = createProductDraftFromJson(NEW_PROD_DRAFT_WITH_VARIANTS_REMOVE_MASTER); - - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .syncFilter(SyncFilter.of()) - .build(); - - final Map attributesMetaData = new HashMap<>(); - final AttributeMetaData priceInfo = AttributeMetaData.of( - AttributeDefinitionBuilder.of("priceInfo", null, null) - .build()); - attributesMetaData.put("priceInfo", priceInfo); - - final List> updateActions = - buildVariantsUpdateActions(productOld, productDraftNew, productSyncOptions, attributesMetaData); - - // check remove variants are the first in the list, but not the master variant - assertThat(updateActions.subList(0, 2)) - .containsExactlyInAnyOrder(RemoveVariant.ofVariantId(2, true), RemoveVariant.ofVariantId(3, true)); - - // check add actions - - final ProductVariantDraft draftMaster = productDraftNew.getMasterVariant(); - final ProductVariantDraft draft5 = productDraftNew.getVariants().get(1); - final ProductVariantDraft draft6 = productDraftNew.getVariants().get(2); - final ProductVariantDraft draft7 = productDraftNew.getVariants().get(3); - - assertThat(updateActions).contains( - AddVariant.of(draftMaster.getAttributes(), draftMaster.getPrices(), draftMaster.getSku(), true) - .withKey(draftMaster.getKey()) - .withImages(draftMaster.getImages()), + private static final String RES_ROOT = + "com/commercetools/sync/products/utils/productVariantUpdateActionUtils/"; + private static final String OLD_PROD_WITH_VARIANTS = RES_ROOT + "productOld.json"; + private static final String OLD_PROD_WITHOUT_MV_KEY_SKU = + RES_ROOT + "productOld_noMasterVariantKeySku.json"; + + // this product's variants don't contain old master variant + private static final String NEW_PROD_DRAFT_WITH_VARIANTS_REMOVE_MASTER = + RES_ROOT + "productDraftNew_changeRemoveMasterVariant.json"; + + // this product's variants contain only attribute update + private static final String NEW_PROD_DRAFT_WITH_MATCHING_VARIANTS_WITH_UPDATED_ATTR_VALUES = + RES_ROOT + "productDraftNew_matchingVariants.json"; + + // this product's variants contain old master variant, but not as master any more + private static final String NEW_PROD_DRAFT_WITH_VARIANTS_MOVE_MASTER = + RES_ROOT + "productDraftNew_moveMasterVariant.json"; + + private static final String NEW_PROD_DRAFT_WITHOUT_MV = + RES_ROOT + "productDraftNew_noMasterVariant.json"; + + private static final String NEW_PROD_DRAFT_WITHOUT_MV_KEY = + RES_ROOT + "productDraftNew_noMasterVariantKey.json"; + + private static final String NEW_PROD_DRAFT_WITHOUT_MV_SKU = + RES_ROOT + "productDraftNew_noMasterVariantSku.json"; + + @Test + void buildVariantsUpdateActions_updatesVariants() { + // preparation + final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); + final ProductDraft productDraftNew = + createProductDraftFromJson(NEW_PROD_DRAFT_WITH_VARIANTS_REMOVE_MASTER); + + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).syncFilter(SyncFilter.of()).build(); + + final Map attributesMetaData = new HashMap<>(); + final AttributeMetaData priceInfo = + AttributeMetaData.of(AttributeDefinitionBuilder.of("priceInfo", null, null).build()); + attributesMetaData.put("priceInfo", priceInfo); + + final List> updateActions = + buildVariantsUpdateActions( + productOld, productDraftNew, productSyncOptions, attributesMetaData); + + // check remove variants are the first in the list, but not the master variant + assertThat(updateActions.subList(0, 2)) + .containsExactlyInAnyOrder( + RemoveVariant.ofVariantId(2, true), RemoveVariant.ofVariantId(3, true)); + + // check add actions + + final ProductVariantDraft draftMaster = productDraftNew.getMasterVariant(); + final ProductVariantDraft draft5 = productDraftNew.getVariants().get(1); + final ProductVariantDraft draft6 = productDraftNew.getVariants().get(2); + final ProductVariantDraft draft7 = productDraftNew.getVariants().get(3); + + assertThat(updateActions) + .contains( + AddVariant.of( + draftMaster.getAttributes(), + draftMaster.getPrices(), + draftMaster.getSku(), + true) + .withKey(draftMaster.getKey()) + .withImages(draftMaster.getImages()), AddVariant.of(draft5.getAttributes(), draft5.getPrices(), draft5.getSku(), true) - .withKey(draft5.getKey()) - .withImages(draft5.getImages()), + .withKey(draft5.getKey()) + .withImages(draft5.getImages()), AddVariant.of(draft6.getAttributes(), draft6.getPrices(), draft6.getSku(), true) - .withKey(draft6.getKey()) - .withImages(draft6.getImages()), + .withKey(draft6.getKey()) + .withImages(draft6.getImages()), AddVariant.of(draft7.getAttributes(), draft7.getPrices(), draft7.getSku(), true) - .withKey(draft7.getKey()) - .withImages(draft7.getImages())); - - // Check add asset actions of new variants - assertThat(updateActions).containsAll( - draft7.getAssets() - .stream() - .map(assetDraft -> (UpdateAction) AddAsset - .ofSku(draft7.getSku(), assetDraft) - .withStaged(true)) - .collect(toList()) - ); - - // variant 4 sku change - assertThat(updateActions).containsOnlyOnce(SetSku.of(4, "var-44-sku", true)); - - // verify image update of variant 4 - RemoveImage removeImage = RemoveImage.ofVariantId(4, "https://xxx.ggg/4.png", true); - AddExternalImage addExternalImage = - AddExternalImage.ofVariantId(4, productDraftNew.getVariants().get(0).getImages().get(0), true); - assertThat(updateActions).containsOnlyOnce(removeImage); - assertThat(updateActions).containsOnlyOnce(addExternalImage); - assertThat(updateActions.indexOf(removeImage)) - .withFailMessage("Remove image action must be executed before add image action") - .isLessThan(updateActions.indexOf(addExternalImage)); - - // verify attributes changes - assertThat(updateActions).contains(SetAttribute.ofVariantId(4, "priceInfo", "44/kg", true)); - - // change master variant must be always after variants are added/updated, - // because it is set by SKU and we should be sure the master variant is already added and SKUs are actual. - // Also, master variant should be removed because it is missing in NEW_PROD_DRAFT_WITH_VARIANTS_REMOVE_MASTER - final int size = updateActions.size(); - assertThat(updateActions.subList(size - 2, size)).containsExactly( + .withKey(draft7.getKey()) + .withImages(draft7.getImages())); + + // Check add asset actions of new variants + assertThat(updateActions) + .containsAll( + draft7.getAssets().stream() + .map( + assetDraft -> + (UpdateAction) + AddAsset.ofSku(draft7.getSku(), assetDraft).withStaged(true)) + .collect(toList())); + + // variant 4 sku change + assertThat(updateActions).containsOnlyOnce(SetSku.of(4, "var-44-sku", true)); + + // verify image update of variant 4 + RemoveImage removeImage = RemoveImage.ofVariantId(4, "https://xxx.ggg/4.png", true); + AddExternalImage addExternalImage = + AddExternalImage.ofVariantId( + 4, productDraftNew.getVariants().get(0).getImages().get(0), true); + assertThat(updateActions).containsOnlyOnce(removeImage); + assertThat(updateActions).containsOnlyOnce(addExternalImage); + assertThat(updateActions.indexOf(removeImage)) + .withFailMessage("Remove image action must be executed before add image action") + .isLessThan(updateActions.indexOf(addExternalImage)); + + // verify attributes changes + assertThat(updateActions).contains(SetAttribute.ofVariantId(4, "priceInfo", "44/kg", true)); + + // change master variant must be always after variants are added/updated, + // because it is set by SKU and we should be sure the master variant is already added and SKUs + // are actual. + // Also, master variant should be removed because it is missing in + // NEW_PROD_DRAFT_WITH_VARIANTS_REMOVE_MASTER + final int size = updateActions.size(); + assertThat(updateActions.subList(size - 2, size)) + .containsExactly( ChangeMasterVariant.ofSku("var-7-sku", true), RemoveVariant.of(productOld.getMasterData().getStaged().getMasterVariant())); - } - - @Test - void buildVariantsUpdateActions_updateVariantsWithSameForAll() { - // preparation - final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); - final ProductDraft productDraftNew = createProductDraftFromJson( - NEW_PROD_DRAFT_WITH_MATCHING_VARIANTS_WITH_UPDATED_ATTR_VALUES); - - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .syncFilter(SyncFilter.of()) - .build(); - - final Map attributesMetaData = new HashMap<>(); - final AttributeMetaData priceInfo = AttributeMetaData.of( - AttributeDefinitionBuilder.of("priceInfo", null, null) - .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) - .build()); - final AttributeMetaData size = AttributeMetaData.of( - AttributeDefinitionBuilder.of("size", null, null) - .build()); - attributesMetaData.put("priceInfo", priceInfo); - attributesMetaData.put("size", size); - - final List> updateActions = - buildVariantsUpdateActions(productOld, productDraftNew, productSyncOptions, attributesMetaData); - - // check that we only have one generated action for all the variants and no duplicates - assertThat(updateActions.size()).isEqualTo(3); - assertThat(updateActions).containsOnlyOnce(SetAttributeInAllVariants.of( - AttributeDraft.of("priceInfo", "74,90/kg"), true)); - // Other update actions can be duplicated per variant - assertThat(updateActions).containsOnlyOnce(SetAttribute.of(2, - AttributeDraft.of("size", "ca. 1 x 1200 g"), true)); - assertThat(updateActions).containsOnlyOnce(SetAttribute.of(3, - AttributeDraft.of("size", "ca. 1 x 1200 g"), true)); - } - - @Test - void buildVariantsUpdateActions_doesNotRemoveMaster() { - final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); - final ProductDraft productDraftNew = createProductDraftFromJson(NEW_PROD_DRAFT_WITH_VARIANTS_MOVE_MASTER); + } - final ProductSyncOptions productSyncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .syncFilter(SyncFilter.of()) - .build(); + @Test + void buildVariantsUpdateActions_updateVariantsWithSameForAll() { + // preparation + final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); + final ProductDraft productDraftNew = + createProductDraftFromJson(NEW_PROD_DRAFT_WITH_MATCHING_VARIANTS_WITH_UPDATED_ATTR_VALUES); - final Map attributesMetaData = new HashMap<>(); + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).syncFilter(SyncFilter.of()).build(); - - final AttributeMetaData priceInfo = AttributeMetaData.of( + final Map attributesMetaData = new HashMap<>(); + final AttributeMetaData priceInfo = + AttributeMetaData.of( AttributeDefinitionBuilder.of("priceInfo", null, null) - .build()); - attributesMetaData.put("priceInfo", priceInfo); - - final List> updateActions = - buildVariantsUpdateActions(productOld, productDraftNew, productSyncOptions, attributesMetaData); - - // check remove variants are the first in the list, but not the master variant - assertThat(updateActions.subList(0, 3)) - .containsExactlyInAnyOrder(RemoveVariant.ofVariantId(2, true), - RemoveVariant.ofVariantId(3, true), RemoveVariant.ofVariantId(4, true)); - - // change master variant must be always after variants are added/updated, - // because it is set by SKU and we should be sure the master variant is already added and SKUs are actual. - assertThat(updateActions).endsWith(ChangeMasterVariant.ofSku("var-7-sku", true)); - - // Old master variant should NOT be removed because it exists in NEW_PROD_DRAFT_WITH_VARIANTS_MOVE_MASTER - final ProductVariant oldMasterVariant = productOld.getMasterData().getStaged().getMasterVariant(); - assertThat(updateActions).filteredOn(action -> { - // verify old master variant is not removed - if (action instanceof RemoveVariant) { - RemoveVariant removeVariantAction = (RemoveVariant)action; - return Objects.equals(oldMasterVariant.getId(), removeVariantAction.getId()) - || Objects.equals(oldMasterVariant.getSku(), removeVariantAction.getSku()); - } - return false; - } - ).isEmpty(); - } - - @Test - void buildVariantsUpdateActions_withEmptyOldMasterVariantKey() { - assertMissingMasterVariantKey(OLD_PROD_WITHOUT_MV_KEY_SKU, NEW_PROD_DRAFT_WITH_VARIANTS_MOVE_MASTER, - BLANK_OLD_MASTER_VARIANT_KEY); - } - - @Test - void buildVariantsUpdateActions_withEmptyNewMasterVariantOrKey_ShouldNotBuildActionAndTriggerCallback() { - assertMissingMasterVariantKey(OLD_PROD_WITH_VARIANTS, NEW_PROD_DRAFT_WITHOUT_MV, BLANK_NEW_MASTER_VARIANT_KEY); - assertMissingMasterVariantKey(OLD_PROD_WITH_VARIANTS, NEW_PROD_DRAFT_WITHOUT_MV_KEY, - BLANK_NEW_MASTER_VARIANT_KEY); - } - - @Test - void buildVariantsUpdateActions_withEmptyBothMasterVariantKey_ShouldNotBuildActionAndTriggerCallback() { - assertMissingMasterVariantKey(OLD_PROD_WITHOUT_MV_KEY_SKU, NEW_PROD_DRAFT_WITHOUT_MV_KEY, - BLANK_OLD_MASTER_VARIANT_KEY, BLANK_NEW_MASTER_VARIANT_KEY); - } - - private void assertMissingMasterVariantKey(final String oldProduct, - final String newProduct, - final String ... errorMessages) { - final Product productOld = createProductFromJson(oldProduct); - final ProductDraft productDraftNew = createProductDraftFromJson(newProduct); - - final List errorsCatcher = new ArrayList<>(); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) + .build()); + final AttributeMetaData size = + AttributeMetaData.of(AttributeDefinitionBuilder.of("size", null, null).build()); + attributesMetaData.put("priceInfo", priceInfo); + attributesMetaData.put("size", size); + + final List> updateActions = + buildVariantsUpdateActions( + productOld, productDraftNew, productSyncOptions, attributesMetaData); + + // check that we only have one generated action for all the variants and no duplicates + assertThat(updateActions.size()).isEqualTo(3); + assertThat(updateActions) + .containsOnlyOnce( + SetAttributeInAllVariants.of(AttributeDraft.of("priceInfo", "74,90/kg"), true)); + // Other update actions can be duplicated per variant + assertThat(updateActions) + .containsOnlyOnce(SetAttribute.of(2, AttributeDraft.of("size", "ca. 1 x 1200 g"), true)); + assertThat(updateActions) + .containsOnlyOnce(SetAttribute.of(3, AttributeDraft.of("size", "ca. 1 x 1200 g"), true)); + } + + @Test + void buildVariantsUpdateActions_doesNotRemoveMaster() { + final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); + final ProductDraft productDraftNew = + createProductDraftFromJson(NEW_PROD_DRAFT_WITH_VARIANTS_MOVE_MASTER); + + final ProductSyncOptions productSyncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).syncFilter(SyncFilter.of()).build(); + + final Map attributesMetaData = new HashMap<>(); + + final AttributeMetaData priceInfo = + AttributeMetaData.of(AttributeDefinitionBuilder.of("priceInfo", null, null).build()); + attributesMetaData.put("priceInfo", priceInfo); + + final List> updateActions = + buildVariantsUpdateActions( + productOld, productDraftNew, productSyncOptions, attributesMetaData); + + // check remove variants are the first in the list, but not the master variant + assertThat(updateActions.subList(0, 3)) + .containsExactlyInAnyOrder( + RemoveVariant.ofVariantId(2, true), + RemoveVariant.ofVariantId(3, true), + RemoveVariant.ofVariantId(4, true)); + + // change master variant must be always after variants are added/updated, + // because it is set by SKU and we should be sure the master variant is already added and SKUs + // are actual. + assertThat(updateActions).endsWith(ChangeMasterVariant.ofSku("var-7-sku", true)); + + // Old master variant should NOT be removed because it exists in + // NEW_PROD_DRAFT_WITH_VARIANTS_MOVE_MASTER + final ProductVariant oldMasterVariant = + productOld.getMasterData().getStaged().getMasterVariant(); + assertThat(updateActions) + .filteredOn( + action -> { + // verify old master variant is not removed + if (action instanceof RemoveVariant) { + RemoveVariant removeVariantAction = (RemoveVariant) action; + return Objects.equals(oldMasterVariant.getId(), removeVariantAction.getId()) + || Objects.equals(oldMasterVariant.getSku(), removeVariantAction.getSku()); + } + return false; + }) + .isEmpty(); + } + + @Test + void buildVariantsUpdateActions_withEmptyOldMasterVariantKey() { + assertMissingMasterVariantKey( + OLD_PROD_WITHOUT_MV_KEY_SKU, + NEW_PROD_DRAFT_WITH_VARIANTS_MOVE_MASTER, + BLANK_OLD_MASTER_VARIANT_KEY); + } + + @Test + void + buildVariantsUpdateActions_withEmptyNewMasterVariantOrKey_ShouldNotBuildActionAndTriggerCallback() { + assertMissingMasterVariantKey( + OLD_PROD_WITH_VARIANTS, NEW_PROD_DRAFT_WITHOUT_MV, BLANK_NEW_MASTER_VARIANT_KEY); + assertMissingMasterVariantKey( + OLD_PROD_WITH_VARIANTS, NEW_PROD_DRAFT_WITHOUT_MV_KEY, BLANK_NEW_MASTER_VARIANT_KEY); + } + + @Test + void + buildVariantsUpdateActions_withEmptyBothMasterVariantKey_ShouldNotBuildActionAndTriggerCallback() { + assertMissingMasterVariantKey( + OLD_PROD_WITHOUT_MV_KEY_SKU, + NEW_PROD_DRAFT_WITHOUT_MV_KEY, + BLANK_OLD_MASTER_VARIANT_KEY, + BLANK_NEW_MASTER_VARIANT_KEY); + } + + private void assertMissingMasterVariantKey( + final String oldProduct, final String newProduct, final String... errorMessages) { + final Product productOld = createProductFromJson(oldProduct); + final ProductDraft productDraftNew = createProductDraftFromJson(newProduct); + + final List errorsCatcher = new ArrayList<>(); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) .errorCallback( - (exception, oldResource, newResource, updateActions) -> errorsCatcher.add(exception.getMessage())) + (exception, oldResource, newResource, updateActions) -> + errorsCatcher.add(exception.getMessage())) .build(); - final List> updateActions = - buildVariantsUpdateActions(productOld, productDraftNew, syncOptions, emptyMap()); - assertThat(updateActions).isEmpty(); - assertThat(errorsCatcher).hasSize(errorMessages.length); - - // verify all expected error messages - for (int i = 0; i < errorMessages.length; i++) { - assertThat(errorsCatcher.get(i)) - .containsIgnoringCase("failed") - .contains(productOld.getKey()) - .containsIgnoringCase(errorMessages[i]); - } - } - - @Test - void buildChangeMasterVariantUpdateAction_changesMasterVariant() { - final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); - final ProductDraft productDraftNew = createProductDraftFromJson(NEW_PROD_DRAFT_WITH_VARIANTS_REMOVE_MASTER); - - final List> changeMasterVariant = - buildChangeMasterVariantUpdateAction(productOld, productDraftNew, - ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - assertThat(changeMasterVariant).hasSize(2); - assertThat(changeMasterVariant.get(0)) - .isEqualTo(ChangeMasterVariant.ofSku(productDraftNew.getMasterVariant().getSku(), true)); - assertThat(changeMasterVariant.get(1)) - .isEqualTo(RemoveVariant.of(productOld.getMasterData().getStaged().getMasterVariant())); + final List> updateActions = + buildVariantsUpdateActions(productOld, productDraftNew, syncOptions, emptyMap()); + assertThat(updateActions).isEmpty(); + assertThat(errorsCatcher).hasSize(errorMessages.length); + + // verify all expected error messages + for (int i = 0; i < errorMessages.length; i++) { + assertThat(errorsCatcher.get(i)) + .containsIgnoringCase("failed") + .contains(productOld.getKey()) + .containsIgnoringCase(errorMessages[i]); } - - @Test - void buildVariantsUpdateActions_withEmptyKey_ShouldNotBuildActionAndTriggerCallback() { - assertChangeMasterVariantEmptyErrorCatcher(NEW_PROD_DRAFT_WITHOUT_MV_KEY, BLANK_NEW_MASTER_VARIANT_KEY); - } - - @Test - void buildVariantsUpdateActions_withEmptySku_ShouldNotBuildActionAndTriggerCallback() { - assertChangeMasterVariantEmptyErrorCatcher(NEW_PROD_DRAFT_WITHOUT_MV_SKU, BLANK_NEW_MASTER_VARIANT_SKU); - } - - private void assertChangeMasterVariantEmptyErrorCatcher(final String productMockName, - final String expectedErrorReason) { - final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); - final ProductDraft productDraftNew_withoutKey = createProductDraftFromJson(productMockName); - - final List errorsCatcher = new ArrayList<>(); - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + } + + @Test + void buildChangeMasterVariantUpdateAction_changesMasterVariant() { + final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); + final ProductDraft productDraftNew = + createProductDraftFromJson(NEW_PROD_DRAFT_WITH_VARIANTS_REMOVE_MASTER); + + final List> changeMasterVariant = + buildChangeMasterVariantUpdateAction( + productOld, + productDraftNew, + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build()); + assertThat(changeMasterVariant).hasSize(2); + assertThat(changeMasterVariant.get(0)) + .isEqualTo(ChangeMasterVariant.ofSku(productDraftNew.getMasterVariant().getSku(), true)); + assertThat(changeMasterVariant.get(1)) + .isEqualTo(RemoveVariant.of(productOld.getMasterData().getStaged().getMasterVariant())); + } + + @Test + void buildVariantsUpdateActions_withEmptyKey_ShouldNotBuildActionAndTriggerCallback() { + assertChangeMasterVariantEmptyErrorCatcher( + NEW_PROD_DRAFT_WITHOUT_MV_KEY, BLANK_NEW_MASTER_VARIANT_KEY); + } + + @Test + void buildVariantsUpdateActions_withEmptySku_ShouldNotBuildActionAndTriggerCallback() { + assertChangeMasterVariantEmptyErrorCatcher( + NEW_PROD_DRAFT_WITHOUT_MV_SKU, BLANK_NEW_MASTER_VARIANT_SKU); + } + + private void assertChangeMasterVariantEmptyErrorCatcher( + final String productMockName, final String expectedErrorReason) { + final Product productOld = createProductFromJson(OLD_PROD_WITH_VARIANTS); + final ProductDraft productDraftNew_withoutKey = createProductDraftFromJson(productMockName); + + final List errorsCatcher = new ArrayList<>(); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) .errorCallback( - (exception, oldResource, newResource, updateActions) -> errorsCatcher.add(exception.getMessage())) + (exception, oldResource, newResource, updateActions) -> + errorsCatcher.add(exception.getMessage())) .build(); - final List> changeMasterVariant = - buildChangeMasterVariantUpdateAction(productOld, productDraftNew_withoutKey, syncOptions); - assertThat(changeMasterVariant).hasSize(0); - assertThat(errorsCatcher).hasSize(1); - assertThat(errorsCatcher.get(0)) - .containsIgnoringCase("failed") - .contains(productOld.getKey()) - .containsIgnoringCase(expectedErrorReason); - } - - @Test - void buildAddVariantUpdateActionFromDraft_WithAttribsPricesAndImages_ShouldBuildCorrectAddVariantAction() { - // preparation - final List attributeList = emptyList(); - final List priceList = emptyList(); - final List imageList = emptyList(); - final ProductVariantDraft draft = ProductVariantDraftBuilder.of() - .attributes(attributeList) - .prices(priceList) - .sku("testSKU") - .key("testKey") - .images(imageList) - .build(); - - // test - final List> result = buildAddVariantUpdateActionFromDraft(draft); - - // assertion - assertThat(result).hasOnlyOneElementSatisfying(action -> { - assertThat(action).isInstanceOf(AddVariant.class); - final AddVariant addVariant = (AddVariant) action; - assertThat(addVariant.getAttributes()).isSameAs(attributeList); - assertThat(addVariant.getPrices()).isSameAs(priceList); - assertThat(addVariant.getSku()).isEqualTo("testSKU"); - assertThat(addVariant.getKey()).isEqualTo("testKey"); - assertThat(addVariant.getImages()).isSameAs(imageList); - }); - } - - @Test - void buildAddVariantUpdateActionFromDraft_WithNoAssets_BuildsNoAddAssets() { - // preparation - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder.of() - .sku("foo") - .build(); - - // test - final List> result = buildAddVariantUpdateActionFromDraft(productVariantDraft); - - // assertion - assertThat(result).containsExactlyInAnyOrder( - AddVariant.of(null, null, "foo", true) - ); - } - - @Test - void buildAddVariantUpdateActionFromDraft_WithMultipleAssets_BuildsMultipleAddAssetsActions() { - // preparation - final List assetDrafts = IntStream - .range(1, 4) - .mapToObj(i -> AssetDraftBuilder - .of(singletonList(AssetSourceBuilder.ofUri("foo").build()), ofEnglish("assetName")) - .key(i + "") - .build()) - .collect(toList()); - - final ProductVariantDraft productVariantDraft = ProductVariantDraftBuilder - .of() - .sku("foo") - .assets(assetDrafts) + final List> changeMasterVariant = + buildChangeMasterVariantUpdateAction(productOld, productDraftNew_withoutKey, syncOptions); + assertThat(changeMasterVariant).hasSize(0); + assertThat(errorsCatcher).hasSize(1); + assertThat(errorsCatcher.get(0)) + .containsIgnoringCase("failed") + .contains(productOld.getKey()) + .containsIgnoringCase(expectedErrorReason); + } + + @Test + void + buildAddVariantUpdateActionFromDraft_WithAttribsPricesAndImages_ShouldBuildCorrectAddVariantAction() { + // preparation + final List attributeList = emptyList(); + final List priceList = emptyList(); + final List imageList = emptyList(); + final ProductVariantDraft draft = + ProductVariantDraftBuilder.of() + .attributes(attributeList) + .prices(priceList) + .sku("testSKU") + .key("testKey") + .images(imageList) .build(); - // test - final List> result = buildAddVariantUpdateActionFromDraft(productVariantDraft); - - // assertion - final ArrayList> expectedActions = - new ArrayList<>(singletonList(AddVariant.of(null, null, "foo", true))); - expectedActions.addAll( - assetDrafts.stream() - .map(assetDraft -> AddAsset.ofSku(productVariantDraft.getSku(), assetDraft).withStaged(true)) - .collect(toList()) - ); - - assertThat(result).containsExactlyElementsOf(expectedActions); - } + // test + final List> result = buildAddVariantUpdateActionFromDraft(draft); + + // assertion + assertThat(result) + .hasOnlyOneElementSatisfying( + action -> { + assertThat(action).isInstanceOf(AddVariant.class); + final AddVariant addVariant = (AddVariant) action; + assertThat(addVariant.getAttributes()).isSameAs(attributeList); + assertThat(addVariant.getPrices()).isSameAs(priceList); + assertThat(addVariant.getSku()).isEqualTo("testSKU"); + assertThat(addVariant.getKey()).isEqualTo("testKey"); + assertThat(addVariant.getImages()).isSameAs(imageList); + }); + } + + @Test + void buildAddVariantUpdateActionFromDraft_WithNoAssets_BuildsNoAddAssets() { + // preparation + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().sku("foo").build(); + + // test + final List> result = + buildAddVariantUpdateActionFromDraft(productVariantDraft); + + // assertion + assertThat(result).containsExactlyInAnyOrder(AddVariant.of(null, null, "foo", true)); + } + + @Test + void buildAddVariantUpdateActionFromDraft_WithMultipleAssets_BuildsMultipleAddAssetsActions() { + // preparation + final List assetDrafts = + IntStream.range(1, 4) + .mapToObj( + i -> + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("foo").build()), + ofEnglish("assetName")) + .key(i + "") + .build()) + .collect(toList()); - @Test - void getAllVariants_WithNoVariants_ShouldReturnListWithNullMasterVariant() { - final ProductDraft productDraft = ProductDraftBuilder - .of(mock(ProductType.class), ofEnglish("name"), ofEnglish("slug"), emptyList()) + final ProductVariantDraft productVariantDraft = + ProductVariantDraftBuilder.of().sku("foo").assets(assetDrafts).build(); + + // test + final List> result = + buildAddVariantUpdateActionFromDraft(productVariantDraft); + + // assertion + final ArrayList> expectedActions = + new ArrayList<>(singletonList(AddVariant.of(null, null, "foo", true))); + expectedActions.addAll( + assetDrafts.stream() + .map( + assetDraft -> + AddAsset.ofSku(productVariantDraft.getSku(), assetDraft).withStaged(true)) + .collect(toList())); + + assertThat(result).containsExactlyElementsOf(expectedActions); + } + + @Test + void getAllVariants_WithNoVariants_ShouldReturnListWithNullMasterVariant() { + final ProductDraft productDraft = + ProductDraftBuilder.of( + mock(ProductType.class), ofEnglish("name"), ofEnglish("slug"), emptyList()) .build(); - final List allVariants = getAllVariants(productDraft); + final List allVariants = getAllVariants(productDraft); - assertThat(allVariants).hasSize(1).containsOnlyNulls(); - } + assertThat(allVariants).hasSize(1).containsOnlyNulls(); + } - @Test - void getAllVariants_WithOnlyMasterVariant_ShouldReturnListWithMasterVariant() { - final ProductVariantDraft masterVariant = ProductVariantDraftBuilder.of().build(); + @Test + void getAllVariants_WithOnlyMasterVariant_ShouldReturnListWithMasterVariant() { + final ProductVariantDraft masterVariant = ProductVariantDraftBuilder.of().build(); - final ProductDraft productDraft = ProductDraftBuilder - .of(mock(ProductType.class), ofEnglish("name"), ofEnglish("slug"), masterVariant) + final ProductDraft productDraft = + ProductDraftBuilder.of( + mock(ProductType.class), ofEnglish("name"), ofEnglish("slug"), masterVariant) .build(); - final List allVariants = getAllVariants(productDraft); + final List allVariants = getAllVariants(productDraft); - assertThat(allVariants).containsExactly(masterVariant); - } + assertThat(allVariants).containsExactly(masterVariant); + } - @Test - void getAllVariants_WithOnlyVariants_ShouldReturnListWithVariants() { - final ProductVariantDraft variant1 = ProductVariantDraftBuilder.of().build(); - final ProductVariantDraft variant2 = ProductVariantDraftBuilder.of().build(); - final List variants = asList(variant1, variant2); + @Test + void getAllVariants_WithOnlyVariants_ShouldReturnListWithVariants() { + final ProductVariantDraft variant1 = ProductVariantDraftBuilder.of().build(); + final ProductVariantDraft variant2 = ProductVariantDraftBuilder.of().build(); + final List variants = asList(variant1, variant2); - final ProductDraft productDraft = ProductDraftBuilder - .of(mock(ProductType.class), ofEnglish("name"), ofEnglish("slug"), variants) + final ProductDraft productDraft = + ProductDraftBuilder.of( + mock(ProductType.class), ofEnglish("name"), ofEnglish("slug"), variants) .build(); - final List allVariants = getAllVariants(productDraft); + final List allVariants = getAllVariants(productDraft); - assertThat(allVariants).containsExactlyElementsOf(variants); - } + assertThat(allVariants).containsExactlyElementsOf(variants); + } - @Test - void getAllVariants_WithNullInVariants_ShouldReturnListWithVariants() { - final ProductVariantDraft variant1 = ProductVariantDraftBuilder.of().build(); - final ProductVariantDraft variant2 = ProductVariantDraftBuilder.of().build(); - final List variants = asList(variant1, variant2, null); + @Test + void getAllVariants_WithNullInVariants_ShouldReturnListWithVariants() { + final ProductVariantDraft variant1 = ProductVariantDraftBuilder.of().build(); + final ProductVariantDraft variant2 = ProductVariantDraftBuilder.of().build(); + final List variants = asList(variant1, variant2, null); - final ProductDraft productDraft = ProductDraftBuilder - .of(mock(ProductType.class), ofEnglish("name"), ofEnglish("slug"), variants) + final ProductDraft productDraft = + ProductDraftBuilder.of( + mock(ProductType.class), ofEnglish("name"), ofEnglish("slug"), variants) .build(); - final List allVariants = getAllVariants(productDraft); + final List allVariants = getAllVariants(productDraft); - assertThat(allVariants).containsExactlyElementsOf(variants); - } -} \ No newline at end of file + assertThat(allVariants).containsExactlyElementsOf(variants); + } +} diff --git a/src/test/java/com/commercetools/sync/products/utils/ProductVariantAssetUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/products/utils/ProductVariantAssetUpdateActionUtilsTest.java index 37dc39b7bc..4cfdda949c 100644 --- a/src/test/java/com/commercetools/sync/products/utils/ProductVariantAssetUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/ProductVariantAssetUpdateActionUtilsTest.java @@ -1,5 +1,19 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildActions; +import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildChangeAssetNameUpdateAction; +import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildCustomUpdateActions; +import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildSetAssetDescriptionUpdateAction; +import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildSetAssetSourcesUpdateAction; +import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildSetAssetTagsUpdateAction; +import static io.sphere.sdk.models.LocalizedString.empty; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -23,8 +37,6 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -34,512 +46,508 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; - -import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildActions; -import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildChangeAssetNameUpdateAction; -import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildCustomUpdateActions; -import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildSetAssetDescriptionUpdateAction; -import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildSetAssetSourcesUpdateAction; -import static com.commercetools.sync.products.utils.ProductVariantAssetUpdateActionUtils.buildSetAssetTagsUpdateAction; -import static io.sphere.sdk.models.LocalizedString.empty; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductVariantAssetUpdateActionUtilsTest { - private static final ProductSyncOptions SYNC_OPTIONS = ProductSyncOptionsBuilder - .of(mock(SphereClient.class)).build(); - final Product product = mock(Product.class); - final ProductDraft productDraft = mock(ProductDraft.class); - - @Test - void buildActions_WithDifferentValues_ShouldBuildUpdateAction() { - final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); - final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); - - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - - final Set oldTags = new HashSet<>(); - oldTags.add("oldTag"); - final Set newTags = new HashSet<>(); - oldTags.add("newTag"); - - final List oldAssetSources = singletonList(AssetSourceBuilder.ofUri("oldUri").build()); - final List newAssetSources = singletonList(AssetSourceBuilder.ofUri("newUri").build()); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getName()).thenReturn(oldName); - when(oldAsset.getSources()).thenReturn(oldAssetSources); - when(oldAsset.getTags()).thenReturn(oldTags); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final AssetDraft newAssetDraft = AssetDraftBuilder.of(newAssetSources, newName) - .tags(newTags) - .custom(newCustomFieldsDraft) - .build(); - - Product product = mock(Product.class); - ProductDraft productDraft = mock(ProductDraft.class); - final List> updateActions = buildActions(product,productDraft,1, - oldAsset, newAssetDraft, SYNC_OPTIONS); - - assertThat(updateActions).hasSize(5); - assertThat(updateActions).containsExactlyInAnyOrder( + private static final ProductSyncOptions SYNC_OPTIONS = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + final Product product = mock(Product.class); + final ProductDraft productDraft = mock(ProductDraft.class); + + @Test + void buildActions_WithDifferentValues_ShouldBuildUpdateAction() { + final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); + final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); + + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + + final Set oldTags = new HashSet<>(); + oldTags.add("oldTag"); + final Set newTags = new HashSet<>(); + oldTags.add("newTag"); + + final List oldAssetSources = + singletonList(AssetSourceBuilder.ofUri("oldUri").build()); + final List newAssetSources = + singletonList(AssetSourceBuilder.ofUri("newUri").build()); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getName()).thenReturn(oldName); + when(oldAsset.getSources()).thenReturn(oldAssetSources); + when(oldAsset.getTags()).thenReturn(oldTags); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(newAssetSources, newName) + .tags(newTags) + .custom(newCustomFieldsDraft) + .build(); + + Product product = mock(Product.class); + ProductDraft productDraft = mock(ProductDraft.class); + final List> updateActions = + buildActions(product, productDraft, 1, oldAsset, newAssetDraft, SYNC_OPTIONS); + + assertThat(updateActions).hasSize(5); + assertThat(updateActions) + .containsExactlyInAnyOrder( ChangeAssetName.ofAssetKeyAndVariantId(1, null, newName, true), SetAssetTags.ofVariantIdAndAssetKey(1, null, newTags, true), SetAssetSources.ofVariantIdAndAssetKey(1, null, newAssetSources, true), - SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, null, - "invisibleInShop", newCustomFieldsMap.get("invisibleInShop"), true), - SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, null, "backgroundColor", - newCustomFieldsMap.get("backgroundColor"), true) - ); - } - - @Test - void buildActions_WithIdenticalValues_ShouldBuildUpdateAction() { - final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); - - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final Set oldTags = new HashSet<>(); - oldTags.add("oldTag"); + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, null, "invisibleInShop", newCustomFieldsMap.get("invisibleInShop"), true), + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, null, "backgroundColor", newCustomFieldsMap.get("backgroundColor"), true)); + } - final List oldAssetSources = singletonList(AssetSourceBuilder.ofUri("oldUri").build()); + @Test + void buildActions_WithIdenticalValues_ShouldBuildUpdateAction() { + final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getName()).thenReturn(oldName); - when(oldAsset.getSources()).thenReturn(oldAssetSources); - when(oldAsset.getTags()).thenReturn(oldTags); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(oldAsset).build(); + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final Set oldTags = new HashSet<>(); + oldTags.add("oldTag"); - final List> updateActions = buildActions(product, productDraft,1, oldAsset, - newAssetDraft, SYNC_OPTIONS); + final List oldAssetSources = + singletonList(AssetSourceBuilder.ofUri("oldUri").build()); - assertThat(updateActions).isEmpty(); - } + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getName()).thenReturn(oldName); + when(oldAsset.getSources()).thenReturn(oldAssetSources); + when(oldAsset.getTags()).thenReturn(oldTags); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); - @Test - void buildChangeAssetNameUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); - final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); + final AssetDraft newAssetDraft = AssetDraftBuilder.of(oldAsset).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getName()).thenReturn(oldName); + final List> updateActions = + buildActions(product, productDraft, 1, oldAsset, newAssetDraft, SYNC_OPTIONS); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), newName).build(); + assertThat(updateActions).isEmpty(); + } - final UpdateAction changeNameUpdateAction = - buildChangeAssetNameUpdateAction(1, oldAsset, newAssetDraft).orElse(null); + @Test + void buildChangeAssetNameUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); + final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction).isInstanceOf(ChangeAssetName.class); - assertThat(((ChangeAssetName) changeNameUpdateAction).getName()).isEqualTo(newName); - } + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getName()).thenReturn(oldName); - @Test - void buildChangeAssetNameUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); + final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), newName).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getName()).thenReturn(oldName); + final UpdateAction changeNameUpdateAction = + buildChangeAssetNameUpdateAction(1, oldAsset, newAssetDraft).orElse(null); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), oldName).build(); + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction).isInstanceOf(ChangeAssetName.class); + assertThat(((ChangeAssetName) changeNameUpdateAction).getName()).isEqualTo(newName); + } - final Optional> changeNameUpdateAction = - buildChangeAssetNameUpdateAction(1, oldAsset, newAssetDraft); + @Test + void buildChangeAssetNameUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final LocalizedString oldName = LocalizedString.of(Locale.GERMAN, "oldName"); - assertThat(changeNameUpdateAction).isEmpty(); - } + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getName()).thenReturn(oldName); - @Test - void buildSetAssetDescriptionUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final LocalizedString oldDesc = LocalizedString.of(Locale.GERMAN, "oldDesc"); - final LocalizedString newDesc = LocalizedString.of(Locale.GERMAN, "newDesc"); + final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), oldName).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getDescription()).thenReturn(oldDesc); + final Optional> changeNameUpdateAction = + buildChangeAssetNameUpdateAction(1, oldAsset, newAssetDraft); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .description(newDesc).build(); + assertThat(changeNameUpdateAction).isEmpty(); + } - final UpdateAction setAssetDescription = - buildSetAssetDescriptionUpdateAction(1, oldAsset, newAssetDraft).orElse(null); + @Test + void buildSetAssetDescriptionUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final LocalizedString oldDesc = LocalizedString.of(Locale.GERMAN, "oldDesc"); + final LocalizedString newDesc = LocalizedString.of(Locale.GERMAN, "newDesc"); - assertThat(setAssetDescription).isNotNull(); - assertThat(setAssetDescription).isInstanceOf(SetAssetDescription.class); - assertThat(((SetAssetDescription) setAssetDescription).getDescription()).isEqualTo(newDesc); - } + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getDescription()).thenReturn(oldDesc); - @Test - void buildSetAssetDescriptionUpdateAction_WithNullOldStagedValues_ShouldBuildUpdateAction() { - final LocalizedString newDesc = LocalizedString.of(Locale.GERMAN, "newDesc"); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).description(newDesc).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getDescription()).thenReturn(null); + final UpdateAction setAssetDescription = + buildSetAssetDescriptionUpdateAction(1, oldAsset, newAssetDraft).orElse(null); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .description(newDesc).build(); + assertThat(setAssetDescription).isNotNull(); + assertThat(setAssetDescription).isInstanceOf(SetAssetDescription.class); + assertThat(((SetAssetDescription) setAssetDescription).getDescription()).isEqualTo(newDesc); + } - final UpdateAction setAssetDescription = - buildSetAssetDescriptionUpdateAction(1, oldAsset, newAssetDraft).orElse(null); + @Test + void buildSetAssetDescriptionUpdateAction_WithNullOldStagedValues_ShouldBuildUpdateAction() { + final LocalizedString newDesc = LocalizedString.of(Locale.GERMAN, "newDesc"); - assertThat(setAssetDescription).isNotNull(); - assertThat(setAssetDescription).isInstanceOf(SetAssetDescription.class); - assertThat(((SetAssetDescription) setAssetDescription).getDescription()).isEqualTo(newDesc); - } + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getDescription()).thenReturn(null); - @Test - void buildSetAssetDescriptionUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final LocalizedString oldDesc = LocalizedString.of(Locale.GERMAN, "oldDesc"); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).description(newDesc).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getDescription()).thenReturn(oldDesc); + final UpdateAction setAssetDescription = + buildSetAssetDescriptionUpdateAction(1, oldAsset, newAssetDraft).orElse(null); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .description(oldDesc).build(); + assertThat(setAssetDescription).isNotNull(); + assertThat(setAssetDescription).isInstanceOf(SetAssetDescription.class); + assertThat(((SetAssetDescription) setAssetDescription).getDescription()).isEqualTo(newDesc); + } - final Optional> setAssetDescription = - buildSetAssetDescriptionUpdateAction(1, oldAsset, newAssetDraft); + @Test + void buildSetAssetDescriptionUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final LocalizedString oldDesc = LocalizedString.of(Locale.GERMAN, "oldDesc"); - assertThat(setAssetDescription).isEmpty(); - } + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getDescription()).thenReturn(oldDesc); - @Test - void buildSetAssetTagsUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final Set oldTags = new HashSet<>(); - oldTags.add("oldTag"); - final Set newTags = new HashSet<>(); - newTags.add("newTag"); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).description(oldDesc).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getTags()).thenReturn(oldTags); + final Optional> setAssetDescription = + buildSetAssetDescriptionUpdateAction(1, oldAsset, newAssetDraft); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .tags(newTags).build(); + assertThat(setAssetDescription).isEmpty(); + } - final UpdateAction productUpdateAction = - buildSetAssetTagsUpdateAction(1, oldAsset, newAssetDraft).orElse(null); + @Test + void buildSetAssetTagsUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final Set oldTags = new HashSet<>(); + oldTags.add("oldTag"); + final Set newTags = new HashSet<>(); + newTags.add("newTag"); - assertThat(productUpdateAction).isNotNull(); - assertThat(productUpdateAction).isInstanceOf(SetAssetTags.class); - assertThat(((SetAssetTags) productUpdateAction).getTags()).isEqualTo(newTags); - } + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getTags()).thenReturn(oldTags); - @Test - void buildSetAssetTagsUpdateAction_WithNullOldStagedValues_ShouldBuildUpdateAction() { - final Set newTags = new HashSet<>(); - newTags.add("newTag"); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).tags(newTags).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getTags()).thenReturn(null); + final UpdateAction productUpdateAction = + buildSetAssetTagsUpdateAction(1, oldAsset, newAssetDraft).orElse(null); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .tags(newTags).build(); + assertThat(productUpdateAction).isNotNull(); + assertThat(productUpdateAction).isInstanceOf(SetAssetTags.class); + assertThat(((SetAssetTags) productUpdateAction).getTags()).isEqualTo(newTags); + } - final UpdateAction productUpdateAction = - buildSetAssetTagsUpdateAction(1, oldAsset, newAssetDraft).orElse(null); + @Test + void buildSetAssetTagsUpdateAction_WithNullOldStagedValues_ShouldBuildUpdateAction() { + final Set newTags = new HashSet<>(); + newTags.add("newTag"); - assertThat(productUpdateAction).isNotNull(); - assertThat(productUpdateAction).isInstanceOf(SetAssetTags.class); - assertThat(((SetAssetTags) productUpdateAction).getTags()).isEqualTo(newTags); - } + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getTags()).thenReturn(null); - @Test - void buildSetAssetTagsUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final Set oldTags = new HashSet<>(); - oldTags.add("oldTag"); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).tags(newTags).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getTags()).thenReturn(oldTags); + final UpdateAction productUpdateAction = + buildSetAssetTagsUpdateAction(1, oldAsset, newAssetDraft).orElse(null); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .tags(oldTags).build(); + assertThat(productUpdateAction).isNotNull(); + assertThat(productUpdateAction).isInstanceOf(SetAssetTags.class); + assertThat(((SetAssetTags) productUpdateAction).getTags()).isEqualTo(newTags); + } + @Test + void buildSetAssetTagsUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final Set oldTags = new HashSet<>(); + oldTags.add("oldTag"); - final Optional> productUpdateAction = - buildSetAssetTagsUpdateAction(1, oldAsset, newAssetDraft); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getTags()).thenReturn(oldTags); - assertThat(productUpdateAction).isEmpty(); - } + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).tags(oldTags).build(); - @Test - void buildSetAssetSourcesUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final List oldAssetSources = singletonList(AssetSourceBuilder.ofUri("oldUri").build()); - final List newAssetSources = singletonList(AssetSourceBuilder.ofUri("newUri").build()); + final Optional> productUpdateAction = + buildSetAssetTagsUpdateAction(1, oldAsset, newAssetDraft); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getSources()).thenReturn(oldAssetSources); + assertThat(productUpdateAction).isEmpty(); + } - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .sources(newAssetSources).build(); + @Test + void buildSetAssetSourcesUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final List oldAssetSources = + singletonList(AssetSourceBuilder.ofUri("oldUri").build()); + final List newAssetSources = + singletonList(AssetSourceBuilder.ofUri("newUri").build()); - final UpdateAction productUpdateAction = - buildSetAssetSourcesUpdateAction(1, oldAsset, newAssetDraft).orElse(null); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getSources()).thenReturn(oldAssetSources); - assertThat(productUpdateAction).isNotNull(); - assertThat(productUpdateAction).isInstanceOf(SetAssetSources.class); - assertThat(((SetAssetSources) productUpdateAction).getSources()).isEqualTo(newAssetSources); - } + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).sources(newAssetSources).build(); - @Test - void buildSetAssetSourcesUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final List oldAssetSources = singletonList(AssetSourceBuilder.ofUri("oldUri").build()); + final UpdateAction productUpdateAction = + buildSetAssetSourcesUpdateAction(1, oldAsset, newAssetDraft).orElse(null); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getSources()).thenReturn(oldAssetSources); + assertThat(productUpdateAction).isNotNull(); + assertThat(productUpdateAction).isInstanceOf(SetAssetSources.class); + assertThat(((SetAssetSources) productUpdateAction).getSources()).isEqualTo(newAssetSources); + } - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .sources(oldAssetSources).build(); + @Test + void buildSetAssetSourcesUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final List oldAssetSources = + singletonList(AssetSourceBuilder.ofUri("oldUri").build()); - final Optional> productUpdateAction = - buildSetAssetSourcesUpdateAction(1, oldAsset, newAssetDraft); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getSources()).thenReturn(oldAssetSources); - assertThat(productUpdateAction).isEmpty(); - } + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).sources(oldAssetSources).build(); - @Test - void buildCustomUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + final Optional> productUpdateAction = + buildSetAssetSourcesUpdateAction(1, oldAsset, newAssetDraft); + assertThat(productUpdateAction).isEmpty(); + } - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + @Test + void buildCustomUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); - final List> updateActions = - buildCustomUpdateActions(product, productDraft,1, oldAsset, newAssetDraft, SYNC_OPTIONS); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); - assertThat(updateActions).isEmpty(); - } + final List> updateActions = + buildCustomUpdateActions(product, productDraft, 1, oldAsset, newAssetDraft, SYNC_OPTIONS); - @Test - void buildCustomUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + assertThat(updateActions).isEmpty(); + } - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + @Test + void buildCustomUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); - final List> updateActions = - buildCustomUpdateActions(product, productDraft,1, oldAsset, newAssetDraft, SYNC_OPTIONS); + final List> updateActions = + buildCustomUpdateActions(product, productDraft, 1, oldAsset, newAssetDraft, SYNC_OPTIONS); - assertThat(updateActions).hasSize(2); - } + assertThat(updateActions).hasSize(2); + } - @Test - void buildCustomUpdateActions_WithNullOldStagedValues_ShouldBuildUpdateAction() { - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + @Test + void buildCustomUpdateActions_WithNullOldStagedValues_ShouldBuildUpdateAction() { + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(null); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(null); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); + final List> updateActions = + buildCustomUpdateActions(product, productDraft, 1, oldAsset, newAssetDraft, SYNC_OPTIONS); - final List> updateActions = - buildCustomUpdateActions(product, productDraft,1, oldAsset, newAssetDraft, SYNC_OPTIONS); + assertThat(updateActions) + .containsExactly( + SetAssetCustomType.ofVariantIdAndAssetKey( + 1, newAssetDraft.getKey(), newCustomFieldsDraft, true)); + } - assertThat(updateActions).containsExactly( - SetAssetCustomType.ofVariantIdAndAssetKey(1, newAssetDraft.getKey(), newCustomFieldsDraft, true) - ); - } + @Test + void + buildCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerErrorCallback() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - @Test - void buildCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerErrorCallback() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); + final List errors = new ArrayList<>(); - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final ProductSyncOptions syncOptions = - ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errors.add(exception.getMessage())) + .build(); + + final List> updateActions = + buildCustomUpdateActions(product, productDraft, 1, oldAsset, newAssetDraft, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + format( + "Failed to build custom fields update actions on the asset with id '%s'." + + " Reason: Custom type ids are not set for both the old and new asset.", + oldAsset.getId())); + } + + @Test + void buildCustomUpdateActions_WithNullValue_ShouldCorrectlyBuildAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "setOfBooleans", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + newCustomFieldsMap.put("setOfBooleans", null); + + final CustomFields oldCustomFields = mock(CustomFields.class); + final String typeId = UUID.randomUUID().toString(); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); + + final List errors = new ArrayList<>(); + + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> + errors.add(exception.getMessage())) + .build(); + // test + final List> updateActions = + buildCustomUpdateActions(product, productDraft, 1, oldAsset, newAssetDraft, syncOptions); + + // assertion + assertThat(errors).isEmpty(); + assertThat(updateActions) + .containsExactly( + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, oldAsset.getKey(), "setOfBooleans", null, true)); + } + + @Test + void buildCustomUpdateActions_WithNullJsonNodeValue_ShouldCorrectlyBuildAction() { + // preparation + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put( + "setOfBooleans", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); + + final CustomFields oldCustomFields = mock(CustomFields.class); + final String typeId = UUID.randomUUID().toString(); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); + + final Asset oldAsset = mock(Asset.class); + when(oldAsset.getCustom()).thenReturn(oldCustomFields); + + final AssetDraft newAssetDraft = + AssetDraftBuilder.of(emptyList(), empty()).custom(newCustomFieldsDraft).build(); + + final List errors = new ArrayList<>(); + + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> errors.add(exception.getMessage())) - .build(); - - final List> updateActions = - buildCustomUpdateActions(product, productDraft,1, oldAsset, newAssetDraft, syncOptions); - - assertThat(updateActions).isEmpty(); - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo(format("Failed to build custom fields update actions on the asset with id '%s'." - + " Reason: Custom type ids are not set for both the old and new asset.", oldAsset.getId())); - } - - @Test - void buildCustomUpdateActions_WithNullValue_ShouldCorrectlyBuildAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - newCustomFieldsMap.put("setOfBooleans", null); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - final String typeId = UUID.randomUUID().toString(); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> - errors.add(exception.getMessage())) - .build(); - // test - final List> updateActions = - buildCustomUpdateActions(product, productDraft,1, oldAsset, newAssetDraft, syncOptions); - - // assertion - assertThat(errors).isEmpty(); - assertThat(updateActions) - .containsExactly(SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, oldAsset.getKey(), "setOfBooleans", null, true)); - } - - @Test - void buildCustomUpdateActions_WithNullJsonNodeValue_ShouldCorrectlyBuildAction() { - // preparation - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("setOfBooleans", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("setOfBooleans", JsonNodeFactory.instance.nullNode()); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - final String typeId = UUID.randomUUID().toString(); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); - - final Asset oldAsset = mock(Asset.class); - when(oldAsset.getCustom()).thenReturn(oldCustomFields); - - final AssetDraft newAssetDraft = AssetDraftBuilder.of(emptyList(), empty()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final ProductSyncOptions syncOptions = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> - errors.add(exception.getMessage())) - .build(); - // test - final List> updateActions = - buildCustomUpdateActions(product, productDraft,1, oldAsset, newAssetDraft, syncOptions); - - // assertion - assertThat(errors).isEmpty(); - assertThat(updateActions) - .containsExactly(SetAssetCustomField - .ofVariantIdUsingJsonAndAssetKey(1, oldAsset.getKey(), "setOfBooleans", - null, true)); - } + .build(); + // test + final List> updateActions = + buildCustomUpdateActions(product, productDraft, 1, oldAsset, newAssetDraft, syncOptions); + + // assertion + assertThat(errors).isEmpty(); + assertThat(updateActions) + .containsExactly( + SetAssetCustomField.ofVariantIdUsingJsonAndAssetKey( + 1, oldAsset.getKey(), "setOfBooleans", null, true)); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/ProductVariantPriceUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/products/utils/ProductVariantPriceUpdateActionUtilsTest.java index 1ccf05b809..2799470d7b 100644 --- a/src/test/java/com/commercetools/sync/products/utils/ProductVariantPriceUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/ProductVariantPriceUpdateActionUtilsTest.java @@ -1,5 +1,20 @@ package com.commercetools.sync.products.utils; +import static com.commercetools.sync.products.utils.ProductVariantPriceUpdateActionUtils.buildActions; +import static com.commercetools.sync.products.utils.ProductVariantPriceUpdateActionUtils.buildChangePriceUpdateAction; +import static com.commercetools.sync.products.utils.ProductVariantPriceUpdateActionUtils.buildCustomUpdateActions; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceFixtures.DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY; +import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -21,14 +36,6 @@ import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; import io.sphere.sdk.utils.MoneyImpl; -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 javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.money.MonetaryAmount; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; @@ -36,338 +43,399 @@ import java.util.Map; import java.util.UUID; import java.util.stream.Stream; - -import static com.commercetools.sync.products.utils.ProductVariantPriceUpdateActionUtils.buildActions; -import static com.commercetools.sync.products.utils.ProductVariantPriceUpdateActionUtils.buildChangePriceUpdateAction; -import static com.commercetools.sync.products.utils.ProductVariantPriceUpdateActionUtils.buildCustomUpdateActions; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceFixtures.DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY; -import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.money.MonetaryAmount; +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; class ProductVariantPriceUpdateActionUtilsTest { - private static final ProductSyncOptions SYNC_OPTIONS = ProductSyncOptionsBuilder - .of(mock(SphereClient.class)).build(); - final Product mainProduct = mock(Product.class); - final ProductDraft mainProductDraft = mock(ProductDraft.class); - - private static final MonetaryAmount EUR_10 = MoneyImpl.of(BigDecimal.TEN, EUR); - private static final MonetaryAmount EUR_20 = MoneyImpl.of(BigDecimal.valueOf(20), EUR); - private static final PriceTier TIER_1_EUR_10 = PriceTierBuilder.of(1, EUR_10).build(); - private static final PriceTier TIER_2_EUR_10 = PriceTierBuilder.of(2, EUR_10).build(); - private static final PriceTier TIER_1_EUR_20 = PriceTierBuilder.of(1, EUR_20).build(); - - private static final Price PRICE_EUR_10_TIER_1_EUR_10 = PriceBuilder.of(EUR_10) - .id(UUID.randomUUID().toString()) - .tiers(singletonList(TIER_1_EUR_10)) - .build(); - - private static final PriceDraft DRAFT_EUR_10_TIER_1_EUR_10 = PriceDraftBuilder.of(EUR_10) - .tiers(singletonList(TIER_1_EUR_10)) - .build(); - - private static final PriceDraft DRAFT_EUR_20_TIER_1_EUR_10 = PriceDraftBuilder.of(EUR_20) - .tiers(singletonList(TIER_1_EUR_10)) - .build(); - - private static final PriceDraft DRAFT_EUR_10_TIER_1_EUR_20 = PriceDraftBuilder.of(EUR_10) - .tiers(singletonList(TIER_1_EUR_20)) - .build(); - - private static final PriceDraft DRAFT_EUR_10_TIER_2_EUR_10 = PriceDraftBuilder.of(EUR_10) - .tiers(singletonList(TIER_2_EUR_10)) - .build(); - - private static final PriceDraft DRAFT_EUR_10_MULTIPLE_TIERS = PriceDraftBuilder.of(EUR_10) - .tiers(asList(TIER_2_EUR_10, - TIER_1_EUR_10)) - .build(); - - private static final PriceDraft DRAFT_NULL_VALUE = PriceDraftBuilder.of((MonetaryAmount) null).build(); - - private static final Price PRICE_EUR_10_NULL_TIERS = PriceBuilder.of(EUR_10) - .id(UUID.randomUUID().toString()) - .tiers(null) - .build(); - - private static final PriceDraft DRAFT_EUR_10_NULL_TIERS = PriceDraftBuilder.of(EUR_10) - .tiers(null) - .build(); - - private static final Price PRICE_EUR_10_EMPTY_TIERS = PriceBuilder.of(EUR_10) - .id(UUID.randomUUID().toString()) - .tiers(emptyList()) - .build(); - - private static final PriceDraft DRAFT_EUR_10_EMPTY_TIERS = PriceDraftBuilder.of(EUR_10) - .tiers(emptyList()) - .build(); - - - @ParameterizedTest(name = "[#buildActions]: {0}") - @MethodSource("buildActionsTestCases") - void buildActionsTest(@Nonnull final String testCaseName, - @Nonnull final Price oldPrice, - @Nonnull final PriceDraft newPrice, - @Nonnull final List> expectedResult, - @Nonnull final List expectedWarnings) { - // preparation - final List warnings = new ArrayList<>(); - final ProductSyncOptions syncOptions = - ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .warningCallback((exception, oldResource, newResource) -> - warnings.add(exception.getMessage())) - .build(); - - // test - final List> result = buildActions(mainProduct, mainProductDraft,0, oldPrice, - newPrice, syncOptions); - - // assertion - assertEquals(expectedResult, result); - assertEquals(expectedWarnings, warnings); - } - - private static Stream buildActionsTestCases() { - final String case1 = "identical values and null tiers"; - final String case2 = "identical values and empty tiers"; - final String case3 = "identical values and identical tiers"; - final String case4 = "different values and identical tiers"; - final String case5 = "identical values and different tiers [different in value]"; - final String case6 = "identical values and different tiers [different in minimumQuantity]"; - final String case7 = "identical values and different tiers [different in number of tiers]"; - final String case8 = "different values and different custom fields"; - final String case9 = "different values (with a null new value)"; - - return Stream.of( - Arguments.of(case1, PRICE_EUR_10_NULL_TIERS, DRAFT_EUR_10_NULL_TIERS, emptyList(), emptyList()), - Arguments.of(case2, PRICE_EUR_10_EMPTY_TIERS, DRAFT_EUR_10_EMPTY_TIERS, emptyList(), emptyList()), - Arguments.of(case3, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_1_EUR_10, emptyList(), emptyList()), - Arguments.of(case4, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_20_TIER_1_EUR_10, - singletonList(ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_20_TIER_1_EUR_10, true)), - emptyList()), - Arguments.of(case5, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_1_EUR_20, - singletonList(ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_1_EUR_20, true)), - emptyList()), - Arguments.of(case6, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_2_EUR_10, - singletonList(ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_2_EUR_10, true)), - emptyList()), - Arguments.of(case7, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_MULTIPLE_TIERS, - singletonList(ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_MULTIPLE_TIERS, true)), - emptyList()), - Arguments.of(case8, DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, - DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, - asList(ChangePrice.of(DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, - DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, true), - SetProductPriceCustomField.ofJson("foo", - DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getFields().get("foo"), - DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY.getId(), true)), emptyList()), - Arguments.of(case9, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_NULL_VALUE, emptyList(), singletonList( - format("Cannot unset 'value' field of price with id '%s'.", PRICE_EUR_10_TIER_1_EUR_10.getId()))) - ); - } - - @ParameterizedTest(name = "[#buildChangePrice]: {0}") - @MethodSource("buildChangePriceTestCases") - void buildChangePriceUpdateActionTest(@Nonnull final String testCaseName, - @Nonnull final Price oldPrice, - @Nonnull final PriceDraft newPrice, - @Nullable final UpdateAction expectedResult, - @Nonnull final List expectedWarnings) { - // preparation - final List warnings = new ArrayList<>(); - final ProductSyncOptions syncOptions = - ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .warningCallback((exception, oldResource, newResource) -> - warnings.add(exception.getMessage())) - .build(); - - // test - final ChangePrice result = buildChangePriceUpdateAction(oldPrice, newPrice, syncOptions).orElse(null); - - // assertion - assertEquals(expectedResult, result); - assertEquals(expectedWarnings, warnings); - } - - private static Stream buildChangePriceTestCases() { - final String case1 = "identical values and null tiers"; - final String case2 = "identical values and empty tiers"; - final String case3 = "identical values and identical tiers"; - final String case4 = "different values and identical tiers"; - final String case5 = "identical values and different tiers [different in value]"; - final String case6 = "identical values and different tiers [different in minimumQuantity]"; - final String case7 = "identical values and different tiers [different in number of tiers]"; - final String case8 = "different values (with a null new value)"; - - - return Stream.of( - Arguments.of(case1, PRICE_EUR_10_NULL_TIERS, DRAFT_EUR_10_NULL_TIERS, null, emptyList()), - Arguments.of(case2, PRICE_EUR_10_EMPTY_TIERS, DRAFT_EUR_10_EMPTY_TIERS, null, emptyList()), - Arguments.of(case3, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_1_EUR_10, null, emptyList()), - Arguments.of(case4, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_20_TIER_1_EUR_10, - ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_20_TIER_1_EUR_10, true), emptyList()), - Arguments.of(case5, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_1_EUR_20, - ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_1_EUR_20, true), emptyList()), - Arguments.of(case6, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_2_EUR_10, - ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_2_EUR_10, true), emptyList()), - Arguments.of(case7, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_MULTIPLE_TIERS, - ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_MULTIPLE_TIERS, true), emptyList()), - Arguments.of(case8, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_NULL_VALUE, null, singletonList( - format("Cannot unset 'value' field of price with id '%s'.", PRICE_EUR_10_TIER_1_EUR_10.getId()))) - ); - } - - - - @Test - void buildCustomUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); - - final Price oldPrice = PriceBuilder.of(EUR_10) - .id(UUID.randomUUID().toString()) - .tiers(singletonList(TIER_1_EUR_10)) - .custom(oldCustomFields) - .build(); - - final PriceDraft newPrice = PriceDraftBuilder.of(EUR_10) - .tiers(singletonList(TIER_1_EUR_10)) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainProduct, mainProductDraft, 1, oldPrice, newPrice, SYNC_OPTIONS); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildCustomUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - - final Price oldPrice = PriceBuilder.of(EUR_10) - .id(UUID.randomUUID().toString()) - .tiers(singletonList(TIER_1_EUR_10)) - .custom(oldCustomFields) - .build(); - - final PriceDraft newPrice = PriceDraftBuilder.of(EUR_10) - .tiers(singletonList(TIER_1_EUR_10)) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainProduct, mainProductDraft,1, oldPrice, newPrice, SYNC_OPTIONS); - - assertThat(updateActions).hasSize(2); - } - - @Test - void buildCustomUpdateActions_WithNullOldStagedValues_ShouldBuildUpdateAction() { - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - - final Price oldPrice = PriceBuilder.of(EUR_10) - .id(UUID.randomUUID().toString()) - .tiers(singletonList(TIER_1_EUR_10)) - .custom(null) - .build(); - - final PriceDraft newPrice = PriceDraftBuilder.of(EUR_10) - .tiers(singletonList(TIER_1_EUR_10)) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainProduct, mainProductDraft,1, oldPrice, newPrice, SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( - SetProductPriceCustomType.ofTypeIdAndJson("1", newCustomFieldsMap, oldPrice.getId(), true) - ); - } - - @Test - void buildCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerErrorCallback() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); - - final Price oldPrice = PriceBuilder.of(EUR_10) - .id(UUID.randomUUID().toString()) - .tiers(singletonList(TIER_1_EUR_10)) - .custom(oldCustomFields) - .build(); - - final PriceDraft newPrice = PriceDraftBuilder.of(EUR_10) - .tiers(singletonList(TIER_1_EUR_10)) - .custom(newCustomFieldsDraft) - .build(); - - - final List errors = new ArrayList<>(); - - final ProductSyncOptions syncOptions = - ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> + private static final ProductSyncOptions SYNC_OPTIONS = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + final Product mainProduct = mock(Product.class); + final ProductDraft mainProductDraft = mock(ProductDraft.class); + + private static final MonetaryAmount EUR_10 = MoneyImpl.of(BigDecimal.TEN, EUR); + private static final MonetaryAmount EUR_20 = MoneyImpl.of(BigDecimal.valueOf(20), EUR); + private static final PriceTier TIER_1_EUR_10 = PriceTierBuilder.of(1, EUR_10).build(); + private static final PriceTier TIER_2_EUR_10 = PriceTierBuilder.of(2, EUR_10).build(); + private static final PriceTier TIER_1_EUR_20 = PriceTierBuilder.of(1, EUR_20).build(); + + private static final Price PRICE_EUR_10_TIER_1_EUR_10 = + PriceBuilder.of(EUR_10) + .id(UUID.randomUUID().toString()) + .tiers(singletonList(TIER_1_EUR_10)) + .build(); + + private static final PriceDraft DRAFT_EUR_10_TIER_1_EUR_10 = + PriceDraftBuilder.of(EUR_10).tiers(singletonList(TIER_1_EUR_10)).build(); + + private static final PriceDraft DRAFT_EUR_20_TIER_1_EUR_10 = + PriceDraftBuilder.of(EUR_20).tiers(singletonList(TIER_1_EUR_10)).build(); + + private static final PriceDraft DRAFT_EUR_10_TIER_1_EUR_20 = + PriceDraftBuilder.of(EUR_10).tiers(singletonList(TIER_1_EUR_20)).build(); + + private static final PriceDraft DRAFT_EUR_10_TIER_2_EUR_10 = + PriceDraftBuilder.of(EUR_10).tiers(singletonList(TIER_2_EUR_10)).build(); + + private static final PriceDraft DRAFT_EUR_10_MULTIPLE_TIERS = + PriceDraftBuilder.of(EUR_10).tiers(asList(TIER_2_EUR_10, TIER_1_EUR_10)).build(); + + private static final PriceDraft DRAFT_NULL_VALUE = + PriceDraftBuilder.of((MonetaryAmount) null).build(); + + private static final Price PRICE_EUR_10_NULL_TIERS = + PriceBuilder.of(EUR_10).id(UUID.randomUUID().toString()).tiers(null).build(); + + private static final PriceDraft DRAFT_EUR_10_NULL_TIERS = + PriceDraftBuilder.of(EUR_10).tiers(null).build(); + + private static final Price PRICE_EUR_10_EMPTY_TIERS = + PriceBuilder.of(EUR_10).id(UUID.randomUUID().toString()).tiers(emptyList()).build(); + + private static final PriceDraft DRAFT_EUR_10_EMPTY_TIERS = + PriceDraftBuilder.of(EUR_10).tiers(emptyList()).build(); + + @ParameterizedTest(name = "[#buildActions]: {0}") + @MethodSource("buildActionsTestCases") + void buildActionsTest( + @Nonnull final String testCaseName, + @Nonnull final Price oldPrice, + @Nonnull final PriceDraft newPrice, + @Nonnull final List> expectedResult, + @Nonnull final List expectedWarnings) { + // preparation + final List warnings = new ArrayList<>(); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .warningCallback( + (exception, oldResource, newResource) -> warnings.add(exception.getMessage())) + .build(); + + // test + final List> result = + buildActions(mainProduct, mainProductDraft, 0, oldPrice, newPrice, syncOptions); + + // assertion + assertEquals(expectedResult, result); + assertEquals(expectedWarnings, warnings); + } + + private static Stream buildActionsTestCases() { + final String case1 = "identical values and null tiers"; + final String case2 = "identical values and empty tiers"; + final String case3 = "identical values and identical tiers"; + final String case4 = "different values and identical tiers"; + final String case5 = "identical values and different tiers [different in value]"; + final String case6 = "identical values and different tiers [different in minimumQuantity]"; + final String case7 = "identical values and different tiers [different in number of tiers]"; + final String case8 = "different values and different custom fields"; + final String case9 = "different values (with a null new value)"; + + return Stream.of( + Arguments.of( + case1, PRICE_EUR_10_NULL_TIERS, DRAFT_EUR_10_NULL_TIERS, emptyList(), emptyList()), + Arguments.of( + case2, PRICE_EUR_10_EMPTY_TIERS, DRAFT_EUR_10_EMPTY_TIERS, emptyList(), emptyList()), + Arguments.of( + case3, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_EUR_10_TIER_1_EUR_10, + emptyList(), + emptyList()), + Arguments.of( + case4, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_EUR_20_TIER_1_EUR_10, + singletonList( + ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_20_TIER_1_EUR_10, true)), + emptyList()), + Arguments.of( + case5, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_EUR_10_TIER_1_EUR_20, + singletonList( + ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_1_EUR_20, true)), + emptyList()), + Arguments.of( + case6, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_EUR_10_TIER_2_EUR_10, + singletonList( + ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_2_EUR_10, true)), + emptyList()), + Arguments.of( + case7, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_EUR_10_MULTIPLE_TIERS, + singletonList( + ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_MULTIPLE_TIERS, true)), + emptyList()), + Arguments.of( + case8, + DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, + DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, + asList( + ChangePrice.of( + DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, + DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, + true), + SetProductPriceCustomField.ofJson( + "foo", + DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX + .getCustom() + .getFields() + .get("foo"), + DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY.getId(), + true)), + emptyList()), + Arguments.of( + case9, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_NULL_VALUE, + emptyList(), + singletonList( + format( + "Cannot unset 'value' field of price with id '%s'.", + PRICE_EUR_10_TIER_1_EUR_10.getId())))); + } + + @ParameterizedTest(name = "[#buildChangePrice]: {0}") + @MethodSource("buildChangePriceTestCases") + void buildChangePriceUpdateActionTest( + @Nonnull final String testCaseName, + @Nonnull final Price oldPrice, + @Nonnull final PriceDraft newPrice, + @Nullable final UpdateAction expectedResult, + @Nonnull final List expectedWarnings) { + // preparation + final List warnings = new ArrayList<>(); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .warningCallback( + (exception, oldResource, newResource) -> warnings.add(exception.getMessage())) + .build(); + + // test + final ChangePrice result = + buildChangePriceUpdateAction(oldPrice, newPrice, syncOptions).orElse(null); + + // assertion + assertEquals(expectedResult, result); + assertEquals(expectedWarnings, warnings); + } + + private static Stream buildChangePriceTestCases() { + final String case1 = "identical values and null tiers"; + final String case2 = "identical values and empty tiers"; + final String case3 = "identical values and identical tiers"; + final String case4 = "different values and identical tiers"; + final String case5 = "identical values and different tiers [different in value]"; + final String case6 = "identical values and different tiers [different in minimumQuantity]"; + final String case7 = "identical values and different tiers [different in number of tiers]"; + final String case8 = "different values (with a null new value)"; + + return Stream.of( + Arguments.of(case1, PRICE_EUR_10_NULL_TIERS, DRAFT_EUR_10_NULL_TIERS, null, emptyList()), + Arguments.of(case2, PRICE_EUR_10_EMPTY_TIERS, DRAFT_EUR_10_EMPTY_TIERS, null, emptyList()), + Arguments.of( + case3, PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_1_EUR_10, null, emptyList()), + Arguments.of( + case4, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_EUR_20_TIER_1_EUR_10, + ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_20_TIER_1_EUR_10, true), + emptyList()), + Arguments.of( + case5, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_EUR_10_TIER_1_EUR_20, + ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_1_EUR_20, true), + emptyList()), + Arguments.of( + case6, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_EUR_10_TIER_2_EUR_10, + ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_TIER_2_EUR_10, true), + emptyList()), + Arguments.of( + case7, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_EUR_10_MULTIPLE_TIERS, + ChangePrice.of(PRICE_EUR_10_TIER_1_EUR_10, DRAFT_EUR_10_MULTIPLE_TIERS, true), + emptyList()), + Arguments.of( + case8, + PRICE_EUR_10_TIER_1_EUR_10, + DRAFT_NULL_VALUE, + null, + singletonList( + format( + "Cannot unset 'value' field of price with id '%s'.", + PRICE_EUR_10_TIER_1_EUR_10.getId())))); + } + + @Test + void buildCustomUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); + + final Price oldPrice = + PriceBuilder.of(EUR_10) + .id(UUID.randomUUID().toString()) + .tiers(singletonList(TIER_1_EUR_10)) + .custom(oldCustomFields) + .build(); + + final PriceDraft newPrice = + PriceDraftBuilder.of(EUR_10) + .tiers(singletonList(TIER_1_EUR_10)) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildCustomUpdateActions( + mainProduct, mainProductDraft, 1, oldPrice, newPrice, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildCustomUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + + final Price oldPrice = + PriceBuilder.of(EUR_10) + .id(UUID.randomUUID().toString()) + .tiers(singletonList(TIER_1_EUR_10)) + .custom(oldCustomFields) + .build(); + + final PriceDraft newPrice = + PriceDraftBuilder.of(EUR_10) + .tiers(singletonList(TIER_1_EUR_10)) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildCustomUpdateActions( + mainProduct, mainProductDraft, 1, oldPrice, newPrice, SYNC_OPTIONS); + + assertThat(updateActions).hasSize(2); + } + + @Test + void buildCustomUpdateActions_WithNullOldStagedValues_ShouldBuildUpdateAction() { + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + + final Price oldPrice = + PriceBuilder.of(EUR_10) + .id(UUID.randomUUID().toString()) + .tiers(singletonList(TIER_1_EUR_10)) + .custom(null) + .build(); + + final PriceDraft newPrice = + PriceDraftBuilder.of(EUR_10) + .tiers(singletonList(TIER_1_EUR_10)) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildCustomUpdateActions( + mainProduct, mainProductDraft, 1, oldPrice, newPrice, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + SetProductPriceCustomType.ofTypeIdAndJson( + "1", newCustomFieldsMap, oldPrice.getId(), true)); + } + + @Test + void + buildCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerErrorCallback() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("de", "rot")); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("invisibleInShop", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put( + "backgroundColor", JsonNodeFactory.instance.objectNode().put("es", "rojo")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); + + final Price oldPrice = + PriceBuilder.of(EUR_10) + .id(UUID.randomUUID().toString()) + .tiers(singletonList(TIER_1_EUR_10)) + .custom(oldCustomFields) + .build(); + + final PriceDraft newPrice = + PriceDraftBuilder.of(EUR_10) + .tiers(singletonList(TIER_1_EUR_10)) + .custom(newCustomFieldsDraft) + .build(); + + final List errors = new ArrayList<>(); + + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> errors.add(exception.getMessage())) - .build(); - - final List> updateActions = - buildCustomUpdateActions(mainProduct, mainProductDraft,1, oldPrice, newPrice, syncOptions); - - - assertThat(updateActions).isEmpty(); - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo(format("Failed to build custom fields update actions on the product-price with id '%s'." - + " Reason: Custom type ids are not set for both the old and new product-price.", oldPrice.getId())); - } + .build(); + + final List> updateActions = + buildCustomUpdateActions(mainProduct, mainProductDraft, 1, oldPrice, newPrice, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + format( + "Failed to build custom fields update actions on the product-price with id '%s'." + + " Reason: Custom type ids are not set for both the old and new product-price.", + oldPrice.getId())); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/VariantReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/products/utils/VariantReferenceResolutionUtilsTest.java index aff924a8ee..f50117495b 100644 --- a/src/test/java/com/commercetools/sync/products/utils/VariantReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/VariantReferenceResolutionUtilsTest.java @@ -1,30 +1,5 @@ package com.commercetools.sync.products.utils; -import com.fasterxml.jackson.databind.JsonNode; -import io.sphere.sdk.channels.Channel; -import io.sphere.sdk.customergroups.CustomerGroup; -import io.sphere.sdk.models.Asset; -import io.sphere.sdk.models.Reference; -import io.sphere.sdk.models.ResourceIdentifier; -import io.sphere.sdk.products.Price; -import io.sphere.sdk.products.PriceDraft; -import io.sphere.sdk.products.Product; -import io.sphere.sdk.products.ProductVariant; -import io.sphere.sdk.products.ProductVariantDraft; -import io.sphere.sdk.products.attributes.Attribute; -import io.sphere.sdk.products.attributes.AttributeAccess; -import io.sphere.sdk.products.attributes.AttributeDraft; -import io.sphere.sdk.types.CustomFieldsDraft; -import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; - import static com.commercetools.sync.commons.MockUtils.getAssetMockWithCustomFields; import static com.commercetools.sync.commons.MockUtils.getTypeMock; import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; @@ -61,419 +36,483 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class VariantReferenceResolutionUtilsTest { - - @Test - void mapToProductVariantDrafts_WithExpandedReferences_ShouldReturnVariantDraftsWithReplacedKeys() { - final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - - final String channelKey = "channelKey"; - final Channel channel = getChannelMock(channelKey); - - final Reference channelReference = Reference - .ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel.getId(), channel); - - final Reference priceCustomTypeReference = - Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType); - - final CustomerGroup customerGroup = getMockCustomerGroup("customer-group-id", "customer-group-key"); - final Reference customerGroupReference = - Reference.ofResourceTypeIdAndObj(CustomerGroup.referenceTypeId(), customerGroup); - - final Price price = getPriceMockWithReferences(channelReference, priceCustomTypeReference, - customerGroupReference); - - final Asset asset = - getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType)); - - final ProductVariant productVariant = getProductVariantMock(singletonList(price), singletonList(asset)); - - final List variantDrafts = mapToProductVariantDrafts( - singletonList(productVariant)); - - assertThat(variantDrafts).hasSize(1); - assertThat(variantDrafts.get(0).getPrices()).hasSize(1); - final ResourceIdentifier channelReferenceAfterReplacement = - variantDrafts.get(0).getPrices().get(0).getChannel(); - assertThat(channelReferenceAfterReplacement).isNotNull(); - assertThat(channelReferenceAfterReplacement.getKey()).isEqualTo(channelKey); - - final CustomFieldsDraft priceCustomAfterReplacement = variantDrafts.get(0).getPrices().get(0).getCustom(); - assertThat(priceCustomAfterReplacement).isNotNull(); - final ResourceIdentifier priceCustomTypeAfterReplacement = priceCustomAfterReplacement.getType(); - assertThat(priceCustomTypeAfterReplacement).isNotNull(); - assertThat(priceCustomTypeAfterReplacement.getKey()).isEqualTo(customType.getKey()); - - assertThat(variantDrafts.get(0).getAssets()).hasSize(1); - final ResourceIdentifier referenceReplacedType = - variantDrafts.get(0).getAssets().get(0).getCustom().getType(); - assertThat(referenceReplacedType).isNotNull(); - assertThat(referenceReplacedType.getKey()).isEqualTo(customType.getKey()); - } - - @Test - void mapToProductVariantDrafts_WithSomeExpandedReferences_ShouldReplaceSomeKeys() { - final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - - final Reference priceCustomTypeReference1 = - Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType); - final Reference priceCustomTypeReference2 = Type.referenceOfId(UUID.randomUUID().toString()); - - final String channelKey1 = "channelKey1"; - final Channel channel1 = getChannelMock(channelKey1); - - final Reference channelReference1 = Reference - .ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel1.getId(), channel1); - final Reference channelReference2 = Channel.referenceOfId(UUID.randomUUID().toString()); - - final Price price1 = getPriceMockWithReferences(channelReference1, priceCustomTypeReference1, null); - final Price price2 = getPriceMockWithReferences(channelReference2, priceCustomTypeReference2, null); - - final Asset asset1 = getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), - customType)); - final Asset asset2 = getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), - UUID.randomUUID().toString())); - - final ProductVariant productVariant1 = getProductVariantMock(singletonList(price1), singletonList(asset1)); - final ProductVariant productVariant2 = getProductVariantMock(singletonList(price2), singletonList(asset2)); - - final Product product = readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - final String uuid = UUID.randomUUID().toString(); - final Reference expandedReference = - Reference.ofResourceTypeIdAndIdAndObj(Product.referenceTypeId(), uuid, product); - final Attribute expandedProductRefAttribute = - Attribute.of("attrName", AttributeAccess.ofProductReference(), expandedReference); - - when(productVariant2.getAttributes()).thenReturn(singletonList(expandedProductRefAttribute)); - - final Reference nonExpandedReference = Product.referenceOfId(uuid); - final Attribute nonExpandedProductRefAttribute = - Attribute.of("attrName", AttributeAccess.ofProductReference(), nonExpandedReference); - when(productVariant1.getAttributes()).thenReturn(singletonList(nonExpandedProductRefAttribute)); - - - final List variantDrafts = mapToProductVariantDrafts( - asList(productVariant1, productVariant2)); - - assertThat(variantDrafts).hasSize(2); - assertThat(variantDrafts.get(0).getPrices()).hasSize(1); - - final ResourceIdentifier channel1ReferenceAfterReplacement = - variantDrafts.get(0).getPrices().get(0).getChannel(); - assertThat(channel1ReferenceAfterReplacement).isNotNull(); - assertThat(channel1ReferenceAfterReplacement.getKey()).isEqualTo(channelKey1); - - final CustomFieldsDraft price1CustomFieldsAfterReplacement = - variantDrafts.get(0).getPrices().get(0).getCustom(); - assertThat(price1CustomFieldsAfterReplacement).isNotNull(); - final ResourceIdentifier priceCustomType1ReferenceAfterReplacement = - price1CustomFieldsAfterReplacement.getType(); - assertThat(priceCustomType1ReferenceAfterReplacement).isNotNull(); - assertThat(priceCustomType1ReferenceAfterReplacement.getKey()).isEqualTo(customType.getKey()); - - assertThat(variantDrafts.get(0).getAssets()).hasSize(1); - final ResourceIdentifier asset1CustomType = variantDrafts.get(0).getAssets().get(0).getCustom().getType(); - assertThat(asset1CustomType).isNotNull(); - assertThat(asset1CustomType.getKey()).isEqualTo(customType.getKey()); - - assertThat(variantDrafts.get(1).getPrices()).hasSize(1); - - final ResourceIdentifier channel2ReferenceAfterReplacement = - variantDrafts.get(1).getPrices().get(0).getChannel(); - assertThat(channel2ReferenceAfterReplacement).isNotNull(); - // Asset price channel reference id is not replaced. - assertThat(channel2ReferenceAfterReplacement.getId()).isEqualTo(channelReference2.getId()); - - final CustomFieldsDraft price2CustomFieldsAfterReplacement = - variantDrafts.get(1).getPrices().get(0).getCustom(); - assertThat(price2CustomFieldsAfterReplacement).isNotNull(); - final ResourceIdentifier priceCustomType2ReferenceAfterReplacement = - price2CustomFieldsAfterReplacement.getType(); - assertThat(priceCustomType2ReferenceAfterReplacement).isNotNull(); - // Asset price custom type reference id is not replaced. - assertThat(priceCustomType2ReferenceAfterReplacement.getId()).isEqualTo(priceCustomTypeReference2.getId()); - - assertThat(variantDrafts.get(1).getAssets()).hasSize(1); - final ResourceIdentifier asset2CustomType = variantDrafts.get(1).getAssets().get(0).getCustom().getType(); - assertThat(asset2CustomType).isNotNull(); - // Asset price asset custom type reference id is not replaced. - assertThat(asset2CustomType.getId()).isEqualTo(asset2.getCustom().getType().getId()); - - final JsonNode productReference1Value = variantDrafts.get(0).getAttributes().get(0).getValue(); - assertThat(productReference1Value).isNotNull(); - assertThat(productReference1Value.get("id")).isNotNull(); - assertThat(productReference1Value.get("id").asText()).isEqualTo(uuid); - - - final JsonNode productReference2Value = variantDrafts.get(1).getAttributes().get(0).getValue(); - assertThat(productReference2Value).isNotNull(); - assertThat(productReference2Value.get("id")).isNotNull(); - assertThat(productReference2Value.get("id").asText()).isEqualTo("productKey1"); - } - - @Test - void mapToProductVariantDrafts_WithNoExpandedReferences_ShouldNotReplaceIds() { - final Reference channelReference = Channel.referenceOfId(UUID.randomUUID().toString()); - final Reference customTypeReference = Type.referenceOfId(UUID.randomUUID().toString()); - - final Asset asset2 = getAssetMockWithCustomFields(Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), - UUID.randomUUID().toString())); - - final Price price = getPriceMockWithReferences(channelReference, customTypeReference, null); - final ProductVariant productVariant = getProductVariantMock(singletonList(price), singletonList(asset2)); - - final List variantDrafts = - mapToProductVariantDrafts(singletonList(productVariant)); - - assertThat(variantDrafts).hasSize(1); - assertThat(variantDrafts.get(0).getPrices()).hasSize(1); - - final ResourceIdentifier channelReferenceAfterReplacement = - variantDrafts.get(0).getPrices().get(0).getChannel(); - assertThat(channelReferenceAfterReplacement).isNotNull(); - // Assert price channel reference id is not replaced. - assertThat(channelReferenceAfterReplacement.getId()).isEqualTo(channelReference.getId()); - - - final CustomFieldsDraft priceCustomFields = variantDrafts.get(0).getPrices().get(0).getCustom(); - assertThat(priceCustomFields).isNotNull(); - final ResourceIdentifier priceCustomTypeAfterReplacement = priceCustomFields.getType(); - // Assert price custom type reference id is not replaced. - assertThat(priceCustomTypeAfterReplacement.getId()).isEqualTo(customTypeReference.getId()); - - assertThat(variantDrafts.get(0).getAssets()).hasSize(1); - final ResourceIdentifier assetCustomTypeReference = variantDrafts.get(0).getAssets().get(0) - .getCustom().getType(); - assertThat(assetCustomTypeReference).isNotNull(); - // Assert asset custom type reference id is not replaced. - assertThat(assetCustomTypeReference.getId()).isEqualTo(variantDrafts.get(0).getAssets().get(0) - .getCustom().getType().getId()); - } - - @Test - void mapToPriceDraft_WithNoExpandedReferences_ShouldNotReplaceIds() { - final Reference channelReference = Channel.referenceOfId(UUID.randomUUID().toString()); - final Reference typeReference = Type.referenceOfId(UUID.randomUUID().toString()); - - final Price price = getPriceMockWithReferences(channelReference, typeReference, null); - final ProductVariant productVariant = getProductVariantMock(singletonList(price)); - - final List priceDrafts = mapToPriceDrafts(productVariant); - - assertThat(priceDrafts).hasSize(1); - final PriceDraft priceDraftAfterReplacement = priceDrafts.get(0); - - final ResourceIdentifier channelReferenceAfterReplacement = priceDraftAfterReplacement.getChannel(); - assertThat(channelReferenceAfterReplacement).isNotNull(); - // Assert Id is not replaced with key. - assertThat(channelReferenceAfterReplacement.getId()).isEqualTo(channelReference.getId()); - - final CustomFieldsDraft customAfterReplacement = priceDraftAfterReplacement.getCustom(); - assertThat(customAfterReplacement).isNotNull(); - final ResourceIdentifier customTypeAfterReplacement = customAfterReplacement.getType(); - - assertThat(customTypeAfterReplacement).isNotNull(); - // Assert Id is not replaced with key. - assertThat(customTypeAfterReplacement.getId()).isEqualTo(typeReference.getId()); - } - - @Test - void mapToPriceDraft_WithAllExpandedReferences_ShouldReplaceIds() { - final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - - final String channelKey1 = "channelKey1"; - final String channelKey2 = "channelKey2"; - - final Channel channel1 = getChannelMock(channelKey1); - final Channel channel2 = getChannelMock(channelKey2); - - final Reference channelReference1 = - Reference.ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel1.getId(), channel1); - final Reference channelReference2 = - Reference.ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel2.getId(), channel2); - final Reference customTypeReference = - Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType); - - final Price price1 = getPriceMockWithReferences(channelReference1, customTypeReference, null); - final Price price2 = getPriceMockWithReferences(channelReference2, customTypeReference, null); - - final ProductVariant productVariant = getProductVariantMock(asList(price1, price2)); - - final List priceDrafts = mapToPriceDrafts(productVariant); +import com.fasterxml.jackson.databind.JsonNode; +import io.sphere.sdk.channels.Channel; +import io.sphere.sdk.customergroups.CustomerGroup; +import io.sphere.sdk.models.Asset; +import io.sphere.sdk.models.Reference; +import io.sphere.sdk.models.ResourceIdentifier; +import io.sphere.sdk.products.Price; +import io.sphere.sdk.products.PriceDraft; +import io.sphere.sdk.products.Product; +import io.sphere.sdk.products.ProductVariant; +import io.sphere.sdk.products.ProductVariantDraft; +import io.sphere.sdk.products.attributes.Attribute; +import io.sphere.sdk.products.attributes.AttributeAccess; +import io.sphere.sdk.products.attributes.AttributeDraft; +import io.sphere.sdk.types.CustomFieldsDraft; +import io.sphere.sdk.types.Type; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import org.junit.jupiter.api.Test; - assertThat(priceDrafts).hasSize(2); +class VariantReferenceResolutionUtilsTest { - final PriceDraft priceDraft1AfterReplacement = priceDrafts.get(0); - final ResourceIdentifier channelReference1AfterReplacement = priceDraft1AfterReplacement.getChannel(); - assertThat(channelReference1AfterReplacement).isNotNull(); - assertThat(channelReference1AfterReplacement.getKey()).isEqualTo(channelKey1); - - final CustomFieldsDraft custom1AfterReplacement = priceDraft1AfterReplacement.getCustom(); - assertThat(custom1AfterReplacement).isNotNull(); - final ResourceIdentifier customType1AfterReplacement = custom1AfterReplacement.getType(); - assertThat(customType1AfterReplacement).isNotNull(); - assertThat(customType1AfterReplacement.getKey()).isEqualTo(customType.getKey()); - - - final PriceDraft priceDraft2AfterReplacement = priceDrafts.get(1); - final ResourceIdentifier channelReference2AfterReplacement = priceDraft2AfterReplacement.getChannel(); - assertThat(channelReference2AfterReplacement).isNotNull(); - assertThat(channelReference2AfterReplacement.getKey()).isEqualTo(channelKey2); - - final CustomFieldsDraft custom2AfterReplacement = priceDraft2AfterReplacement.getCustom(); - assertThat(custom2AfterReplacement).isNotNull(); - final ResourceIdentifier customType2AfterReplacement = custom2AfterReplacement.getType(); - assertThat(customType2AfterReplacement).isNotNull(); - assertThat(customType2AfterReplacement.getKey()).isEqualTo(customType.getKey()); - } - - @Test - void mapToPriceDraft_WithSomeExpandedReferences_ShouldReplaceOnlyExpandedIds() { - final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - final String channelKey1 = "channelKey1"; - - final Channel channel1 = getChannelMock(channelKey1); - - final Reference channelReference1 = - Reference.ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel1.getId(), channel1); - final Reference channelReference2 = Channel.referenceOfId(UUID.randomUUID().toString()); - - final Reference typeReference1 = Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType); - final Reference typeReference2 = Type.referenceOfId(UUID.randomUUID().toString()); - - final Price price1 = getPriceMockWithReferences(channelReference1, typeReference1, null); - final Price price2 = getPriceMockWithReferences(channelReference2, typeReference2, null); - - final ProductVariant productVariant = getProductVariantMock(asList(price1, price2)); - - final List priceDrafts = mapToPriceDrafts(productVariant); - - assertThat(priceDrafts).hasSize(2); - - final ResourceIdentifier channelReference1AfterReplacement = priceDrafts.get(0).getChannel(); - assertThat(channelReference1AfterReplacement).isNotNull(); - assertThat(channelReference1AfterReplacement.getKey()).isEqualTo(channelKey1); - - final ResourceIdentifier channelReference2AfterReplacement = priceDrafts.get(1).getChannel(); - assertThat(channelReference2AfterReplacement).isNotNull(); - // Assert non expanded reference has id not replaced. - assertThat(channelReference2AfterReplacement.getId()).isEqualTo(channelReference2.getId()); - - final CustomFieldsDraft customType1AfterReplacement = priceDrafts.get(0).getCustom(); - assertThat(customType1AfterReplacement).isNotNull(); - assertThat(customType1AfterReplacement.getType()).isNotNull(); - assertThat(customType1AfterReplacement.getType().getKey()).isEqualTo(customType.getKey()); - - final CustomFieldsDraft customType2AfterReplacement = priceDrafts.get(1).getCustom(); - assertThat(customType2AfterReplacement).isNotNull(); - assertThat(customType2AfterReplacement.getType()).isNotNull(); - // Assert expanded reference has id not replaced. - assertThat(customType2AfterReplacement.getType().getId()).isEqualTo(typeReference2.getId()); - } - - @Test - void replaceAttributeReferenceIdWithKey_WithTextAttribute_ShouldReturnEmptyOptional() { - final Attribute attribute = Attribute.of("attrName", AttributeAccess.ofText(), "value"); - final Optional> attributeReferenceIdWithKey = replaceAttributeReferenceIdWithKey(attribute); - - assertThat(attributeReferenceIdWithKey).isEmpty(); - } - - @Test - void replaceAttributeReferenceIdWithKey_WithProductReferenceSetAttribute_ShouldReturnEmptyOptional() { - final Attribute attribute = - Attribute.of("attrName", AttributeAccess.ofProductReferenceSet(), new HashSet<>()); - final Optional> attributeReferenceIdWithKey = replaceAttributeReferenceIdWithKey(attribute); - - assertThat(attributeReferenceIdWithKey).isEmpty(); - } - - @Test - void replaceAttributeReferenceIdWithKey_WithNonExpandedProductReferenceAttribute_ShouldNotReplaceId() { - final Reference nonExpandedReference = Product.referenceOfId(UUID.randomUUID().toString()); - final Attribute attribute = - Attribute.of("attrName", AttributeAccess.ofProductReference(), nonExpandedReference); - final Optional> attributeReferenceIdWithKey = replaceAttributeReferenceIdWithKey(attribute); - - assertThat(attributeReferenceIdWithKey).contains(nonExpandedReference); - } - - @Test - void replaceAttributeReferenceIdWithKey_WithExpandedProductReferenceAttribute_ShouldReplaceId() { - final Product product = readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - final Reference expandedReference = - Reference.ofResourceTypeIdAndIdAndObj(Product.referenceTypeId(), UUID.randomUUID().toString(), product); - final Attribute attribute = - Attribute.of("attrName", AttributeAccess.ofProductReference(), expandedReference); - final Optional> attributeReferenceIdWithKey = replaceAttributeReferenceIdWithKey(attribute); - assertThat(attributeReferenceIdWithKey).contains(Product.referenceOfId("productKey1")); - } - - @Test - void replaceAttributeReferenceSetIdsWithKeys_WithTextAttribute_ShouldReturnEmptyOptional() { - final Attribute attribute = Attribute.of("attrName", AttributeAccess.ofText(), "value"); - final Optional>> attributeReferenceSetIdsWithKeys = - replaceAttributeReferenceSetIdsWithKeys(attribute); - - assertThat(attributeReferenceSetIdsWithKeys).isEmpty(); - } - - @Test - void replaceAttributesReferencesIdsWithKeys_WithNoAttributes_ShouldNotReplaceIds() { - final ProductVariant variant = mock(ProductVariant.class); - when(variant.getAttributes()).thenReturn(new ArrayList<>()); - final List replacedDrafts = replaceAttributesReferencesIdsWithKeys(variant); - assertThat(replacedDrafts).isEmpty(); - } - - @Test - void replaceAttributesReferencesIdsWithKeys_WithAttributesWithNoReferences_ShouldNotChangeAttributes() { - final Product product = readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - final ProductVariant masterVariant = product.getMasterData().getStaged().getMasterVariant(); - final List replacedDrafts = replaceAttributesReferencesIdsWithKeys(masterVariant); - replacedDrafts.forEach(attributeDraft -> { - final String name = attributeDraft.getName(); - final Attribute originalAttribute = masterVariant.getAttribute(name); - assertThat(originalAttribute).isNotNull(); - assertThat(originalAttribute.getValueAsJsonNode()).isEqualTo(attributeDraft.getValue()); + @Test + void + mapToProductVariantDrafts_WithExpandedReferences_ShouldReturnVariantDraftsWithReplacedKeys() { + final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + + final String channelKey = "channelKey"; + final Channel channel = getChannelMock(channelKey); + + final Reference channelReference = + Reference.ofResourceTypeIdAndIdAndObj(Channel.referenceTypeId(), channel.getId(), channel); + + final Reference priceCustomTypeReference = + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType); + + final CustomerGroup customerGroup = + getMockCustomerGroup("customer-group-id", "customer-group-key"); + final Reference customerGroupReference = + Reference.ofResourceTypeIdAndObj(CustomerGroup.referenceTypeId(), customerGroup); + + final Price price = + getPriceMockWithReferences( + channelReference, priceCustomTypeReference, customerGroupReference); + + final Asset asset = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType)); + + final ProductVariant productVariant = + getProductVariantMock(singletonList(price), singletonList(asset)); + + final List variantDrafts = + mapToProductVariantDrafts(singletonList(productVariant)); + + assertThat(variantDrafts).hasSize(1); + assertThat(variantDrafts.get(0).getPrices()).hasSize(1); + final ResourceIdentifier channelReferenceAfterReplacement = + variantDrafts.get(0).getPrices().get(0).getChannel(); + assertThat(channelReferenceAfterReplacement).isNotNull(); + assertThat(channelReferenceAfterReplacement.getKey()).isEqualTo(channelKey); + + final CustomFieldsDraft priceCustomAfterReplacement = + variantDrafts.get(0).getPrices().get(0).getCustom(); + assertThat(priceCustomAfterReplacement).isNotNull(); + final ResourceIdentifier priceCustomTypeAfterReplacement = + priceCustomAfterReplacement.getType(); + assertThat(priceCustomTypeAfterReplacement).isNotNull(); + assertThat(priceCustomTypeAfterReplacement.getKey()).isEqualTo(customType.getKey()); + + assertThat(variantDrafts.get(0).getAssets()).hasSize(1); + final ResourceIdentifier referenceReplacedType = + variantDrafts.get(0).getAssets().get(0).getCustom().getType(); + assertThat(referenceReplacedType).isNotNull(); + assertThat(referenceReplacedType.getKey()).isEqualTo(customType.getKey()); + } + + @Test + void mapToProductVariantDrafts_WithSomeExpandedReferences_ShouldReplaceSomeKeys() { + final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + + final Reference priceCustomTypeReference1 = + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType); + final Reference priceCustomTypeReference2 = + Type.referenceOfId(UUID.randomUUID().toString()); + + final String channelKey1 = "channelKey1"; + final Channel channel1 = getChannelMock(channelKey1); + + final Reference channelReference1 = + Reference.ofResourceTypeIdAndIdAndObj( + Channel.referenceTypeId(), channel1.getId(), channel1); + final Reference channelReference2 = + Channel.referenceOfId(UUID.randomUUID().toString()); + + final Price price1 = + getPriceMockWithReferences(channelReference1, priceCustomTypeReference1, null); + final Price price2 = + getPriceMockWithReferences(channelReference2, priceCustomTypeReference2, null); + + final Asset asset1 = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType)); + final Asset asset2 = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), UUID.randomUUID().toString())); + + final ProductVariant productVariant1 = + getProductVariantMock(singletonList(price1), singletonList(asset1)); + final ProductVariant productVariant2 = + getProductVariantMock(singletonList(price2), singletonList(asset2)); + + final Product product = readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + final String uuid = UUID.randomUUID().toString(); + final Reference expandedReference = + Reference.ofResourceTypeIdAndIdAndObj(Product.referenceTypeId(), uuid, product); + final Attribute expandedProductRefAttribute = + Attribute.of("attrName", AttributeAccess.ofProductReference(), expandedReference); + + when(productVariant2.getAttributes()).thenReturn(singletonList(expandedProductRefAttribute)); + + final Reference nonExpandedReference = Product.referenceOfId(uuid); + final Attribute nonExpandedProductRefAttribute = + Attribute.of("attrName", AttributeAccess.ofProductReference(), nonExpandedReference); + when(productVariant1.getAttributes()).thenReturn(singletonList(nonExpandedProductRefAttribute)); + + final List variantDrafts = + mapToProductVariantDrafts(asList(productVariant1, productVariant2)); + + assertThat(variantDrafts).hasSize(2); + assertThat(variantDrafts.get(0).getPrices()).hasSize(1); + + final ResourceIdentifier channel1ReferenceAfterReplacement = + variantDrafts.get(0).getPrices().get(0).getChannel(); + assertThat(channel1ReferenceAfterReplacement).isNotNull(); + assertThat(channel1ReferenceAfterReplacement.getKey()).isEqualTo(channelKey1); + + final CustomFieldsDraft price1CustomFieldsAfterReplacement = + variantDrafts.get(0).getPrices().get(0).getCustom(); + assertThat(price1CustomFieldsAfterReplacement).isNotNull(); + final ResourceIdentifier priceCustomType1ReferenceAfterReplacement = + price1CustomFieldsAfterReplacement.getType(); + assertThat(priceCustomType1ReferenceAfterReplacement).isNotNull(); + assertThat(priceCustomType1ReferenceAfterReplacement.getKey()).isEqualTo(customType.getKey()); + + assertThat(variantDrafts.get(0).getAssets()).hasSize(1); + final ResourceIdentifier asset1CustomType = + variantDrafts.get(0).getAssets().get(0).getCustom().getType(); + assertThat(asset1CustomType).isNotNull(); + assertThat(asset1CustomType.getKey()).isEqualTo(customType.getKey()); + + assertThat(variantDrafts.get(1).getPrices()).hasSize(1); + + final ResourceIdentifier channel2ReferenceAfterReplacement = + variantDrafts.get(1).getPrices().get(0).getChannel(); + assertThat(channel2ReferenceAfterReplacement).isNotNull(); + // Asset price channel reference id is not replaced. + assertThat(channel2ReferenceAfterReplacement.getId()).isEqualTo(channelReference2.getId()); + + final CustomFieldsDraft price2CustomFieldsAfterReplacement = + variantDrafts.get(1).getPrices().get(0).getCustom(); + assertThat(price2CustomFieldsAfterReplacement).isNotNull(); + final ResourceIdentifier priceCustomType2ReferenceAfterReplacement = + price2CustomFieldsAfterReplacement.getType(); + assertThat(priceCustomType2ReferenceAfterReplacement).isNotNull(); + // Asset price custom type reference id is not replaced. + assertThat(priceCustomType2ReferenceAfterReplacement.getId()) + .isEqualTo(priceCustomTypeReference2.getId()); + + assertThat(variantDrafts.get(1).getAssets()).hasSize(1); + final ResourceIdentifier asset2CustomType = + variantDrafts.get(1).getAssets().get(0).getCustom().getType(); + assertThat(asset2CustomType).isNotNull(); + // Asset price asset custom type reference id is not replaced. + assertThat(asset2CustomType.getId()).isEqualTo(asset2.getCustom().getType().getId()); + + final JsonNode productReference1Value = variantDrafts.get(0).getAttributes().get(0).getValue(); + assertThat(productReference1Value).isNotNull(); + assertThat(productReference1Value.get("id")).isNotNull(); + assertThat(productReference1Value.get("id").asText()).isEqualTo(uuid); + + final JsonNode productReference2Value = variantDrafts.get(1).getAttributes().get(0).getValue(); + assertThat(productReference2Value).isNotNull(); + assertThat(productReference2Value.get("id")).isNotNull(); + assertThat(productReference2Value.get("id").asText()).isEqualTo("productKey1"); + } + + @Test + void mapToProductVariantDrafts_WithNoExpandedReferences_ShouldNotReplaceIds() { + final Reference channelReference = Channel.referenceOfId(UUID.randomUUID().toString()); + final Reference customTypeReference = Type.referenceOfId(UUID.randomUUID().toString()); + + final Asset asset2 = + getAssetMockWithCustomFields( + Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), UUID.randomUUID().toString())); + + final Price price = getPriceMockWithReferences(channelReference, customTypeReference, null); + final ProductVariant productVariant = + getProductVariantMock(singletonList(price), singletonList(asset2)); + + final List variantDrafts = + mapToProductVariantDrafts(singletonList(productVariant)); + + assertThat(variantDrafts).hasSize(1); + assertThat(variantDrafts.get(0).getPrices()).hasSize(1); + + final ResourceIdentifier channelReferenceAfterReplacement = + variantDrafts.get(0).getPrices().get(0).getChannel(); + assertThat(channelReferenceAfterReplacement).isNotNull(); + // Assert price channel reference id is not replaced. + assertThat(channelReferenceAfterReplacement.getId()).isEqualTo(channelReference.getId()); + + final CustomFieldsDraft priceCustomFields = variantDrafts.get(0).getPrices().get(0).getCustom(); + assertThat(priceCustomFields).isNotNull(); + final ResourceIdentifier priceCustomTypeAfterReplacement = priceCustomFields.getType(); + // Assert price custom type reference id is not replaced. + assertThat(priceCustomTypeAfterReplacement.getId()).isEqualTo(customTypeReference.getId()); + + assertThat(variantDrafts.get(0).getAssets()).hasSize(1); + final ResourceIdentifier assetCustomTypeReference = + variantDrafts.get(0).getAssets().get(0).getCustom().getType(); + assertThat(assetCustomTypeReference).isNotNull(); + // Assert asset custom type reference id is not replaced. + assertThat(assetCustomTypeReference.getId()) + .isEqualTo(variantDrafts.get(0).getAssets().get(0).getCustom().getType().getId()); + } + + @Test + void mapToPriceDraft_WithNoExpandedReferences_ShouldNotReplaceIds() { + final Reference channelReference = Channel.referenceOfId(UUID.randomUUID().toString()); + final Reference typeReference = Type.referenceOfId(UUID.randomUUID().toString()); + + final Price price = getPriceMockWithReferences(channelReference, typeReference, null); + final ProductVariant productVariant = getProductVariantMock(singletonList(price)); + + final List priceDrafts = mapToPriceDrafts(productVariant); + + assertThat(priceDrafts).hasSize(1); + final PriceDraft priceDraftAfterReplacement = priceDrafts.get(0); + + final ResourceIdentifier channelReferenceAfterReplacement = + priceDraftAfterReplacement.getChannel(); + assertThat(channelReferenceAfterReplacement).isNotNull(); + // Assert Id is not replaced with key. + assertThat(channelReferenceAfterReplacement.getId()).isEqualTo(channelReference.getId()); + + final CustomFieldsDraft customAfterReplacement = priceDraftAfterReplacement.getCustom(); + assertThat(customAfterReplacement).isNotNull(); + final ResourceIdentifier customTypeAfterReplacement = customAfterReplacement.getType(); + + assertThat(customTypeAfterReplacement).isNotNull(); + // Assert Id is not replaced with key. + assertThat(customTypeAfterReplacement.getId()).isEqualTo(typeReference.getId()); + } + + @Test + void mapToPriceDraft_WithAllExpandedReferences_ShouldReplaceIds() { + final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + + final String channelKey1 = "channelKey1"; + final String channelKey2 = "channelKey2"; + + final Channel channel1 = getChannelMock(channelKey1); + final Channel channel2 = getChannelMock(channelKey2); + + final Reference channelReference1 = + Reference.ofResourceTypeIdAndIdAndObj( + Channel.referenceTypeId(), channel1.getId(), channel1); + final Reference channelReference2 = + Reference.ofResourceTypeIdAndIdAndObj( + Channel.referenceTypeId(), channel2.getId(), channel2); + final Reference customTypeReference = + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType); + + final Price price1 = getPriceMockWithReferences(channelReference1, customTypeReference, null); + final Price price2 = getPriceMockWithReferences(channelReference2, customTypeReference, null); + + final ProductVariant productVariant = getProductVariantMock(asList(price1, price2)); + + final List priceDrafts = mapToPriceDrafts(productVariant); + + assertThat(priceDrafts).hasSize(2); + + final PriceDraft priceDraft1AfterReplacement = priceDrafts.get(0); + final ResourceIdentifier channelReference1AfterReplacement = + priceDraft1AfterReplacement.getChannel(); + assertThat(channelReference1AfterReplacement).isNotNull(); + assertThat(channelReference1AfterReplacement.getKey()).isEqualTo(channelKey1); + + final CustomFieldsDraft custom1AfterReplacement = priceDraft1AfterReplacement.getCustom(); + assertThat(custom1AfterReplacement).isNotNull(); + final ResourceIdentifier customType1AfterReplacement = custom1AfterReplacement.getType(); + assertThat(customType1AfterReplacement).isNotNull(); + assertThat(customType1AfterReplacement.getKey()).isEqualTo(customType.getKey()); + + final PriceDraft priceDraft2AfterReplacement = priceDrafts.get(1); + final ResourceIdentifier channelReference2AfterReplacement = + priceDraft2AfterReplacement.getChannel(); + assertThat(channelReference2AfterReplacement).isNotNull(); + assertThat(channelReference2AfterReplacement.getKey()).isEqualTo(channelKey2); + + final CustomFieldsDraft custom2AfterReplacement = priceDraft2AfterReplacement.getCustom(); + assertThat(custom2AfterReplacement).isNotNull(); + final ResourceIdentifier customType2AfterReplacement = custom2AfterReplacement.getType(); + assertThat(customType2AfterReplacement).isNotNull(); + assertThat(customType2AfterReplacement.getKey()).isEqualTo(customType.getKey()); + } + + @Test + void mapToPriceDraft_WithSomeExpandedReferences_ShouldReplaceOnlyExpandedIds() { + final Type customType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + final String channelKey1 = "channelKey1"; + + final Channel channel1 = getChannelMock(channelKey1); + + final Reference channelReference1 = + Reference.ofResourceTypeIdAndIdAndObj( + Channel.referenceTypeId(), channel1.getId(), channel1); + final Reference channelReference2 = + Channel.referenceOfId(UUID.randomUUID().toString()); + + final Reference typeReference1 = + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), customType); + final Reference typeReference2 = Type.referenceOfId(UUID.randomUUID().toString()); + + final Price price1 = getPriceMockWithReferences(channelReference1, typeReference1, null); + final Price price2 = getPriceMockWithReferences(channelReference2, typeReference2, null); + + final ProductVariant productVariant = getProductVariantMock(asList(price1, price2)); + + final List priceDrafts = mapToPriceDrafts(productVariant); + + assertThat(priceDrafts).hasSize(2); + + final ResourceIdentifier channelReference1AfterReplacement = + priceDrafts.get(0).getChannel(); + assertThat(channelReference1AfterReplacement).isNotNull(); + assertThat(channelReference1AfterReplacement.getKey()).isEqualTo(channelKey1); + + final ResourceIdentifier channelReference2AfterReplacement = + priceDrafts.get(1).getChannel(); + assertThat(channelReference2AfterReplacement).isNotNull(); + // Assert non expanded reference has id not replaced. + assertThat(channelReference2AfterReplacement.getId()).isEqualTo(channelReference2.getId()); + + final CustomFieldsDraft customType1AfterReplacement = priceDrafts.get(0).getCustom(); + assertThat(customType1AfterReplacement).isNotNull(); + assertThat(customType1AfterReplacement.getType()).isNotNull(); + assertThat(customType1AfterReplacement.getType().getKey()).isEqualTo(customType.getKey()); + + final CustomFieldsDraft customType2AfterReplacement = priceDrafts.get(1).getCustom(); + assertThat(customType2AfterReplacement).isNotNull(); + assertThat(customType2AfterReplacement.getType()).isNotNull(); + // Assert expanded reference has id not replaced. + assertThat(customType2AfterReplacement.getType().getId()).isEqualTo(typeReference2.getId()); + } + + @Test + void replaceAttributeReferenceIdWithKey_WithTextAttribute_ShouldReturnEmptyOptional() { + final Attribute attribute = Attribute.of("attrName", AttributeAccess.ofText(), "value"); + final Optional> attributeReferenceIdWithKey = + replaceAttributeReferenceIdWithKey(attribute); + + assertThat(attributeReferenceIdWithKey).isEmpty(); + } + + @Test + void + replaceAttributeReferenceIdWithKey_WithProductReferenceSetAttribute_ShouldReturnEmptyOptional() { + final Attribute attribute = + Attribute.of("attrName", AttributeAccess.ofProductReferenceSet(), new HashSet<>()); + final Optional> attributeReferenceIdWithKey = + replaceAttributeReferenceIdWithKey(attribute); + + assertThat(attributeReferenceIdWithKey).isEmpty(); + } + + @Test + void + replaceAttributeReferenceIdWithKey_WithNonExpandedProductReferenceAttribute_ShouldNotReplaceId() { + final Reference nonExpandedReference = + Product.referenceOfId(UUID.randomUUID().toString()); + final Attribute attribute = + Attribute.of("attrName", AttributeAccess.ofProductReference(), nonExpandedReference); + final Optional> attributeReferenceIdWithKey = + replaceAttributeReferenceIdWithKey(attribute); + + assertThat(attributeReferenceIdWithKey).contains(nonExpandedReference); + } + + @Test + void replaceAttributeReferenceIdWithKey_WithExpandedProductReferenceAttribute_ShouldReplaceId() { + final Product product = readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + final Reference expandedReference = + Reference.ofResourceTypeIdAndIdAndObj( + Product.referenceTypeId(), UUID.randomUUID().toString(), product); + final Attribute attribute = + Attribute.of("attrName", AttributeAccess.ofProductReference(), expandedReference); + final Optional> attributeReferenceIdWithKey = + replaceAttributeReferenceIdWithKey(attribute); + assertThat(attributeReferenceIdWithKey).contains(Product.referenceOfId("productKey1")); + } + + @Test + void replaceAttributeReferenceSetIdsWithKeys_WithTextAttribute_ShouldReturnEmptyOptional() { + final Attribute attribute = Attribute.of("attrName", AttributeAccess.ofText(), "value"); + final Optional>> attributeReferenceSetIdsWithKeys = + replaceAttributeReferenceSetIdsWithKeys(attribute); + + assertThat(attributeReferenceSetIdsWithKeys).isEmpty(); + } + + @Test + void replaceAttributesReferencesIdsWithKeys_WithNoAttributes_ShouldNotReplaceIds() { + final ProductVariant variant = mock(ProductVariant.class); + when(variant.getAttributes()).thenReturn(new ArrayList<>()); + final List replacedDrafts = replaceAttributesReferencesIdsWithKeys(variant); + assertThat(replacedDrafts).isEmpty(); + } + + @Test + void + replaceAttributesReferencesIdsWithKeys_WithAttributesWithNoReferences_ShouldNotChangeAttributes() { + final Product product = readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + final ProductVariant masterVariant = product.getMasterData().getStaged().getMasterVariant(); + final List replacedDrafts = + replaceAttributesReferencesIdsWithKeys(masterVariant); + replacedDrafts.forEach( + attributeDraft -> { + final String name = attributeDraft.getName(); + final Attribute originalAttribute = masterVariant.getAttribute(name); + assertThat(originalAttribute).isNotNull(); + assertThat(originalAttribute.getValueAsJsonNode()).isEqualTo(attributeDraft.getValue()); }); - } - - @Test - void isProductReference_WithDifferentAttributeTypes_ShouldBeTrueForProductReferenceAttributeOnly() { - assertThat(isProductReference(BOOLEAN_ATTRIBUTE_TRUE)).isFalse(); - assertThat(isProductReference(TEXT_ATTRIBUTE_BAR)).isFalse(); - assertThat(isProductReference(LTEXT_ATTRIBUTE_EN_BAR)).isFalse(); - assertThat(isProductReference(ENUM_ATTRIBUTE_BARLABEL_BARKEY)).isFalse(); - assertThat(isProductReference(LENUM_ATTRIBUTE_EN_BAR)).isFalse(); - assertThat(isProductReference(NUMBER_ATTRIBUTE_10)).isFalse(); - assertThat(isProductReference(MONEY_ATTRIBUTE_EUR_2300)).isFalse(); - assertThat(isProductReference(DATE_ATTRIBUTE_2017_11_09)).isFalse(); - assertThat(isProductReference(TIME_ATTRIBUTE_10_08_46)).isFalse(); - assertThat(isProductReference(DATE_TIME_ATTRIBUTE_2016_05_20T01_02_46)).isFalse(); - assertThat(isProductReference(PRODUCT_REFERENCE_SET_ATTRIBUTE)).isFalse(); - assertThat(isProductReference(CATEGORY_REFERENCE_ATTRIBUTE)).isFalse(); - assertThat(isProductReference(LTEXT_SET_ATTRIBUTE)).isFalse(); - assertThat(isProductReference(EMPTY_SET_ATTRIBUTE)).isFalse(); - assertThat(isProductReference(PRODUCT_REFERENCE_ATTRIBUTE)).isTrue(); - } - - @Test - void isProductReferenceSet_WithDifferentAttributeTypes_ShouldBeTrueForProductReferenceSetAttributeOnly() { - assertThat(isProductReferenceSet(BOOLEAN_ATTRIBUTE_TRUE)).isFalse(); - assertThat(isProductReferenceSet(TEXT_ATTRIBUTE_BAR)).isFalse(); - assertThat(isProductReferenceSet(LTEXT_ATTRIBUTE_EN_BAR)).isFalse(); - assertThat(isProductReferenceSet(ENUM_ATTRIBUTE_BARLABEL_BARKEY)).isFalse(); - assertThat(isProductReferenceSet(LENUM_ATTRIBUTE_EN_BAR)).isFalse(); - assertThat(isProductReferenceSet(NUMBER_ATTRIBUTE_10)).isFalse(); - assertThat(isProductReferenceSet(MONEY_ATTRIBUTE_EUR_2300)).isFalse(); - assertThat(isProductReferenceSet(DATE_ATTRIBUTE_2017_11_09)).isFalse(); - assertThat(isProductReferenceSet(TIME_ATTRIBUTE_10_08_46)).isFalse(); - assertThat(isProductReferenceSet(DATE_TIME_ATTRIBUTE_2016_05_20T01_02_46)).isFalse(); - assertThat(isProductReferenceSet(PRODUCT_REFERENCE_ATTRIBUTE)).isFalse(); - assertThat(isProductReferenceSet(CATEGORY_REFERENCE_ATTRIBUTE)).isFalse(); - assertThat(isProductReferenceSet(LTEXT_SET_ATTRIBUTE)).isFalse(); - assertThat(isProductReferenceSet(EMPTY_SET_ATTRIBUTE)).isFalse(); - assertThat(isProductReferenceSet(PRODUCT_REFERENCE_SET_ATTRIBUTE)).isTrue(); - } + } + + @Test + void + isProductReference_WithDifferentAttributeTypes_ShouldBeTrueForProductReferenceAttributeOnly() { + assertThat(isProductReference(BOOLEAN_ATTRIBUTE_TRUE)).isFalse(); + assertThat(isProductReference(TEXT_ATTRIBUTE_BAR)).isFalse(); + assertThat(isProductReference(LTEXT_ATTRIBUTE_EN_BAR)).isFalse(); + assertThat(isProductReference(ENUM_ATTRIBUTE_BARLABEL_BARKEY)).isFalse(); + assertThat(isProductReference(LENUM_ATTRIBUTE_EN_BAR)).isFalse(); + assertThat(isProductReference(NUMBER_ATTRIBUTE_10)).isFalse(); + assertThat(isProductReference(MONEY_ATTRIBUTE_EUR_2300)).isFalse(); + assertThat(isProductReference(DATE_ATTRIBUTE_2017_11_09)).isFalse(); + assertThat(isProductReference(TIME_ATTRIBUTE_10_08_46)).isFalse(); + assertThat(isProductReference(DATE_TIME_ATTRIBUTE_2016_05_20T01_02_46)).isFalse(); + assertThat(isProductReference(PRODUCT_REFERENCE_SET_ATTRIBUTE)).isFalse(); + assertThat(isProductReference(CATEGORY_REFERENCE_ATTRIBUTE)).isFalse(); + assertThat(isProductReference(LTEXT_SET_ATTRIBUTE)).isFalse(); + assertThat(isProductReference(EMPTY_SET_ATTRIBUTE)).isFalse(); + assertThat(isProductReference(PRODUCT_REFERENCE_ATTRIBUTE)).isTrue(); + } + + @Test + void + isProductReferenceSet_WithDifferentAttributeTypes_ShouldBeTrueForProductReferenceSetAttributeOnly() { + assertThat(isProductReferenceSet(BOOLEAN_ATTRIBUTE_TRUE)).isFalse(); + assertThat(isProductReferenceSet(TEXT_ATTRIBUTE_BAR)).isFalse(); + assertThat(isProductReferenceSet(LTEXT_ATTRIBUTE_EN_BAR)).isFalse(); + assertThat(isProductReferenceSet(ENUM_ATTRIBUTE_BARLABEL_BARKEY)).isFalse(); + assertThat(isProductReferenceSet(LENUM_ATTRIBUTE_EN_BAR)).isFalse(); + assertThat(isProductReferenceSet(NUMBER_ATTRIBUTE_10)).isFalse(); + assertThat(isProductReferenceSet(MONEY_ATTRIBUTE_EUR_2300)).isFalse(); + assertThat(isProductReferenceSet(DATE_ATTRIBUTE_2017_11_09)).isFalse(); + assertThat(isProductReferenceSet(TIME_ATTRIBUTE_10_08_46)).isFalse(); + assertThat(isProductReferenceSet(DATE_TIME_ATTRIBUTE_2016_05_20T01_02_46)).isFalse(); + assertThat(isProductReferenceSet(PRODUCT_REFERENCE_ATTRIBUTE)).isFalse(); + assertThat(isProductReferenceSet(CATEGORY_REFERENCE_ATTRIBUTE)).isFalse(); + assertThat(isProductReferenceSet(LTEXT_SET_ATTRIBUTE)).isFalse(); + assertThat(isProductReferenceSet(EMPTY_SET_ATTRIBUTE)).isFalse(); + assertThat(isProductReferenceSet(PRODUCT_REFERENCE_SET_ATTRIBUTE)).isTrue(); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildAddToCategoryUpdateActionsTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildAddToCategoryUpdateActionsTest.java index 6e201149c2..30760ba723 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildAddToCategoryUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildAddToCategoryUpdateActionsTest.java @@ -1,58 +1,58 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.CATEGORY_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildAddToCategoryUpdateActions; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.categories.Category; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.AddToCategory; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; - -import static com.commercetools.sync.products.ProductSyncMockUtils.CATEGORY_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildAddToCategoryUpdateActions; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class BuildAddToCategoryUpdateActionsTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildAddToCategoryUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final Category category = readObjectFromResource(CATEGORY_KEY_1_RESOURCE_PATH, Category.class); - final Set> newProductCategories = new HashSet<>(); - newProductCategories.add(category.toResourceIdentifier()); - - final List> addToCategoryUpdateAction = - getAddToCategoryUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, newProductCategories); - - assertThat(addToCategoryUpdateAction).hasSize(1); - assertThat(addToCategoryUpdateAction.get(0).getAction()).isEqualTo("addToCategory"); - assertThat(((AddToCategory) addToCategoryUpdateAction.get(0)).getCategory()).isEqualTo(category.toReference()); - } - - @Test - void buildAddToCategoryUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final List> addToCategoryUpdateAction = - getAddToCategoryUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, Collections.emptySet()); - - assertThat(addToCategoryUpdateAction).isEmpty(); - } - - private List> getAddToCategoryUpdateActions(@Nonnull final Product oldProduct, - @Nonnull final Set> - newProductCategories) { - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getCategories()).thenReturn(newProductCategories); - return buildAddToCategoryUpdateActions(oldProduct, newProductDraft); - } + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildAddToCategoryUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final Category category = readObjectFromResource(CATEGORY_KEY_1_RESOURCE_PATH, Category.class); + final Set> newProductCategories = new HashSet<>(); + newProductCategories.add(category.toResourceIdentifier()); + + final List> addToCategoryUpdateAction = + getAddToCategoryUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, newProductCategories); + + assertThat(addToCategoryUpdateAction).hasSize(1); + assertThat(addToCategoryUpdateAction.get(0).getAction()).isEqualTo("addToCategory"); + assertThat(((AddToCategory) addToCategoryUpdateAction.get(0)).getCategory()) + .isEqualTo(category.toReference()); + } + + @Test + void buildAddToCategoryUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final List> addToCategoryUpdateAction = + getAddToCategoryUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, Collections.emptySet()); + + assertThat(addToCategoryUpdateAction).isEmpty(); + } + + private List> getAddToCategoryUpdateActions( + @Nonnull final Product oldProduct, + @Nonnull final Set> newProductCategories) { + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getCategories()).thenReturn(newProductCategories); + return buildAddToCategoryUpdateActions(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildChangeNameUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildChangeNameUpdateActionTest.java index 5f3a89b7cb..639c9a60e2 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildChangeNameUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildChangeNameUpdateActionTest.java @@ -1,52 +1,51 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildChangeNameUpdateAction; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.ChangeName; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildChangeNameUpdateAction; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class BuildChangeNameUpdateActionTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildChangeNameUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); - final UpdateAction changeNameUpdateAction = - getChangeNameUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newName).orElse(null); - - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); - assertThat(((ChangeName) changeNameUpdateAction).getName()).isEqualTo(newName); - } - - @Test - void buildChangeNameUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final LocalizedString newName = LocalizedString.of(Locale.ENGLISH, "english name"); - final Optional> changeNameUpdateAction = - getChangeNameUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newName); - - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction).isNotPresent(); - } - - private Optional> getChangeNameUpdateAction(@Nonnull final Product oldProduct, - @Nonnull final LocalizedString newProductName) { - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getName()).thenReturn(newProductName); - return buildChangeNameUpdateAction(oldProduct, newProductDraft); - } + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildChangeNameUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final LocalizedString newName = LocalizedString.of(Locale.GERMAN, "newName"); + final UpdateAction changeNameUpdateAction = + getChangeNameUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newName).orElse(null); + + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); + assertThat(((ChangeName) changeNameUpdateAction).getName()).isEqualTo(newName); + } + + @Test + void buildChangeNameUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final LocalizedString newName = LocalizedString.of(Locale.ENGLISH, "english name"); + final Optional> changeNameUpdateAction = + getChangeNameUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newName); + + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction).isNotPresent(); + } + + private Optional> getChangeNameUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final LocalizedString newProductName) { + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getName()).thenReturn(newProductName); + return buildChangeNameUpdateAction(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildChangeSlugUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildChangeSlugUpdateActionTest.java index 1df76f0cb5..f322a1c487 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildChangeSlugUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildChangeSlugUpdateActionTest.java @@ -1,53 +1,51 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildChangeSlugUpdateAction; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.ChangeSlug; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildChangeSlugUpdateAction; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class BuildChangeSlugUpdateActionTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildChangeSlugUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final LocalizedString newSlug = LocalizedString.of(Locale.GERMAN, "newSlug"); - final UpdateAction changeSlugUpdateAction = - getChangeSlugUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newSlug).orElse(null); - - assertThat(changeSlugUpdateAction).isNotNull(); - assertThat(changeSlugUpdateAction.getAction()).isEqualTo("changeSlug"); - assertThat(((ChangeSlug) changeSlugUpdateAction).getSlug()).isEqualTo(newSlug); - } - - @Test - void buildChangeSlugUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final LocalizedString newSlug = LocalizedString.of(Locale.ENGLISH, "english-slug"); - final Optional> changeSlugUpdateAction = - getChangeSlugUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newSlug); - - assertThat(changeSlugUpdateAction).isNotNull(); - assertThat(changeSlugUpdateAction).isNotPresent(); - } - - private Optional> getChangeSlugUpdateAction(@Nonnull final Product oldProduct, - @Nonnull final LocalizedString - newProductDescription) { - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getSlug()).thenReturn(newProductDescription); - return buildChangeSlugUpdateAction(oldProduct, newProductDraft); - } + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildChangeSlugUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final LocalizedString newSlug = LocalizedString.of(Locale.GERMAN, "newSlug"); + final UpdateAction changeSlugUpdateAction = + getChangeSlugUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newSlug).orElse(null); + + assertThat(changeSlugUpdateAction).isNotNull(); + assertThat(changeSlugUpdateAction.getAction()).isEqualTo("changeSlug"); + assertThat(((ChangeSlug) changeSlugUpdateAction).getSlug()).isEqualTo(newSlug); + } + + @Test + void buildChangeSlugUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final LocalizedString newSlug = LocalizedString.of(Locale.ENGLISH, "english-slug"); + final Optional> changeSlugUpdateAction = + getChangeSlugUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newSlug); + + assertThat(changeSlugUpdateAction).isNotNull(); + assertThat(changeSlugUpdateAction).isNotPresent(); + } + + private Optional> getChangeSlugUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final LocalizedString newProductDescription) { + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getSlug()).thenReturn(newProductDescription); + return buildChangeSlugUpdateAction(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildPublishOrUnpublishUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildPublishOrUnpublishUpdateActionTest.java index e1fc4d232a..dfc82ea683 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildPublishOrUnpublishUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildPublishOrUnpublishUpdateActionTest.java @@ -1,192 +1,190 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildPublishOrUnpublishUpdateAction; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductCatalogData; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.Publish; import io.sphere.sdk.products.commands.updateactions.Unpublish; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nullable; import java.util.Optional; - -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildPublishOrUnpublishUpdateAction; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; class BuildPublishOrUnpublishUpdateActionTest { - @Test - void buildPublishOrUnpublishUpdateAction_State_1_ShouldNotBuildUpdateAction() { - final Optional> action = - getPublishOrUnpublishUpdateAction(false, false, false, false); - - assertThat(action).isNotNull(); - assertThat(action).isNotPresent(); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_2_ShouldNotBuildUpdateAction() { - final Optional> action = - getPublishOrUnpublishUpdateAction(false, false, false, true); - - assertThat(action).isNotNull(); - assertThat(action).isNotPresent(); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_3_ShouldNotBuildUpdateAction() { - final Optional> action = - getPublishOrUnpublishUpdateAction(false, false, true, false); - - assertThat(action).isNotNull(); - assertThat(action).isNotPresent(); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_4_ShouldNotBuildUpdateAction() { - final Optional> action = - getPublishOrUnpublishUpdateAction(false, false, true, true); - - assertThat(action).isNotNull(); - assertThat(action).isNotPresent(); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_5_ShouldBuildUnpublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(false, true, false, false).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Unpublish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_6_ShouldBuildUnpublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(false, true, false, true).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Unpublish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_7_ShouldBuildUnpublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(false, true, true, false).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Unpublish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_8_ShouldBuildUnpublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(false, true, true, true).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Unpublish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_9_ShouldBuildPublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(true, false, false, false).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Publish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_10_ShouldBuildPublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(true, false, false, true).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Publish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_11_ShouldBuildPublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(true, false, true, false).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Publish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_12_ShouldBuildPublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(true, false, true, true).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Publish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_13_ShouldNotBuildUpdateAction() { - final Optional> action = - getPublishOrUnpublishUpdateAction(true, true, false, false); - - assertThat(action).isNotNull(); - assertThat(action).isNotPresent(); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_14_ShouldBuildPublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(true, true, false, true).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Publish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_15_ShouldBuildPublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(true, true, true, false).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Publish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_State_16_ShouldBuildPublishUpdateAction() { - final UpdateAction action = - getPublishOrUnpublishUpdateAction(true, true, true, true).orElse(null); - - assertThat(action).isNotNull(); - assertThat(action).isEqualTo(Publish.of()); - } - - @Test - void buildPublishOrUnpublishUpdateAction_WithNullIsPublishedValues_ShouldAssumeTheValuesAsFalse() { - final Optional> action = - getPublishOrUnpublishUpdateAction(null, null, true, true); - - assertThat(action).isNotNull(); - assertThat(action).isNotPresent(); - } - - private Optional> getPublishOrUnpublishUpdateAction( - @Nullable final Boolean isNewProductDraftPublished, - @Nullable final Boolean isOldProductPublished, - final boolean hasNewUpdateActions, - final boolean hasOldProductStagedChanges) { - - final ProductCatalogData productCatalogData = mock(ProductCatalogData.class); - when(productCatalogData.isPublished()).thenReturn(isOldProductPublished); - when(productCatalogData.hasStagedChanges()).thenReturn(hasOldProductStagedChanges); - - final Product oldProduct = mock(Product.class); - when(oldProduct.getMasterData()).thenReturn(productCatalogData); - - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.isPublish()).thenReturn(isNewProductDraftPublished); - return buildPublishOrUnpublishUpdateAction(oldProduct, newProductDraft, hasNewUpdateActions); - } - - + @Test + void buildPublishOrUnpublishUpdateAction_State_1_ShouldNotBuildUpdateAction() { + final Optional> action = + getPublishOrUnpublishUpdateAction(false, false, false, false); + + assertThat(action).isNotNull(); + assertThat(action).isNotPresent(); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_2_ShouldNotBuildUpdateAction() { + final Optional> action = + getPublishOrUnpublishUpdateAction(false, false, false, true); + + assertThat(action).isNotNull(); + assertThat(action).isNotPresent(); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_3_ShouldNotBuildUpdateAction() { + final Optional> action = + getPublishOrUnpublishUpdateAction(false, false, true, false); + + assertThat(action).isNotNull(); + assertThat(action).isNotPresent(); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_4_ShouldNotBuildUpdateAction() { + final Optional> action = + getPublishOrUnpublishUpdateAction(false, false, true, true); + + assertThat(action).isNotNull(); + assertThat(action).isNotPresent(); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_5_ShouldBuildUnpublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(false, true, false, false).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Unpublish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_6_ShouldBuildUnpublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(false, true, false, true).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Unpublish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_7_ShouldBuildUnpublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(false, true, true, false).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Unpublish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_8_ShouldBuildUnpublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(false, true, true, true).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Unpublish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_9_ShouldBuildPublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(true, false, false, false).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Publish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_10_ShouldBuildPublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(true, false, false, true).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Publish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_11_ShouldBuildPublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(true, false, true, false).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Publish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_12_ShouldBuildPublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(true, false, true, true).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Publish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_13_ShouldNotBuildUpdateAction() { + final Optional> action = + getPublishOrUnpublishUpdateAction(true, true, false, false); + + assertThat(action).isNotNull(); + assertThat(action).isNotPresent(); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_14_ShouldBuildPublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(true, true, false, true).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Publish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_15_ShouldBuildPublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(true, true, true, false).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Publish.of()); + } + + @Test + void buildPublishOrUnpublishUpdateAction_State_16_ShouldBuildPublishUpdateAction() { + final UpdateAction action = + getPublishOrUnpublishUpdateAction(true, true, true, true).orElse(null); + + assertThat(action).isNotNull(); + assertThat(action).isEqualTo(Publish.of()); + } + + @Test + void + buildPublishOrUnpublishUpdateAction_WithNullIsPublishedValues_ShouldAssumeTheValuesAsFalse() { + final Optional> action = + getPublishOrUnpublishUpdateAction(null, null, true, true); + + assertThat(action).isNotNull(); + assertThat(action).isNotPresent(); + } + + private Optional> getPublishOrUnpublishUpdateAction( + @Nullable final Boolean isNewProductDraftPublished, + @Nullable final Boolean isOldProductPublished, + final boolean hasNewUpdateActions, + final boolean hasOldProductStagedChanges) { + + final ProductCatalogData productCatalogData = mock(ProductCatalogData.class); + when(productCatalogData.isPublished()).thenReturn(isOldProductPublished); + when(productCatalogData.hasStagedChanges()).thenReturn(hasOldProductStagedChanges); + + final Product oldProduct = mock(Product.class); + when(oldProduct.getMasterData()).thenReturn(productCatalogData); + + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.isPublish()).thenReturn(isNewProductDraftPublished); + return buildPublishOrUnpublishUpdateAction(oldProduct, newProductDraft, hasNewUpdateActions); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildRemoveFromCategoryUpdateActionsTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildRemoveFromCategoryUpdateActionsTest.java index 30559e35da..f3d8f8645e 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildRemoveFromCategoryUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildRemoveFromCategoryUpdateActionsTest.java @@ -1,60 +1,58 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildRemoveFromCategoryUpdateActions; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.categories.Category; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildRemoveFromCategoryUpdateActions; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class BuildRemoveFromCategoryUpdateActionsTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildRemoveFromCategoryUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final List> removeFromCategoryUpdateActions = - getRemoveFromCategoryUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, Collections.emptySet()); - - assertThat(removeFromCategoryUpdateActions).hasSize(4); - removeFromCategoryUpdateActions - .forEach(updateAction -> assertThat(updateAction.getAction()).isEqualTo("removeFromCategory")); - } - - @Test - void buildRemoveFromCategoryUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final Set> newProductCategories = new HashSet<>(); - newProductCategories.add(Category.referenceOfId("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); - newProductCategories.add(Category.referenceOfId("2dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); - newProductCategories.add(Category.referenceOfId("3dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); - newProductCategories.add(Category.referenceOfId("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); - - final List> removeFromCategoryUpdateActions = - getRemoveFromCategoryUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, newProductCategories); - - assertThat(removeFromCategoryUpdateActions).isEmpty(); - } - - private List> getRemoveFromCategoryUpdateActions(@Nonnull final Product oldProduct, - @Nonnull final - Set> - newProductCategories) { - - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getCategories()).thenReturn(newProductCategories); - return buildRemoveFromCategoryUpdateActions(oldProduct, newProductDraft); - } + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildRemoveFromCategoryUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final List> removeFromCategoryUpdateActions = + getRemoveFromCategoryUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, Collections.emptySet()); + + assertThat(removeFromCategoryUpdateActions).hasSize(4); + removeFromCategoryUpdateActions.forEach( + updateAction -> assertThat(updateAction.getAction()).isEqualTo("removeFromCategory")); + } + + @Test + void buildRemoveFromCategoryUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final Set> newProductCategories = new HashSet<>(); + newProductCategories.add(Category.referenceOfId("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); + newProductCategories.add(Category.referenceOfId("2dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); + newProductCategories.add(Category.referenceOfId("3dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); + newProductCategories.add(Category.referenceOfId("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f")); + + final List> removeFromCategoryUpdateActions = + getRemoveFromCategoryUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, newProductCategories); + + assertThat(removeFromCategoryUpdateActions).isEmpty(); + } + + private List> getRemoveFromCategoryUpdateActions( + @Nonnull final Product oldProduct, + @Nonnull final Set> newProductCategories) { + + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getCategories()).thenReturn(newProductCategories); + return buildRemoveFromCategoryUpdateActions(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetCategoryOrderHintUpdateActionsTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetCategoryOrderHintUpdateActionsTest.java index fc1ce895d1..a0fce4a729 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetCategoryOrderHintUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetCategoryOrderHintUpdateActionsTest.java @@ -1,63 +1,67 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetCategoryOrderHintUpdateActions; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.CategoryOrderHints; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.SetCategoryOrderHint; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.HashMap; import java.util.List; import java.util.Map; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetCategoryOrderHintUpdateActions; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class BuildSetCategoryOrderHintUpdateActionsTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildSetCategoryOrderHintUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final Map categoryOrderHintsMap = new HashMap<>(); - categoryOrderHintsMap.put("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.33"); - final CategoryOrderHints newProductCategoryOrderHints = CategoryOrderHints.of(categoryOrderHintsMap); - - final List> setCategoryOrderHintUpdateActions = - getSetCategoryOrderHintUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, newProductCategoryOrderHints); - - assertThat(setCategoryOrderHintUpdateActions).hasSize(1); - assertThat(setCategoryOrderHintUpdateActions.get(0).getAction()).isEqualTo("setCategoryOrderHint"); - assertThat(((SetCategoryOrderHint) setCategoryOrderHintUpdateActions.get(0)).getOrderHint()).isEqualTo("0.33"); - } - - @Test - void buildSetCategoryOrderHintUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final Map categoryOrderHintsMap = new HashMap<>(); - categoryOrderHintsMap.put("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.43"); - categoryOrderHintsMap.put("2dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.53"); - categoryOrderHintsMap.put("3dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.63"); - categoryOrderHintsMap.put("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.73"); - final CategoryOrderHints newProductCategoryOrderHints = CategoryOrderHints.of(categoryOrderHintsMap); - - - final List> setCategoryOrderHintUpdateActions = - getSetCategoryOrderHintUpdateActions(MOCK_OLD_PUBLISHED_PRODUCT, newProductCategoryOrderHints); - - assertThat(setCategoryOrderHintUpdateActions).isEmpty(); - } - - private List> getSetCategoryOrderHintUpdateActions(@Nonnull final Product oldProduct, - @Nonnull final CategoryOrderHints - newProductCategoryOrderHints) { - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getCategoryOrderHints()).thenReturn(newProductCategoryOrderHints); - return buildSetCategoryOrderHintUpdateActions(oldProduct, newProductDraft); - } + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildSetCategoryOrderHintUpdateActions_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final Map categoryOrderHintsMap = new HashMap<>(); + categoryOrderHintsMap.put("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.33"); + final CategoryOrderHints newProductCategoryOrderHints = + CategoryOrderHints.of(categoryOrderHintsMap); + + final List> setCategoryOrderHintUpdateActions = + getSetCategoryOrderHintUpdateActions( + MOCK_OLD_PUBLISHED_PRODUCT, newProductCategoryOrderHints); + + assertThat(setCategoryOrderHintUpdateActions).hasSize(1); + assertThat(setCategoryOrderHintUpdateActions.get(0).getAction()) + .isEqualTo("setCategoryOrderHint"); + assertThat(((SetCategoryOrderHint) setCategoryOrderHintUpdateActions.get(0)).getOrderHint()) + .isEqualTo("0.33"); + } + + @Test + void buildSetCategoryOrderHintUpdateActions_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final Map categoryOrderHintsMap = new HashMap<>(); + categoryOrderHintsMap.put("1dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.43"); + categoryOrderHintsMap.put("2dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.53"); + categoryOrderHintsMap.put("3dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.63"); + categoryOrderHintsMap.put("4dfc8bea-84f2-45bc-b3c2-cdc94bf96f1f", "0.73"); + final CategoryOrderHints newProductCategoryOrderHints = + CategoryOrderHints.of(categoryOrderHintsMap); + + final List> setCategoryOrderHintUpdateActions = + getSetCategoryOrderHintUpdateActions( + MOCK_OLD_PUBLISHED_PRODUCT, newProductCategoryOrderHints); + + assertThat(setCategoryOrderHintUpdateActions).isEmpty(); + } + + private List> getSetCategoryOrderHintUpdateActions( + @Nonnull final Product oldProduct, + @Nonnull final CategoryOrderHints newProductCategoryOrderHints) { + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getCategoryOrderHints()).thenReturn(newProductCategoryOrderHints); + return buildSetCategoryOrderHintUpdateActions(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetDescriptionUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetDescriptionUpdateActionTest.java index 9701895466..2e2ac2a971 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetDescriptionUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetDescriptionUpdateActionTest.java @@ -1,53 +1,53 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetDescriptionUpdateAction; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.SetDescription; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetDescriptionUpdateAction; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class BuildSetDescriptionUpdateActionTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildSetDescriptionUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final LocalizedString newDescription = LocalizedString.of(Locale.GERMAN, "newDescription"); - final UpdateAction setDescriptionUpdateAction = - getSetDescriptionUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newDescription).orElse(null); - - assertThat(setDescriptionUpdateAction).isNotNull(); - assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); - assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()).isEqualTo(newDescription); - } - - @Test - void buildSetDescriptionUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final LocalizedString newDescription = LocalizedString.of(Locale.ENGLISH, "english description."); - final Optional> setDescriptionUpdateAction = - getSetDescriptionUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newDescription); - - assertThat(setDescriptionUpdateAction).isNotNull(); - assertThat(setDescriptionUpdateAction).isNotPresent(); - } - - private Optional> getSetDescriptionUpdateAction(@Nonnull final Product oldProduct, - @Nonnull final LocalizedString - newProductDescription) { - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getDescription()).thenReturn(newProductDescription); - return buildSetDescriptionUpdateAction(oldProduct, newProductDraft); - } + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildSetDescriptionUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final LocalizedString newDescription = LocalizedString.of(Locale.GERMAN, "newDescription"); + final UpdateAction setDescriptionUpdateAction = + getSetDescriptionUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newDescription).orElse(null); + + assertThat(setDescriptionUpdateAction).isNotNull(); + assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); + assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()) + .isEqualTo(newDescription); + } + + @Test + void buildSetDescriptionUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final LocalizedString newDescription = + LocalizedString.of(Locale.ENGLISH, "english description."); + final Optional> setDescriptionUpdateAction = + getSetDescriptionUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newDescription); + + assertThat(setDescriptionUpdateAction).isNotNull(); + assertThat(setDescriptionUpdateAction).isNotPresent(); + } + + private Optional> getSetDescriptionUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final LocalizedString newProductDescription) { + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getDescription()).thenReturn(newProductDescription); + return buildSetDescriptionUpdateAction(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaDescriptionUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaDescriptionUpdateActionTest.java index 4aca12f5c8..14b433db88 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaDescriptionUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaDescriptionUpdateActionTest.java @@ -1,54 +1,53 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetMetaDescriptionUpdateAction; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.SetMetaDescription; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetMetaDescriptionUpdateAction; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; class BuildSetMetaDescriptionUpdateActionTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildSetMetaDescriptionUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final LocalizedString newDescription = LocalizedString.of(Locale.GERMAN, "newDescription"); - final UpdateAction setMetaDescriptionUpdateAction = - getSetMetaDescriptionUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newDescription).orElse(null); - - assertThat(setMetaDescriptionUpdateAction).isNotNull(); - assertThat(setMetaDescriptionUpdateAction.getAction()).isEqualTo("setMetaDescription"); - assertThat(((SetMetaDescription) setMetaDescriptionUpdateAction).getMetaDescription()) - .isEqualTo(newDescription); - } - - @Test - void buildSetMetaDescriptionUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final Optional> setMetaDescriptionUpdateAction = - getSetMetaDescriptionUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, null); - - assertThat(setMetaDescriptionUpdateAction).isNotNull(); - assertThat(setMetaDescriptionUpdateAction).isNotPresent(); - } - - private Optional> getSetMetaDescriptionUpdateAction(@Nonnull final Product oldProduct, - @Nullable final LocalizedString - newProductMetaDescription) { - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getMetaDescription()).thenReturn(newProductMetaDescription); - return buildSetMetaDescriptionUpdateAction(oldProduct, newProductDraft); - } + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildSetMetaDescriptionUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final LocalizedString newDescription = LocalizedString.of(Locale.GERMAN, "newDescription"); + final UpdateAction setMetaDescriptionUpdateAction = + getSetMetaDescriptionUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newDescription).orElse(null); + + assertThat(setMetaDescriptionUpdateAction).isNotNull(); + assertThat(setMetaDescriptionUpdateAction.getAction()).isEqualTo("setMetaDescription"); + assertThat(((SetMetaDescription) setMetaDescriptionUpdateAction).getMetaDescription()) + .isEqualTo(newDescription); + } + + @Test + void buildSetMetaDescriptionUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final Optional> setMetaDescriptionUpdateAction = + getSetMetaDescriptionUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, null); + + assertThat(setMetaDescriptionUpdateAction).isNotNull(); + assertThat(setMetaDescriptionUpdateAction).isNotPresent(); + } + + private Optional> getSetMetaDescriptionUpdateAction( + @Nonnull final Product oldProduct, + @Nullable final LocalizedString newProductMetaDescription) { + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getMetaDescription()).thenReturn(newProductMetaDescription); + return buildSetMetaDescriptionUpdateAction(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaKeywordsUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaKeywordsUpdateActionTest.java index 6d22205a32..6d44de133e 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaKeywordsUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaKeywordsUpdateActionTest.java @@ -1,53 +1,52 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetMetaKeywordsUpdateAction; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.SetMetaKeywords; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetMetaKeywordsUpdateAction; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; class BuildSetMetaKeywordsUpdateActionTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildSetMetaKeywordsUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final LocalizedString newKeywords = LocalizedString.of(Locale.GERMAN, "newKeywords"); - final UpdateAction setMetaKeywordsUpdateAction = - getSetMetaKeywordsUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newKeywords).orElse(null); - - assertThat(setMetaKeywordsUpdateAction).isNotNull(); - assertThat(setMetaKeywordsUpdateAction.getAction()).isEqualTo("setMetaKeywords"); - assertThat(((SetMetaKeywords) setMetaKeywordsUpdateAction).getMetaKeywords()).isEqualTo(newKeywords); - } - - @Test - void buildSetMetaKeywordsUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final Optional> setMetaKeywordsUpdateAction = - getSetMetaKeywordsUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, null); - - assertThat(setMetaKeywordsUpdateAction).isNotNull(); - assertThat(setMetaKeywordsUpdateAction).isNotPresent(); - } - - private Optional> getSetMetaKeywordsUpdateAction(@Nonnull final Product oldProduct, - @Nullable final LocalizedString - newProductMetaKeywords) { - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getMetaKeywords()).thenReturn(newProductMetaKeywords); - return buildSetMetaKeywordsUpdateAction(oldProduct, newProductDraft); - } + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildSetMetaKeywordsUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final LocalizedString newKeywords = LocalizedString.of(Locale.GERMAN, "newKeywords"); + final UpdateAction setMetaKeywordsUpdateAction = + getSetMetaKeywordsUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newKeywords).orElse(null); + + assertThat(setMetaKeywordsUpdateAction).isNotNull(); + assertThat(setMetaKeywordsUpdateAction.getAction()).isEqualTo("setMetaKeywords"); + assertThat(((SetMetaKeywords) setMetaKeywordsUpdateAction).getMetaKeywords()) + .isEqualTo(newKeywords); + } + + @Test + void buildSetMetaKeywordsUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final Optional> setMetaKeywordsUpdateAction = + getSetMetaKeywordsUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, null); + + assertThat(setMetaKeywordsUpdateAction).isNotNull(); + assertThat(setMetaKeywordsUpdateAction).isNotPresent(); + } + + private Optional> getSetMetaKeywordsUpdateAction( + @Nonnull final Product oldProduct, @Nullable final LocalizedString newProductMetaKeywords) { + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getMetaKeywords()).thenReturn(newProductMetaKeywords); + return buildSetMetaKeywordsUpdateAction(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaTitleUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaTitleUpdateActionTest.java index 411e4ae689..0fa94f8bc1 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaTitleUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetMetaTitleUpdateActionTest.java @@ -1,53 +1,51 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetMetaTitleUpdateAction; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.SetMetaTitle; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetMetaTitleUpdateAction; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; class BuildSetMetaTitleUpdateActionTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildSetMetaTitleUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final LocalizedString newTitle = LocalizedString.of(Locale.GERMAN, "newTitle"); - final UpdateAction setMetaTitleUpdateAction = - getSetMetaTitleUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newTitle).orElse(null); - - assertThat(setMetaTitleUpdateAction).isNotNull(); - assertThat(setMetaTitleUpdateAction.getAction()).isEqualTo("setMetaTitle"); - assertThat(((SetMetaTitle) setMetaTitleUpdateAction).getMetaTitle()).isEqualTo(newTitle); - } - - @Test - void buildSetMetaTitleUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final Optional> setMetaTitleUpdateAction = - getSetMetaTitleUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, null); - - assertThat(setMetaTitleUpdateAction).isNotNull(); - assertThat(setMetaTitleUpdateAction).isNotPresent(); - } - - private Optional> getSetMetaTitleUpdateAction(@Nonnull final Product oldProduct, - @Nullable final LocalizedString - newProductMetaTitle) { - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getMetaTitle()).thenReturn(newProductMetaTitle); - return buildSetMetaTitleUpdateAction(oldProduct, newProductDraft); - } + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildSetMetaTitleUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final LocalizedString newTitle = LocalizedString.of(Locale.GERMAN, "newTitle"); + final UpdateAction setMetaTitleUpdateAction = + getSetMetaTitleUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newTitle).orElse(null); + + assertThat(setMetaTitleUpdateAction).isNotNull(); + assertThat(setMetaTitleUpdateAction.getAction()).isEqualTo("setMetaTitle"); + assertThat(((SetMetaTitle) setMetaTitleUpdateAction).getMetaTitle()).isEqualTo(newTitle); + } + + @Test + void buildSetMetaTitleUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final Optional> setMetaTitleUpdateAction = + getSetMetaTitleUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, null); + + assertThat(setMetaTitleUpdateAction).isNotNull(); + assertThat(setMetaTitleUpdateAction).isNotPresent(); + } + + private Optional> getSetMetaTitleUpdateAction( + @Nonnull final Product oldProduct, @Nullable final LocalizedString newProductMetaTitle) { + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getMetaTitle()).thenReturn(newProductMetaTitle); + return buildSetMetaTitleUpdateAction(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetSearchKeywordsUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetSearchKeywordsUpdateActionTest.java index 190322a3ce..5d875176e6 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetSearchKeywordsUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetSearchKeywordsUpdateActionTest.java @@ -1,58 +1,58 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetSearchKeywordsUpdateAction; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.products.commands.updateactions.SetSearchKeywords; import io.sphere.sdk.search.SearchKeyword; import io.sphere.sdk.search.SearchKeywords; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.products.ProductSyncMockUtils.PRODUCT_KEY_1_RESOURCE_PATH; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetSearchKeywordsUpdateAction; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class BuildSetSearchKeywordsUpdateActionTest { - private static final Product MOCK_OLD_PUBLISHED_PRODUCT = readObjectFromResource( - PRODUCT_KEY_1_RESOURCE_PATH, Product.class); - - @Test - void buildSetSearchKeywordsUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { - final SearchKeywords newProductSearchKeywords = SearchKeywords.of(Locale.ENGLISH, + private static final Product MOCK_OLD_PUBLISHED_PRODUCT = + readObjectFromResource(PRODUCT_KEY_1_RESOURCE_PATH, Product.class); + + @Test + void buildSetSearchKeywordsUpdateAction_WithDifferentStagedValues_ShouldBuildUpdateAction() { + final SearchKeywords newProductSearchKeywords = + SearchKeywords.of( + Locale.ENGLISH, Arrays.asList(SearchKeyword.of("searchKeyword1"), SearchKeyword.of("searchKeyword2"))); - final UpdateAction setSearchKeywordsUpdateAction = - getSetSearchKeywordsUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newProductSearchKeywords) - .orElse(null); - - assertThat(setSearchKeywordsUpdateAction).isNotNull(); - assertThat(setSearchKeywordsUpdateAction.getAction()).isEqualTo("setSearchKeywords"); - assertThat(((SetSearchKeywords) setSearchKeywordsUpdateAction).getSearchKeywords()) - .isEqualTo(newProductSearchKeywords); - } - - @Test - void buildSetSearchKeywordsUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { - final SearchKeywords newProductSearchKeywords = SearchKeywords.of(); - final Optional> setSearchKeywordsUpdateAction = - getSetSearchKeywordsUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newProductSearchKeywords); - - assertThat(setSearchKeywordsUpdateAction).isNotNull(); - assertThat(setSearchKeywordsUpdateAction).isNotPresent(); - } - - private Optional> getSetSearchKeywordsUpdateAction(@Nonnull final Product oldProduct, - @Nonnull final SearchKeywords - newProductSearchKeywords) { - final ProductDraft newProductDraft = mock(ProductDraft.class); - when(newProductDraft.getSearchKeywords()).thenReturn(newProductSearchKeywords); - return buildSetSearchKeywordsUpdateAction(oldProduct, newProductDraft); - } + final UpdateAction setSearchKeywordsUpdateAction = + getSetSearchKeywordsUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newProductSearchKeywords) + .orElse(null); + + assertThat(setSearchKeywordsUpdateAction).isNotNull(); + assertThat(setSearchKeywordsUpdateAction.getAction()).isEqualTo("setSearchKeywords"); + assertThat(((SetSearchKeywords) setSearchKeywordsUpdateAction).getSearchKeywords()) + .isEqualTo(newProductSearchKeywords); + } + + @Test + void buildSetSearchKeywordsUpdateAction_WithSameStagedValues_ShouldNotBuildUpdateAction() { + final SearchKeywords newProductSearchKeywords = SearchKeywords.of(); + final Optional> setSearchKeywordsUpdateAction = + getSetSearchKeywordsUpdateAction(MOCK_OLD_PUBLISHED_PRODUCT, newProductSearchKeywords); + + assertThat(setSearchKeywordsUpdateAction).isNotNull(); + assertThat(setSearchKeywordsUpdateAction).isNotPresent(); + } + + private Optional> getSetSearchKeywordsUpdateAction( + @Nonnull final Product oldProduct, @Nonnull final SearchKeywords newProductSearchKeywords) { + final ProductDraft newProductDraft = mock(ProductDraft.class); + when(newProductDraft.getSearchKeywords()).thenReturn(newProductSearchKeywords); + return buildSetSearchKeywordsUpdateAction(oldProduct, newProductDraft); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetTaxCategoryUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetTaxCategoryUpdateActionTest.java index 0448de1e32..12f4c5bf55 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetTaxCategoryUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildSetTaxCategoryUpdateActionTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetTaxCategoryUpdateAction; +import static io.sphere.sdk.json.SphereJsonUtils.readObject; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + import io.sphere.sdk.models.Reference; import io.sphere.sdk.models.ResourceIdentifier; import io.sphere.sdk.products.Product; @@ -11,64 +16,61 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildSetTaxCategoryUpdateAction; -import static io.sphere.sdk.json.SphereJsonUtils.readObject; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) class BuildSetTaxCategoryUpdateActionTest { - @Mock - private Product oldProduct; - - @Mock - private ProductDraft newProduct; + @Mock private Product oldProduct; - @SuppressWarnings("unchecked") - private static final Reference oldTaxCategory = readObject( - "{\"typeId\": \"tax-category\",\"id\": \"11111111-1111-1111-1111-111111111111\"}", Reference.class); + @Mock private ProductDraft newProduct; - @SuppressWarnings("unchecked") - private static final ResourceIdentifier newSameTaxCategory = readObject( - "{\"typeId\": \"tax-category\",\"id\": \"11111111-1111-1111-1111-111111111111\"}", - ResourceIdentifier.class); + @SuppressWarnings("unchecked") + private static final Reference oldTaxCategory = + readObject( + "{\"typeId\": \"tax-category\",\"id\": \"11111111-1111-1111-1111-111111111111\"}", + Reference.class); - @SuppressWarnings("unchecked") - private static final ResourceIdentifier newChangedTaxCategory = readObject( - "{\"typeId\": \"tax-category\",\"id\": \"22222222-2222-2222-2222-222222222222\"}", - ResourceIdentifier.class); + @SuppressWarnings("unchecked") + private static final ResourceIdentifier newSameTaxCategory = + readObject( + "{\"typeId\": \"tax-category\",\"id\": \"11111111-1111-1111-1111-111111111111\"}", + ResourceIdentifier.class); - @Test - void buildSetTaxCategoryUpdateAction_withEmptyOld_containsNewCategory() { - assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)).isEmpty(); + @SuppressWarnings("unchecked") + private static final ResourceIdentifier newChangedTaxCategory = + readObject( + "{\"typeId\": \"tax-category\",\"id\": \"22222222-2222-2222-2222-222222222222\"}", + ResourceIdentifier.class); - when(newProduct.getTaxCategory()).thenReturn(newSameTaxCategory); - assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)) - .contains(SetTaxCategory.of(newSameTaxCategory)); - } + @Test + void buildSetTaxCategoryUpdateAction_withEmptyOld_containsNewCategory() { + assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)).isEmpty(); - @Test - void buildSetTaxCategoryUpdateAction_withEmptyNew_ShouldUnset() { - assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)).isEmpty(); + when(newProduct.getTaxCategory()).thenReturn(newSameTaxCategory); + assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)) + .contains(SetTaxCategory.of(newSameTaxCategory)); + } - when(oldProduct.getTaxCategory()).thenReturn(oldTaxCategory); - assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)).contains(SetTaxCategory.of(null)); - } + @Test + void buildSetTaxCategoryUpdateAction_withEmptyNew_ShouldUnset() { + assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)).isEmpty(); - @Test - void buildSetTaxCategoryUpdateAction_withEqual_isEmpty() { - when(oldProduct.getTaxCategory()).thenReturn(oldTaxCategory); - when(newProduct.getTaxCategory()).thenReturn(newSameTaxCategory); - assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)).isEmpty(); - } + when(oldProduct.getTaxCategory()).thenReturn(oldTaxCategory); + assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)) + .contains(SetTaxCategory.of(null)); + } - @Test - void buildSetTaxCategoryUpdateAction_withDifferent_containsNew() { - when(oldProduct.getTaxCategory()).thenReturn(oldTaxCategory); - when(newProduct.getTaxCategory()).thenReturn(newChangedTaxCategory); - assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)) - .contains(SetTaxCategory.of(newChangedTaxCategory)); - } + @Test + void buildSetTaxCategoryUpdateAction_withEqual_isEmpty() { + when(oldProduct.getTaxCategory()).thenReturn(oldTaxCategory); + when(newProduct.getTaxCategory()).thenReturn(newSameTaxCategory); + assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)).isEmpty(); + } -} \ No newline at end of file + @Test + void buildSetTaxCategoryUpdateAction_withDifferent_containsNew() { + when(oldProduct.getTaxCategory()).thenReturn(oldTaxCategory); + when(newProduct.getTaxCategory()).thenReturn(newChangedTaxCategory); + assertThat(buildSetTaxCategoryUpdateAction(oldProduct, newProduct)) + .contains(SetTaxCategory.of(newChangedTaxCategory)); + } +} diff --git a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildTransitionStateUpdateActionTest.java b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildTransitionStateUpdateActionTest.java index 99e8e0de9c..831f13c59b 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildTransitionStateUpdateActionTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productupdateactionutils/BuildTransitionStateUpdateActionTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.products.utils.productupdateactionutils; +import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildTransitionStateUpdateAction; +import static io.sphere.sdk.json.SphereJsonUtils.readObject; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + import io.sphere.sdk.models.Reference; import io.sphere.sdk.products.Product; import io.sphere.sdk.products.ProductDraft; @@ -10,67 +15,64 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import static com.commercetools.sync.products.utils.ProductUpdateActionUtils.buildTransitionStateUpdateAction; -import static io.sphere.sdk.json.SphereJsonUtils.readObject; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) class BuildTransitionStateUpdateActionTest { - @Mock - private Product oldProduct; - - @Mock - private ProductDraft newProduct; - - @SuppressWarnings("unchecked") - private static final Reference oldState = readObject( - "{\"typeId\": \"state\",\"id\": \"11111111-1111-1111-1111-111111111111\"}", Reference.class); - - @SuppressWarnings("unchecked") - private static final Reference newState = readObject( - "{\"typeId\": \"state\",\"id\": \"11111111-1111-1111-1111-111111111111\"}", Reference.class); - - @SuppressWarnings("unchecked") - private static final Reference newChangedState = readObject( - "{\"typeId\": \"state\",\"id\": \"22222222-2222-2222-2222-222222222222\"}", Reference.class); - - @Test - void buildTransitionStateUpdateAction_withEmptyOld() { - assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)).isEmpty(); - - when(newProduct.getState()).thenReturn(newState); - assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)) - .contains(TransitionState.of(newState, true)); - } - - @Test - void buildTransitionStateUpdateAction_withEmptyNewShouldReturnEmpty() { - assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)).isEmpty(); - } - - @Test - void buildTransitionStateUpdateAction_withEqual() { - when(oldProduct.getState()).thenReturn(oldState); - when(newProduct.getState()).thenReturn(newState); - assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)).isEmpty(); - } - - @Test - void buildTransitionStateUpdateAction_withEmptyOldShouldReturnNew() { - when(newProduct.getState()).thenReturn(newChangedState); - assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)) - .contains(TransitionState.of(newChangedState, true)); - } - - - @Test - void buildTransitionStateUpdateAction_withDifferent() { - when(oldProduct.getState()).thenReturn(oldState); - when(newProduct.getState()).thenReturn(newChangedState); - assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)) - .contains(TransitionState.of(newChangedState, true)); - } - -} \ No newline at end of file + @Mock private Product oldProduct; + + @Mock private ProductDraft newProduct; + + @SuppressWarnings("unchecked") + private static final Reference oldState = + readObject( + "{\"typeId\": \"state\",\"id\": \"11111111-1111-1111-1111-111111111111\"}", + Reference.class); + + @SuppressWarnings("unchecked") + private static final Reference newState = + readObject( + "{\"typeId\": \"state\",\"id\": \"11111111-1111-1111-1111-111111111111\"}", + Reference.class); + + @SuppressWarnings("unchecked") + private static final Reference newChangedState = + readObject( + "{\"typeId\": \"state\",\"id\": \"22222222-2222-2222-2222-222222222222\"}", + Reference.class); + + @Test + void buildTransitionStateUpdateAction_withEmptyOld() { + assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)).isEmpty(); + + when(newProduct.getState()).thenReturn(newState); + assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)) + .contains(TransitionState.of(newState, true)); + } + + @Test + void buildTransitionStateUpdateAction_withEmptyNewShouldReturnEmpty() { + assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)).isEmpty(); + } + + @Test + void buildTransitionStateUpdateAction_withEqual() { + when(oldProduct.getState()).thenReturn(oldState); + when(newProduct.getState()).thenReturn(newState); + assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)).isEmpty(); + } + + @Test + void buildTransitionStateUpdateAction_withEmptyOldShouldReturnNew() { + when(newProduct.getState()).thenReturn(newChangedState); + assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)) + .contains(TransitionState.of(newChangedState, true)); + } + + @Test + void buildTransitionStateUpdateAction_withDifferent() { + when(oldProduct.getState()).thenReturn(oldState); + when(newProduct.getState()).thenReturn(newChangedState); + assertThat(buildTransitionStateUpdateAction(oldProduct, newProduct)) + .contains(TransitionState.of(newChangedState, true)); + } +} diff --git a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/BuildProductVariantImagesUpdateActionsTest.java b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/BuildProductVariantImagesUpdateActionsTest.java index 531da8fc4c..515212dbe6 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/BuildProductVariantImagesUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/BuildProductVariantImagesUpdateActionsTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.products.utils.productvariantupdateactionutils; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildMoveImageToPositionUpdateActions; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantImagesUpdateActions; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.products.Image; import io.sphere.sdk.products.ImageDimensions; @@ -9,321 +18,334 @@ import io.sphere.sdk.products.commands.updateactions.AddExternalImage; import io.sphere.sdk.products.commands.updateactions.MoveImageToPosition; import io.sphere.sdk.products.commands.updateactions.RemoveImage; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; - -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildMoveImageToPositionUpdateActions; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantImagesUpdateActions; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class BuildProductVariantImagesUpdateActionsTest { - @Test - void buildProductVariantImagesUpdateActions_WithNullImageLists_ShouldNotBuildUpdateActions() { - final ProductVariant oldVariant = mock(ProductVariant.class); - final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); - - final List> updateActions = - buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); - assertThat(updateActions).hasSize(0); - } - - @Test - void buildProductVariantImagesUpdateActions_WithNullNewImageList_ShouldBuildRemoveImageUpdateActions() { - final ProductVariant oldVariant = mock(ProductVariant.class); - final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); - - final List oldImages = new ArrayList<>(); - oldImages.add(Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel")); - oldImages.add(Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label")); - when(oldVariant.getImages()).thenReturn(oldImages); - - final List> updateActions = - buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); - assertThat(updateActions).hasSize(2); - updateActions.forEach( - productUpdateAction -> { - assertThat(productUpdateAction.getAction()).isEqualTo("removeImage"); - assertThat(productUpdateAction).isExactlyInstanceOf(RemoveImage.class); - } - ); - } - - @Test - void buildProductVariantImagesUpdateActions_WithNullOldImageList_ShouldBuildAddExternalImageActions() { - final ProductVariant oldVariant = mock(ProductVariant.class); - final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); - - final List newImages = new ArrayList<>(); - newImages.add(Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel")); - newImages.add(Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label")); - when(newVariantDraft.getImages()).thenReturn(newImages); - - final List> updateActions = - buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); - assertThat(updateActions).hasSize(2); - updateActions.forEach( - productUpdateAction -> { - assertThat(productUpdateAction.getAction()).isEqualTo("addExternalImage"); - assertThat(productUpdateAction).isExactlyInstanceOf(AddExternalImage.class); - } - ); - } - - @Test - void buildProductVariantImagesUpdateActions_WithIdenticalImageLists_ShouldNotBuildUpdateActions() { - final ProductVariant oldVariant = mock(ProductVariant.class); - final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); - - final List oldImages = new ArrayList<>(); - oldImages.add(Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel")); - oldImages.add(Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label")); - when(oldVariant.getImages()).thenReturn(oldImages); - - final List newImages = new ArrayList<>(); - newImages.add(Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel")); - newImages.add(Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label")); - when(newVariantDraft.getImages()).thenReturn(newImages); - - final List> updateActions = - buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildProductVariantImagesUpdateActions_WithDifferentImageLists_ShouldBuildAddExternalImageAction() { - final ProductVariant oldVariant = mock(ProductVariant.class); - final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); - - final Image image = Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel"); - final Image image2 = Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); - - final List oldImages = new ArrayList<>(); - oldImages.add(image); - when(oldVariant.getImages()).thenReturn(oldImages); - - final List newImages = new ArrayList<>(); - newImages.add(image); - newImages.add(image2); - when(newVariantDraft.getImages()).thenReturn(newImages); - - final List> updateActions = - buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isExactlyInstanceOf(AddExternalImage.class); - final AddExternalImage updateAction = (AddExternalImage) updateActions.get(0); - assertThat(updateAction.getImage()).isEqualTo(image2); - } - - @Test - void buildProductVariantImagesUpdateActions_WithDifferentImageLists_ShouldBuildRemoveImageAction() { - final ProductVariant oldVariant = mock(ProductVariant.class); - final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); - - final Image image = Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel"); - final Image image2 = Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); - - final List oldImages = new ArrayList<>(); - oldImages.add(image); - oldImages.add(image2); - when(oldVariant.getImages()).thenReturn(oldImages); - - final List newImages = new ArrayList<>(); - newImages.add(image); - when(newVariantDraft.getImages()).thenReturn(newImages); - - final List> updateActions = - buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); - assertThat(updateActions).hasSize(1); - assertThat(updateActions.get(0)).isExactlyInstanceOf(RemoveImage.class); - final RemoveImage updateAction = (RemoveImage) updateActions.get(0); - assertThat(updateAction.getImageUrl()).isEqualTo(image2.getUrl()); - } - - @Test - void buildProductVariantImagesUpdateActions_WithDifferentOrderImageLists_ShouldBuildMovePositionActions() { - final ProductVariant oldVariant = mock(ProductVariant.class); - final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); - - final Image image = Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel"); - final Image image2 = Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); - - final List oldImages = new ArrayList<>(); - oldImages.add(image2); - oldImages.add(image); - when(oldVariant.getImages()).thenReturn(oldImages); - - final List newImages = new ArrayList<>(); - newImages.add(image); - newImages.add(image2); - when(newVariantDraft.getImages()).thenReturn(newImages); - - final List> updateActions = - buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); - assertThat(updateActions).hasSize(2); - updateActions.forEach( - productUpdateAction -> { - assertThat(productUpdateAction.getAction()).isEqualTo("moveImageToPosition"); - assertThat(productUpdateAction).isExactlyInstanceOf(MoveImageToPosition.class); - } - ); - final MoveImageToPosition moveImageToPosition = (MoveImageToPosition) updateActions.get(0); - final MoveImageToPosition moveImage2ToPosition = (MoveImageToPosition) updateActions.get(1); - - assertThat(moveImageToPosition.getImageUrl()).isEqualTo(image2.getUrl()); - assertThat(moveImageToPosition.getPosition()).isEqualTo(newImages.indexOf(image2)); - assertThat(moveImage2ToPosition.getImageUrl()).isEqualTo(image.getUrl()); - assertThat(moveImage2ToPosition.getPosition()).isEqualTo(newImages.indexOf(image)); - } - - @Test - void buildProductVariantImagesUpdateActions_WithDifferentOrderAndImages_ShouldBuildActions() { - final ProductVariant oldVariant = mock(ProductVariant.class); - final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); - - final Image image = Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel"); - final Image image2 = Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); - final Image image3 = Image.of("https://image3.url.com", ImageDimensions.of(2, 2), "image3Label"); - final Image image4 = Image.of("https://image4.url.com", ImageDimensions.of(2, 2), "image4Label"); - final Image image5 = Image.of("https://image5.url.com", ImageDimensions.of(2, 2), "image5Label"); - - final List oldImages = new ArrayList<>(); - oldImages.add(image5); - oldImages.add(image2); - oldImages.add(image); - oldImages.add(image3); - when(oldVariant.getImages()).thenReturn(oldImages); - - final List newImages = new ArrayList<>(); - newImages.add(image4); - newImages.add(image5); - newImages.add(image2); - newImages.add(image3); - when(newVariantDraft.getImages()).thenReturn(newImages); - - final List> updateActions = - buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); - assertThat(updateActions).hasSize(6); - final RemoveImage removeImage = (RemoveImage) updateActions.get(0); - final AddExternalImage addImage = (AddExternalImage) updateActions.get(1); - final MoveImageToPosition moveImage5ToPosition = (MoveImageToPosition) updateActions.get(2); - final MoveImageToPosition moveImage2ToPosition = (MoveImageToPosition) updateActions.get(3); - final MoveImageToPosition moveImage3ToPosition = (MoveImageToPosition) updateActions.get(4); - final MoveImageToPosition moveImage4ToPosition = (MoveImageToPosition) updateActions.get(5); - - assertThat(removeImage).isExactlyInstanceOf(RemoveImage.class); - assertThat(removeImage.getImageUrl()).isEqualTo(image.getUrl()); - - assertThat(addImage).isExactlyInstanceOf(AddExternalImage.class); - assertThat(addImage.getImage()).isEqualTo(image4); - - assertThat(moveImage5ToPosition).isExactlyInstanceOf(MoveImageToPosition.class); - assertThat(moveImage5ToPosition.getImageUrl()).isEqualTo(image5.getUrl()); - assertThat(moveImage5ToPosition.getPosition()).isEqualTo(newImages.indexOf(image5)); - - assertThat(moveImage2ToPosition).isExactlyInstanceOf(MoveImageToPosition.class); - assertThat(moveImage2ToPosition.getImageUrl()).isEqualTo(image2.getUrl()); - assertThat(moveImage2ToPosition.getPosition()).isEqualTo(newImages.indexOf(image2)); - - assertThat(moveImage3ToPosition).isExactlyInstanceOf(MoveImageToPosition.class); - assertThat(moveImage3ToPosition.getImageUrl()).isEqualTo(image3.getUrl()); - assertThat(moveImage3ToPosition.getPosition()).isEqualTo(newImages.indexOf(image3)); - - assertThat(moveImage4ToPosition).isExactlyInstanceOf(MoveImageToPosition.class); - assertThat(moveImage4ToPosition.getImageUrl()).isEqualTo(image4.getUrl()); - assertThat(moveImage4ToPosition.getPosition()).isEqualTo(newImages.indexOf(image4)); - } - - @Test - void buildMoveImageToPositionUpdateActions_WithDifferentOrder_ShouldBuildActions() { - final Image image2 = Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); - final Image image3 = Image.of("https://image3.url.com", ImageDimensions.of(2, 2), "image3Label"); - final Image image4 = Image.of("https://image4.url.com", ImageDimensions.of(2, 2), "image4Label"); - final Image image5 = Image.of("https://image5.url.com", ImageDimensions.of(2, 2), "image5Label"); - - final List oldImages = new ArrayList<>(); - oldImages.add(image5); - oldImages.add(image2); - oldImages.add(image3); - oldImages.add(image4); - - final List newImages = new ArrayList<>(); - newImages.add(image4); - newImages.add(image5); - newImages.add(image2); - newImages.add(image3); - - final List updateActions = - buildMoveImageToPositionUpdateActions(1, oldImages, newImages); - - assertThat(updateActions).containsExactlyInAnyOrder( + @Test + void buildProductVariantImagesUpdateActions_WithNullImageLists_ShouldNotBuildUpdateActions() { + final ProductVariant oldVariant = mock(ProductVariant.class); + final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); + + final List> updateActions = + buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); + assertThat(updateActions).hasSize(0); + } + + @Test + void + buildProductVariantImagesUpdateActions_WithNullNewImageList_ShouldBuildRemoveImageUpdateActions() { + final ProductVariant oldVariant = mock(ProductVariant.class); + final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); + + final List oldImages = new ArrayList<>(); + oldImages.add(Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel")); + oldImages.add(Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label")); + when(oldVariant.getImages()).thenReturn(oldImages); + + final List> updateActions = + buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); + assertThat(updateActions).hasSize(2); + updateActions.forEach( + productUpdateAction -> { + assertThat(productUpdateAction.getAction()).isEqualTo("removeImage"); + assertThat(productUpdateAction).isExactlyInstanceOf(RemoveImage.class); + }); + } + + @Test + void + buildProductVariantImagesUpdateActions_WithNullOldImageList_ShouldBuildAddExternalImageActions() { + final ProductVariant oldVariant = mock(ProductVariant.class); + final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); + + final List newImages = new ArrayList<>(); + newImages.add(Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel")); + newImages.add(Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label")); + when(newVariantDraft.getImages()).thenReturn(newImages); + + final List> updateActions = + buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); + assertThat(updateActions).hasSize(2); + updateActions.forEach( + productUpdateAction -> { + assertThat(productUpdateAction.getAction()).isEqualTo("addExternalImage"); + assertThat(productUpdateAction).isExactlyInstanceOf(AddExternalImage.class); + }); + } + + @Test + void + buildProductVariantImagesUpdateActions_WithIdenticalImageLists_ShouldNotBuildUpdateActions() { + final ProductVariant oldVariant = mock(ProductVariant.class); + final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); + + final List oldImages = new ArrayList<>(); + oldImages.add(Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel")); + oldImages.add(Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label")); + when(oldVariant.getImages()).thenReturn(oldImages); + + final List newImages = new ArrayList<>(); + newImages.add(Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel")); + newImages.add(Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label")); + when(newVariantDraft.getImages()).thenReturn(newImages); + + final List> updateActions = + buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildProductVariantImagesUpdateActions_WithDifferentImageLists_ShouldBuildAddExternalImageAction() { + final ProductVariant oldVariant = mock(ProductVariant.class); + final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); + + final Image image = Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel"); + final Image image2 = + Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); + + final List oldImages = new ArrayList<>(); + oldImages.add(image); + when(oldVariant.getImages()).thenReturn(oldImages); + + final List newImages = new ArrayList<>(); + newImages.add(image); + newImages.add(image2); + when(newVariantDraft.getImages()).thenReturn(newImages); + + final List> updateActions = + buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isExactlyInstanceOf(AddExternalImage.class); + final AddExternalImage updateAction = (AddExternalImage) updateActions.get(0); + assertThat(updateAction.getImage()).isEqualTo(image2); + } + + @Test + void + buildProductVariantImagesUpdateActions_WithDifferentImageLists_ShouldBuildRemoveImageAction() { + final ProductVariant oldVariant = mock(ProductVariant.class); + final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); + + final Image image = Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel"); + final Image image2 = + Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); + + final List oldImages = new ArrayList<>(); + oldImages.add(image); + oldImages.add(image2); + when(oldVariant.getImages()).thenReturn(oldImages); + + final List newImages = new ArrayList<>(); + newImages.add(image); + when(newVariantDraft.getImages()).thenReturn(newImages); + + final List> updateActions = + buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); + assertThat(updateActions).hasSize(1); + assertThat(updateActions.get(0)).isExactlyInstanceOf(RemoveImage.class); + final RemoveImage updateAction = (RemoveImage) updateActions.get(0); + assertThat(updateAction.getImageUrl()).isEqualTo(image2.getUrl()); + } + + @Test + void + buildProductVariantImagesUpdateActions_WithDifferentOrderImageLists_ShouldBuildMovePositionActions() { + final ProductVariant oldVariant = mock(ProductVariant.class); + final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); + + final Image image = Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel"); + final Image image2 = + Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); + + final List oldImages = new ArrayList<>(); + oldImages.add(image2); + oldImages.add(image); + when(oldVariant.getImages()).thenReturn(oldImages); + + final List newImages = new ArrayList<>(); + newImages.add(image); + newImages.add(image2); + when(newVariantDraft.getImages()).thenReturn(newImages); + + final List> updateActions = + buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); + assertThat(updateActions).hasSize(2); + updateActions.forEach( + productUpdateAction -> { + assertThat(productUpdateAction.getAction()).isEqualTo("moveImageToPosition"); + assertThat(productUpdateAction).isExactlyInstanceOf(MoveImageToPosition.class); + }); + final MoveImageToPosition moveImageToPosition = (MoveImageToPosition) updateActions.get(0); + final MoveImageToPosition moveImage2ToPosition = (MoveImageToPosition) updateActions.get(1); + + assertThat(moveImageToPosition.getImageUrl()).isEqualTo(image2.getUrl()); + assertThat(moveImageToPosition.getPosition()).isEqualTo(newImages.indexOf(image2)); + assertThat(moveImage2ToPosition.getImageUrl()).isEqualTo(image.getUrl()); + assertThat(moveImage2ToPosition.getPosition()).isEqualTo(newImages.indexOf(image)); + } + + @Test + void buildProductVariantImagesUpdateActions_WithDifferentOrderAndImages_ShouldBuildActions() { + final ProductVariant oldVariant = mock(ProductVariant.class); + final ProductVariantDraft newVariantDraft = mock(ProductVariantDraft.class); + + final Image image = Image.of("https://image.url.com", ImageDimensions.of(2, 2), "imageLabel"); + final Image image2 = + Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); + final Image image3 = + Image.of("https://image3.url.com", ImageDimensions.of(2, 2), "image3Label"); + final Image image4 = + Image.of("https://image4.url.com", ImageDimensions.of(2, 2), "image4Label"); + final Image image5 = + Image.of("https://image5.url.com", ImageDimensions.of(2, 2), "image5Label"); + + final List oldImages = new ArrayList<>(); + oldImages.add(image5); + oldImages.add(image2); + oldImages.add(image); + oldImages.add(image3); + when(oldVariant.getImages()).thenReturn(oldImages); + + final List newImages = new ArrayList<>(); + newImages.add(image4); + newImages.add(image5); + newImages.add(image2); + newImages.add(image3); + when(newVariantDraft.getImages()).thenReturn(newImages); + + final List> updateActions = + buildProductVariantImagesUpdateActions(oldVariant, newVariantDraft); + assertThat(updateActions).hasSize(6); + final RemoveImage removeImage = (RemoveImage) updateActions.get(0); + final AddExternalImage addImage = (AddExternalImage) updateActions.get(1); + final MoveImageToPosition moveImage5ToPosition = (MoveImageToPosition) updateActions.get(2); + final MoveImageToPosition moveImage2ToPosition = (MoveImageToPosition) updateActions.get(3); + final MoveImageToPosition moveImage3ToPosition = (MoveImageToPosition) updateActions.get(4); + final MoveImageToPosition moveImage4ToPosition = (MoveImageToPosition) updateActions.get(5); + + assertThat(removeImage).isExactlyInstanceOf(RemoveImage.class); + assertThat(removeImage.getImageUrl()).isEqualTo(image.getUrl()); + + assertThat(addImage).isExactlyInstanceOf(AddExternalImage.class); + assertThat(addImage.getImage()).isEqualTo(image4); + + assertThat(moveImage5ToPosition).isExactlyInstanceOf(MoveImageToPosition.class); + assertThat(moveImage5ToPosition.getImageUrl()).isEqualTo(image5.getUrl()); + assertThat(moveImage5ToPosition.getPosition()).isEqualTo(newImages.indexOf(image5)); + + assertThat(moveImage2ToPosition).isExactlyInstanceOf(MoveImageToPosition.class); + assertThat(moveImage2ToPosition.getImageUrl()).isEqualTo(image2.getUrl()); + assertThat(moveImage2ToPosition.getPosition()).isEqualTo(newImages.indexOf(image2)); + + assertThat(moveImage3ToPosition).isExactlyInstanceOf(MoveImageToPosition.class); + assertThat(moveImage3ToPosition.getImageUrl()).isEqualTo(image3.getUrl()); + assertThat(moveImage3ToPosition.getPosition()).isEqualTo(newImages.indexOf(image3)); + + assertThat(moveImage4ToPosition).isExactlyInstanceOf(MoveImageToPosition.class); + assertThat(moveImage4ToPosition.getImageUrl()).isEqualTo(image4.getUrl()); + assertThat(moveImage4ToPosition.getPosition()).isEqualTo(newImages.indexOf(image4)); + } + + @Test + void buildMoveImageToPositionUpdateActions_WithDifferentOrder_ShouldBuildActions() { + final Image image2 = + Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); + final Image image3 = + Image.of("https://image3.url.com", ImageDimensions.of(2, 2), "image3Label"); + final Image image4 = + Image.of("https://image4.url.com", ImageDimensions.of(2, 2), "image4Label"); + final Image image5 = + Image.of("https://image5.url.com", ImageDimensions.of(2, 2), "image5Label"); + + final List oldImages = new ArrayList<>(); + oldImages.add(image5); + oldImages.add(image2); + oldImages.add(image3); + oldImages.add(image4); + + final List newImages = new ArrayList<>(); + newImages.add(image4); + newImages.add(image5); + newImages.add(image2); + newImages.add(image3); + + final List updateActions = + buildMoveImageToPositionUpdateActions(1, oldImages, newImages); + + assertThat(updateActions) + .containsExactlyInAnyOrder( MoveImageToPosition.ofImageUrlAndVariantId(image4.getUrl(), 1, 0, true), MoveImageToPosition.ofImageUrlAndVariantId(image5.getUrl(), 1, 1, true), MoveImageToPosition.ofImageUrlAndVariantId(image2.getUrl(), 1, 2, true), MoveImageToPosition.ofImageUrlAndVariantId(image3.getUrl(), 1, 3, true)); - } - - @Test - void buildMoveImageToPositionUpdateActions_WithSameOrder_ShouldNotBuildActions() { - final Image image2 = Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); - final Image image3 = Image.of("https://image3.url.com", ImageDimensions.of(2, 2), "image3Label"); - final Image image4 = Image.of("https://image4.url.com", ImageDimensions.of(2, 2), "image4Label"); - final Image image5 = Image.of("https://image5.url.com", ImageDimensions.of(2, 2), "image5Label"); - - final List oldImages = new ArrayList<>(); - oldImages.add(image5); - oldImages.add(image2); - oldImages.add(image3); - oldImages.add(image4); - - final List newImages = new ArrayList<>(); - newImages.add(image5); - newImages.add(image2); - newImages.add(image3); - newImages.add(image4); - - final List updateActions = - buildMoveImageToPositionUpdateActions(1, oldImages, newImages); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildMoveImageToPositionUpdateActions_WitEmptyArrays_ShouldNotBuildActions() { - final List oldImages = new ArrayList<>(); - final List newImages = new ArrayList<>(); - - final List updateActions = - buildMoveImageToPositionUpdateActions(1, oldImages, newImages); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildMoveImageToPositionUpdateActions_WithDifferentImages_ShouldThrowException() { - final Image image2 = Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); - final Image image3 = Image.of("https://image3.url.com", ImageDimensions.of(2, 2), "image3Label"); - final Image image4 = Image.of("https://image4.url.com", ImageDimensions.of(2, 2), "image4Label"); - final Image image5 = Image.of("https://image5.url.com", ImageDimensions.of(2, 2), "image5Label"); - - final List oldImages = asList(image5, image2, image3); - final List newImagesWithout5 = asList(image4, image2, image3); - - assertThatThrownBy(() -> buildMoveImageToPositionUpdateActions(1, oldImages, newImagesWithout5)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(format("[%s] not found", image5)); - - final List newImagesWith4 = new ArrayList<>(newImagesWithout5); - newImagesWith4.add(image5); - - assertThatThrownBy(() -> buildMoveImageToPositionUpdateActions(1, oldImages, newImagesWith4)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("size") - .hasMessageContaining(String.valueOf(oldImages.size())) - .hasMessageContaining(String.valueOf(newImagesWith4.size())); - } + } + + @Test + void buildMoveImageToPositionUpdateActions_WithSameOrder_ShouldNotBuildActions() { + final Image image2 = + Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); + final Image image3 = + Image.of("https://image3.url.com", ImageDimensions.of(2, 2), "image3Label"); + final Image image4 = + Image.of("https://image4.url.com", ImageDimensions.of(2, 2), "image4Label"); + final Image image5 = + Image.of("https://image5.url.com", ImageDimensions.of(2, 2), "image5Label"); + + final List oldImages = new ArrayList<>(); + oldImages.add(image5); + oldImages.add(image2); + oldImages.add(image3); + oldImages.add(image4); + + final List newImages = new ArrayList<>(); + newImages.add(image5); + newImages.add(image2); + newImages.add(image3); + newImages.add(image4); + + final List updateActions = + buildMoveImageToPositionUpdateActions(1, oldImages, newImages); + assertThat(updateActions).isEmpty(); + } + + @Test + void buildMoveImageToPositionUpdateActions_WitEmptyArrays_ShouldNotBuildActions() { + final List oldImages = new ArrayList<>(); + final List newImages = new ArrayList<>(); + + final List updateActions = + buildMoveImageToPositionUpdateActions(1, oldImages, newImages); + assertThat(updateActions).isEmpty(); + } + + @Test + void buildMoveImageToPositionUpdateActions_WithDifferentImages_ShouldThrowException() { + final Image image2 = + Image.of("https://image2.url.com", ImageDimensions.of(2, 2), "image2Label"); + final Image image3 = + Image.of("https://image3.url.com", ImageDimensions.of(2, 2), "image3Label"); + final Image image4 = + Image.of("https://image4.url.com", ImageDimensions.of(2, 2), "image4Label"); + final Image image5 = + Image.of("https://image5.url.com", ImageDimensions.of(2, 2), "image5Label"); + + final List oldImages = asList(image5, image2, image3); + final List newImagesWithout5 = asList(image4, image2, image3); + + assertThatThrownBy(() -> buildMoveImageToPositionUpdateActions(1, oldImages, newImagesWithout5)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(format("[%s] not found", image5)); + + final List newImagesWith4 = new ArrayList<>(newImagesWithout5); + newImagesWith4.add(image5); + + assertThatThrownBy(() -> buildMoveImageToPositionUpdateActions(1, oldImages, newImagesWith4)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("size") + .hasMessageContaining(String.valueOf(oldImages.size())) + .hasMessageContaining(String.valueOf(newImagesWith4.size())); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/BuildVariantAssetsUpdateActionsTest.java b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/BuildVariantAssetsUpdateActionsTest.java index 2196fc369b..c235814827 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/BuildVariantAssetsUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/BuildVariantAssetsUpdateActionsTest.java @@ -1,5 +1,17 @@ package com.commercetools.sync.products.utils.productvariantupdateactionutils; +import static com.commercetools.sync.products.ProductSyncMockUtils.getBuilderWithProductTypeRefKey; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantAssetsUpdateActions; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import com.commercetools.sync.products.ProductSyncOptions; @@ -19,375 +31,476 @@ import io.sphere.sdk.products.commands.updateactions.RemoveAsset; import io.sphere.sdk.products.commands.updateactions.SetAssetSources; import io.sphere.sdk.products.commands.updateactions.SetAssetTags; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; - -import static com.commercetools.sync.products.ProductSyncMockUtils.getBuilderWithProductTypeRefKey; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantAssetsUpdateActions; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class BuildVariantAssetsUpdateActionsTest { - private static final String RES_ROOT = - "com/commercetools/sync/products/utils/productVariantUpdateActionUtils/assets/"; - private static final String PRODUCT_WITHOUT_ASSETS = RES_ROOT + "product-without-assets.json"; - private static final String PRODUCT_WITH_ASSETS_ABC = RES_ROOT + "product-with-assets-abc.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_ABC = RES_ROOT + "product-draft-with-assets-abc.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_ABB = RES_ROOT + "product-draft-with-assets-abb.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_ABC_WITH_CHANGES = - RES_ROOT + "product-draft-with-assets-abc-with-changes.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_AB = RES_ROOT + "product-draft-with-assets-ab.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_ABCD = RES_ROOT + "product-draft-with-assets-abcd.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_ABD = RES_ROOT + "product-draft-with-assets-abd.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_CAB = RES_ROOT + "product-draft-with-assets-cab.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_CB = RES_ROOT + "product-draft-with-assets-cb.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_ACBD = RES_ROOT + "product-draft-with-assets-acbd.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_ADBC = RES_ROOT + "product-draft-with-assets-adbc.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_CBD = RES_ROOT + "product-draft-with-assets-cbd.json"; - private static final String PRODUCT_DRAFT_WITH_ASSETS_CBD_WITH_CHANGES = - RES_ROOT + "product-draft-with-assets-cbd-with-changes.json"; - private static final ProductSyncOptions SYNC_OPTIONS = ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - - @Test - void buildProductVariantAssetsUpdateActions_WithNullNewAssetsAndExistingAssets_ShouldBuild3RemoveActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - ProductVariantDraft variant = ProductVariantDraftBuilder.of().build(); - final ProductDraft newProduct = getBuilderWithProductTypeRefKey("productTypeKey").plusVariants(variant).build(); - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProduct, oldMasterVariant, - newProduct.getVariants().get(0), SYNC_OPTIONS); - - assertThat(updateActions).containsExactlyInAnyOrder( + private static final String RES_ROOT = + "com/commercetools/sync/products/utils/productVariantUpdateActionUtils/assets/"; + private static final String PRODUCT_WITHOUT_ASSETS = RES_ROOT + "product-without-assets.json"; + private static final String PRODUCT_WITH_ASSETS_ABC = RES_ROOT + "product-with-assets-abc.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_ABC = + RES_ROOT + "product-draft-with-assets-abc.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_ABB = + RES_ROOT + "product-draft-with-assets-abb.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_ABC_WITH_CHANGES = + RES_ROOT + "product-draft-with-assets-abc-with-changes.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_AB = + RES_ROOT + "product-draft-with-assets-ab.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_ABCD = + RES_ROOT + "product-draft-with-assets-abcd.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_ABD = + RES_ROOT + "product-draft-with-assets-abd.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_CAB = + RES_ROOT + "product-draft-with-assets-cab.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_CB = + RES_ROOT + "product-draft-with-assets-cb.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_ACBD = + RES_ROOT + "product-draft-with-assets-acbd.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_ADBC = + RES_ROOT + "product-draft-with-assets-adbc.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_CBD = + RES_ROOT + "product-draft-with-assets-cbd.json"; + private static final String PRODUCT_DRAFT_WITH_ASSETS_CBD_WITH_CHANGES = + RES_ROOT + "product-draft-with-assets-cbd-with-changes.json"; + private static final ProductSyncOptions SYNC_OPTIONS = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + @Test + void + buildProductVariantAssetsUpdateActions_WithNullNewAssetsAndExistingAssets_ShouldBuild3RemoveActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + ProductVariantDraft variant = ProductVariantDraftBuilder.of().build(); + final ProductDraft newProduct = + getBuilderWithProductTypeRefKey("productTypeKey").plusVariants(variant).build(); + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, + newProduct, + oldMasterVariant, + newProduct.getVariants().get(0), + SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactlyInAnyOrder( RemoveAsset.ofVariantIdWithKey(oldMasterVariant.getId(), "a", true), RemoveAsset.ofVariantIdWithKey(oldMasterVariant.getId(), "b", true), RemoveAsset.ofVariantIdWithKey(oldMasterVariant.getId(), "c", true)); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithNullNewAssetsAndNoOldAssets_ShouldNotBuildActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductVariant productVariant = mock(ProductVariant.class); - when(productVariant.getAssets()).thenReturn(emptyList()); - ProductVariantDraft variant = ProductVariantDraftBuilder.of().build(); - final ProductDraft newProduct = getBuilderWithProductTypeRefKey("productTypeKey").plusVariants(variant).build(); - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProduct, productVariant, - ProductVariantDraftBuilder.of().build().withAssets(null), SYNC_OPTIONS); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithNewAssetsAndNoOldAssets_ShouldBuild3AddActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITHOUT_ASSETS, Product.class); - final ProductVariant productVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABC, ProductDraft.class); - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, productVariant, - newProductDraft.getMasterVariant(), SYNC_OPTIONS); - - assertThat(updateActions).containsExactlyInAnyOrder( - AddAsset.ofVariantId(productVariant.getId(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("a").tags(emptySet()).build()).withStaged(true).withPosition(0), - AddAsset.ofVariantId(productVariant.getId(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("b").tags(emptySet()).build()).withStaged(true).withPosition(1), - AddAsset.ofVariantId(productVariant.getId(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("c").tags(emptySet()).build()).withStaged(true).withPosition(2) - ); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithIdenticalAssets_ShouldNotBuildUpdateActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABC, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithDuplicateAssetKeys_ShouldNotBuildActionsAndTriggerErrorCb() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABB, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final ProductSyncOptions syncOptions = - ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); + } + + @Test + void + buildProductVariantAssetsUpdateActions_WithNullNewAssetsAndNoOldAssets_ShouldNotBuildActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductVariant productVariant = mock(ProductVariant.class); + when(productVariant.getAssets()).thenReturn(emptyList()); + ProductVariantDraft variant = ProductVariantDraftBuilder.of().build(); + final ProductDraft newProduct = + getBuilderWithProductTypeRefKey("productTypeKey").plusVariants(variant).build(); + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, + newProduct, + productVariant, + ProductVariantDraftBuilder.of().build().withAssets(null), + SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildProductVariantAssetsUpdateActions_WithNewAssetsAndNoOldAssets_ShouldBuild3AddActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITHOUT_ASSETS, Product.class); + final ProductVariant productVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABC, ProductDraft.class); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, + newProductDraft, + productVariant, + newProductDraft.getMasterVariant(), + SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactlyInAnyOrder( + AddAsset.ofVariantId( + productVariant.getId(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("a") + .tags(emptySet()) + .build()) + .withStaged(true) + .withPosition(0), + AddAsset.ofVariantId( + productVariant.getId(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("b") + .tags(emptySet()) + .build()) + .withStaged(true) + .withPosition(1), + AddAsset.ofVariantId( + productVariant.getId(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("c") + .tags(emptySet()) + .build()) + .withStaged(true) + .withPosition(2)); + } + + @Test + void buildProductVariantAssetsUpdateActions_WithIdenticalAssets_ShouldNotBuildUpdateActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABC, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildProductVariantAssetsUpdateActions_WithDuplicateAssetKeys_ShouldNotBuildActionsAndTriggerErrorCb() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABB, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); }) - .build(); - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - syncOptions); - - assertThat(updateActions).isEmpty(); - assertThat(errorMessages).hasSize(1); - assertThat(errorMessages.get(0)).matches("Failed to build update actions for the assets of the product " - + "variant with the sku 'mv-sku'. Reason: .*DuplicateKeyException: Supplied asset drafts have " - + "duplicate keys. Asset keys are expected to be unique inside their container \\(a product variant or a " - + "category\\)."); - assertThat(exceptions).hasSize(1); - assertThat(exceptions.get(0)).isExactlyInstanceOf(BuildUpdateActionException.class); - assertThat(exceptions.get(0).getMessage()).contains("Supplied asset drafts have duplicate " - + "keys. Asset keys are expected to be unique inside their container (a product variant or a category)."); - assertThat(exceptions.get(0).getCause()).isExactlyInstanceOf(DuplicateKeyException.class); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithSameAssetPositionButChangesWithin_ShouldBuildUpdateActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABC_WITH_CHANGES, - ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - final HashSet expectedNewTags = new HashSet<>(); - expectedNewTags.add("new tag"); - - assertThat(updateActions).containsExactlyInAnyOrder( - ChangeAssetName.ofAssetKeyAndVariantId(oldMasterVariant.getId(), "a", ofEnglish("asset new name"), true), - ChangeAssetName.ofAssetKeyAndVariantId(oldMasterVariant.getId(), "b", ofEnglish("asset new name 2"), true), - ChangeAssetName.ofAssetKeyAndVariantId(oldMasterVariant.getId(), "c", ofEnglish("asset new name 3"), true), - SetAssetTags.ofVariantIdAndAssetKey(oldMasterVariant.getId(), "a", expectedNewTags, true), - SetAssetSources.ofVariantIdAndAssetKey(oldMasterVariant.getId(), "a", - asList(AssetSourceBuilder.ofUri("new uri").build(), - AssetSourceBuilder.ofUri(null).key("new source").build()), true), - SetAssetSources.ofVariantIdAndAssetKey(oldMasterVariant.getId(), "c", - singletonList(AssetSourceBuilder.ofUri("uri").key("newKey").build()), true) - ); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithOneMissingAsset_ShouldBuildRemoveAssetAction() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_AB, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( - RemoveAsset.ofVariantIdWithKey(oldMasterVariant.getId(), "c", true) - ); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithOneExtraAsset_ShouldBuildAddAssetAction() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABCD, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( - AddAsset.ofVariantId(oldMasterVariant.getId(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build()).withStaged(true).withPosition(3) - ); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithOneAssetSwitch_ShouldBuildRemoveAndAddAssetActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABD, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( + .build(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(errorMessages).hasSize(1); + assertThat(errorMessages.get(0)) + .matches( + "Failed to build update actions for the assets of the product " + + "variant with the sku 'mv-sku'. Reason: .*DuplicateKeyException: Supplied asset drafts have " + + "duplicate keys. Asset keys are expected to be unique inside their container \\(a product variant or a " + + "category\\)."); + assertThat(exceptions).hasSize(1); + assertThat(exceptions.get(0)).isExactlyInstanceOf(BuildUpdateActionException.class); + assertThat(exceptions.get(0).getMessage()) + .contains( + "Supplied asset drafts have duplicate " + + "keys. Asset keys are expected to be unique inside their container (a product variant or a category)."); + assertThat(exceptions.get(0).getCause()).isExactlyInstanceOf(DuplicateKeyException.class); + } + + @Test + void + buildProductVariantAssetsUpdateActions_WithSameAssetPositionButChangesWithin_ShouldBuildUpdateActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABC_WITH_CHANGES, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + final HashSet expectedNewTags = new HashSet<>(); + expectedNewTags.add("new tag"); + + assertThat(updateActions) + .containsExactlyInAnyOrder( + ChangeAssetName.ofAssetKeyAndVariantId( + oldMasterVariant.getId(), "a", ofEnglish("asset new name"), true), + ChangeAssetName.ofAssetKeyAndVariantId( + oldMasterVariant.getId(), "b", ofEnglish("asset new name 2"), true), + ChangeAssetName.ofAssetKeyAndVariantId( + oldMasterVariant.getId(), "c", ofEnglish("asset new name 3"), true), + SetAssetTags.ofVariantIdAndAssetKey( + oldMasterVariant.getId(), "a", expectedNewTags, true), + SetAssetSources.ofVariantIdAndAssetKey( + oldMasterVariant.getId(), + "a", + asList( + AssetSourceBuilder.ofUri("new uri").build(), + AssetSourceBuilder.ofUri(null).key("new source").build()), + true), + SetAssetSources.ofVariantIdAndAssetKey( + oldMasterVariant.getId(), + "c", + singletonList(AssetSourceBuilder.ofUri("uri").key("newKey").build()), + true)); + } + + @Test + void buildProductVariantAssetsUpdateActions_WithOneMissingAsset_ShouldBuildRemoveAssetAction() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_AB, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly(RemoveAsset.ofVariantIdWithKey(oldMasterVariant.getId(), "c", true)); + } + + @Test + void buildProductVariantAssetsUpdateActions_WithOneExtraAsset_ShouldBuildAddAssetAction() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABCD, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + AddAsset.ofVariantId( + oldMasterVariant.getId(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build()) + .withStaged(true) + .withPosition(3)); + } + + @Test + void + buildProductVariantAssetsUpdateActions_WithOneAssetSwitch_ShouldBuildRemoveAndAddAssetActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ABD, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveAsset.ofVariantIdWithKey(oldMasterVariant.getId(), "c", true), - AddAsset.ofVariantId(oldMasterVariant.getId(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build()).withStaged(true).withPosition(2) - ); - - } - - @Test - void buildProductVariantAssetsUpdateActions_WithDifferent_ShouldBuildChangeAssetOrderAction() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_CAB, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( - ChangeAssetOrder.ofVariantId(oldMasterVariant.getId(), asList("3", "1", "2"), true) - ); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_CB, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( + AddAsset.ofVariantId( + oldMasterVariant.getId(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build()) + .withStaged(true) + .withPosition(2)); + } + + @Test + void buildProductVariantAssetsUpdateActions_WithDifferent_ShouldBuildChangeAssetOrderAction() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_CAB, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + ChangeAssetOrder.ofVariantId(oldMasterVariant.getId(), asList("3", "1", "2"), true)); + } + + @Test + void + buildProductVariantAssetsUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_CB, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveAsset.ofVariantIdWithKey(oldMasterVariant.getId(), "a", true), - ChangeAssetOrder.ofVariantId(oldMasterVariant.getId(), asList("3", "2"), true) - ); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ACBD, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( + ChangeAssetOrder.ofVariantId(oldMasterVariant.getId(), asList("3", "2"), true)); + } + + @Test + void + buildProductVariantAssetsUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ACBD, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( ChangeAssetOrder.ofVariantId(oldMasterVariant.getId(), asList("1", "3", "2"), true), - AddAsset.ofVariantId(oldMasterVariant.getId(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build()).withStaged(true).withPosition(3) - ); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithAddedAssetInBetween_ShouldBuildAddWithCorrectPositionActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ADBC, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( - AddAsset.ofVariantId(oldMasterVariant.getId(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build()).withStaged(true).withPosition(1) - ); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveAssetActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_CBD, ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( + AddAsset.ofVariantId( + oldMasterVariant.getId(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build()) + .withStaged(true) + .withPosition(3)); + } + + @Test + void + buildProductVariantAssetsUpdateActions_WithAddedAssetInBetween_ShouldBuildAddWithCorrectPositionActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_ADBC, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + AddAsset.ofVariantId( + oldMasterVariant.getId(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build()) + .withStaged(true) + .withPosition(1)); + } + + @Test + void + buildProductVariantAssetsUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveAssetActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_CBD, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveAsset.ofVariantIdWithKey(oldMasterVariant.getId(), "a", true), ChangeAssetOrder.ofVariantId(oldMasterVariant.getId(), asList("3", "2"), true), - AddAsset.ofVariantId(oldMasterVariant.getId(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build()).withStaged(true).withPosition(2) - ); - } - - @Test - void buildProductVariantAssetsUpdateActions_WithAddedRemovedAndDifOrderAndNewName_ShouldBuildAllDiffAssetActions() { - final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); - final ProductDraft newProductDraft = readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_CBD_WITH_CHANGES, - ProductDraft.class); - - - final ProductVariant oldMasterVariant = oldProduct.getMasterData().getStaged().getMasterVariant(); - final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); - - - final List> updateActions = - buildProductVariantAssetsUpdateActions(oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, - SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( + AddAsset.ofVariantId( + oldMasterVariant.getId(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build()) + .withStaged(true) + .withPosition(2)); + } + + @Test + void + buildProductVariantAssetsUpdateActions_WithAddedRemovedAndDifOrderAndNewName_ShouldBuildAllDiffAssetActions() { + final Product oldProduct = readObjectFromResource(PRODUCT_WITH_ASSETS_ABC, Product.class); + final ProductDraft newProductDraft = + readObjectFromResource(PRODUCT_DRAFT_WITH_ASSETS_CBD_WITH_CHANGES, ProductDraft.class); + + final ProductVariant oldMasterVariant = + oldProduct.getMasterData().getStaged().getMasterVariant(); + final ProductVariantDraft newMasterVariant = newProductDraft.getMasterVariant(); + + final List> updateActions = + buildProductVariantAssetsUpdateActions( + oldProduct, newProductDraft, oldMasterVariant, newMasterVariant, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveAsset.ofVariantIdWithKey(oldMasterVariant.getId(), "a", true), - ChangeAssetName.ofAssetKeyAndVariantId(oldMasterVariant.getId(), "c", ofEnglish("new name"), true), + ChangeAssetName.ofAssetKeyAndVariantId( + oldMasterVariant.getId(), "c", ofEnglish("new name"), true), ChangeAssetOrder.ofVariantId(oldMasterVariant.getId(), asList("3", "2"), true), - AddAsset.ofVariantId(oldMasterVariant.getId(), - AssetDraftBuilder.of(singletonList(AssetSourceBuilder.ofUri("uri").build()), ofEnglish("asset name")) - .key("d").tags(emptySet()).build()).withStaged(true).withPosition(2) - ); - } + AddAsset.ofVariantId( + oldMasterVariant.getId(), + AssetDraftBuilder.of( + singletonList(AssetSourceBuilder.ofUri("uri").build()), + ofEnglish("asset name")) + .key("d") + .tags(emptySet()) + .build()) + .withStaged(true) + .withPosition(2)); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/ProductVariantUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/ProductVariantUpdateActionUtilsTest.java index 106d80612b..8b83918df5 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/ProductVariantUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/ProductVariantUpdateActionUtilsTest.java @@ -1,72 +1,71 @@ package com.commercetools.sync.products.utils.productvariantupdateactionutils; -import io.sphere.sdk.products.ProductVariant; -import io.sphere.sdk.products.ProductVariantDraft; -import io.sphere.sdk.products.commands.updateactions.SetSku; -import org.junit.jupiter.api.Test; - import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantSkuUpdateAction; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class ProductVariantUpdateActionUtilsTest { +import io.sphere.sdk.products.ProductVariant; +import io.sphere.sdk.products.ProductVariantDraft; +import io.sphere.sdk.products.commands.updateactions.SetSku; +import org.junit.jupiter.api.Test; - @Test - void buildProductVariantSkuUpdateAction_WithBothNullSkus_ShouldNotBuildAction() { - final ProductVariant variantOld = mock(ProductVariant.class); - final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); +class ProductVariantUpdateActionUtilsTest { - assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)).isEmpty(); - } + @Test + void buildProductVariantSkuUpdateAction_WithBothNullSkus_ShouldNotBuildAction() { + final ProductVariant variantOld = mock(ProductVariant.class); + final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); - @Test - void buildProductVariantSkuUpdateAction_WithNullNewSku_ShouldBuildUpdateAction() { - final ProductVariant variantOld = mock(ProductVariant.class); - final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); + assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)).isEmpty(); + } - when(variantOld.getSku()).thenReturn("sku-old"); - when(variantOld.getId()).thenReturn(42); + @Test + void buildProductVariantSkuUpdateAction_WithNullNewSku_ShouldBuildUpdateAction() { + final ProductVariant variantOld = mock(ProductVariant.class); + final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); - assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)) - .contains(SetSku.of(42, null, true)); - } + when(variantOld.getSku()).thenReturn("sku-old"); + when(variantOld.getId()).thenReturn(42); - @Test - void buildProductVariantSkuUpdateAction_WithNewSku_ShouldBuildUpdateAction() { - final ProductVariant variantOld = mock(ProductVariant.class); - final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); + assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)) + .contains(SetSku.of(42, null, true)); + } - when(variantOld.getSku()).thenReturn("sku-old"); - when(variantOld.getId()).thenReturn(42); - when(variantDraftNew.getSku()).thenReturn("sku-new"); + @Test + void buildProductVariantSkuUpdateAction_WithNewSku_ShouldBuildUpdateAction() { + final ProductVariant variantOld = mock(ProductVariant.class); + final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); + when(variantOld.getSku()).thenReturn("sku-old"); + when(variantOld.getId()).thenReturn(42); + when(variantDraftNew.getSku()).thenReturn("sku-new"); - assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)) - .contains(SetSku.of(42, "sku-new", true)); - } + assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)) + .contains(SetSku.of(42, "sku-new", true)); + } - @Test - void buildProductVariantSkuUpdateAction_WithSameSku_ShouldNotBuildUpdateAction() { - final ProductVariant variantOld = mock(ProductVariant.class); - final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); + @Test + void buildProductVariantSkuUpdateAction_WithSameSku_ShouldNotBuildUpdateAction() { + final ProductVariant variantOld = mock(ProductVariant.class); + final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); - when(variantOld.getSku()).thenReturn("sku-the-same"); - when(variantDraftNew.getSku()).thenReturn("sku-the-same"); + when(variantOld.getSku()).thenReturn("sku-the-same"); + when(variantDraftNew.getSku()).thenReturn("sku-the-same"); - assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)).isEmpty(); - } + assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)).isEmpty(); + } - @Test - void buildProductVariantSkuUpdateAction_WithNullOldSku_ShouldBuildUpdateAction() { - final ProductVariant variantOld = mock(ProductVariant.class); - final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); + @Test + void buildProductVariantSkuUpdateAction_WithNullOldSku_ShouldBuildUpdateAction() { + final ProductVariant variantOld = mock(ProductVariant.class); + final ProductVariantDraft variantDraftNew = mock(ProductVariantDraft.class); - when(variantOld.getSku()).thenReturn(null); - when(variantOld.getId()).thenReturn(42); - when(variantDraftNew.getSku()).thenReturn("sku-new"); + when(variantOld.getSku()).thenReturn(null); + when(variantOld.getId()).thenReturn(42); + when(variantDraftNew.getSku()).thenReturn("sku-new"); - assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)) - .contains(SetSku.of(42, "sku-new", true)); - } -} \ No newline at end of file + assertThat(buildProductVariantSkuUpdateAction(variantOld, variantDraftNew)) + .contains(SetSku.of(42, "sku-new", true)); + } +} diff --git a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/AttributeFixtures.java b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/AttributeFixtures.java index e12b4720bd..f64f2d09da 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/AttributeFixtures.java +++ b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/AttributeFixtures.java @@ -1,46 +1,45 @@ package com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes; -import io.sphere.sdk.products.attributes.Attribute; - import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import io.sphere.sdk.products.attributes.Attribute; + public final class AttributeFixtures { - public static final Attribute BOOLEAN_ATTRIBUTE_TRUE = - readObjectFromResource("boolean-attribute-true.json", Attribute.class); - public static final Attribute BOOLEAN_ATTRIBUTE_FALSE = - readObjectFromResource("boolean-attribute-false.json", Attribute.class); - public static final Attribute TEXT_ATTRIBUTE_BAR = - readObjectFromResource("text-attribute-bar.json", Attribute.class); - public static final Attribute TEXT_ATTRIBUTE_FOO = - readObjectFromResource("text-attribute-foo.json", Attribute.class); - public static final Attribute LTEXT_ATTRIBUTE_EN_BAR = - readObjectFromResource("ltext-attribute-en-bar.json", Attribute.class); - public static final Attribute ENUM_ATTRIBUTE_BARLABEL_BARKEY = - readObjectFromResource("enum-attribute-barLabel-barKey.json", Attribute.class); - public static final Attribute LENUM_ATTRIBUTE_EN_BAR = - readObjectFromResource("lenum-attribute-en-bar.json", Attribute.class); - public static final Attribute NUMBER_ATTRIBUTE_10 = - readObjectFromResource("number-attribute-10.json", Attribute.class); - public static final Attribute MONEY_ATTRIBUTE_EUR_2300 = - readObjectFromResource("money-attribute-eur-2300.json", Attribute.class); - public static final Attribute DATE_ATTRIBUTE_2017_11_09 = - readObjectFromResource("date-attribute-2017-11-09.json", Attribute.class); - public static final Attribute TIME_ATTRIBUTE_10_08_46 = - readObjectFromResource("time-attribute-10-08-46.json", Attribute.class); - public static final Attribute DATE_TIME_ATTRIBUTE_2016_05_20T01_02_46 = - readObjectFromResource("datetime-attribute-2016-05-20T01-02-46.json", Attribute.class); - public static final Attribute PRODUCT_REFERENCE_ATTRIBUTE = - readObjectFromResource("product-reference-attribute.json", Attribute.class); - public static final Attribute CATEGORY_REFERENCE_ATTRIBUTE = - readObjectFromResource("category-reference-attribute.json", Attribute.class); - public static final Attribute LTEXT_SET_ATTRIBUTE = - readObjectFromResource("ltext-set-attribute.json", Attribute.class); - public static final Attribute PRODUCT_REFERENCE_SET_ATTRIBUTE = - readObjectFromResource("product-reference-set-attribute.json", Attribute.class); - public static final Attribute EMPTY_SET_ATTRIBUTE = - readObjectFromResource("empty-set-attribute.json", Attribute.class); + public static final Attribute BOOLEAN_ATTRIBUTE_TRUE = + readObjectFromResource("boolean-attribute-true.json", Attribute.class); + public static final Attribute BOOLEAN_ATTRIBUTE_FALSE = + readObjectFromResource("boolean-attribute-false.json", Attribute.class); + public static final Attribute TEXT_ATTRIBUTE_BAR = + readObjectFromResource("text-attribute-bar.json", Attribute.class); + public static final Attribute TEXT_ATTRIBUTE_FOO = + readObjectFromResource("text-attribute-foo.json", Attribute.class); + public static final Attribute LTEXT_ATTRIBUTE_EN_BAR = + readObjectFromResource("ltext-attribute-en-bar.json", Attribute.class); + public static final Attribute ENUM_ATTRIBUTE_BARLABEL_BARKEY = + readObjectFromResource("enum-attribute-barLabel-barKey.json", Attribute.class); + public static final Attribute LENUM_ATTRIBUTE_EN_BAR = + readObjectFromResource("lenum-attribute-en-bar.json", Attribute.class); + public static final Attribute NUMBER_ATTRIBUTE_10 = + readObjectFromResource("number-attribute-10.json", Attribute.class); + public static final Attribute MONEY_ATTRIBUTE_EUR_2300 = + readObjectFromResource("money-attribute-eur-2300.json", Attribute.class); + public static final Attribute DATE_ATTRIBUTE_2017_11_09 = + readObjectFromResource("date-attribute-2017-11-09.json", Attribute.class); + public static final Attribute TIME_ATTRIBUTE_10_08_46 = + readObjectFromResource("time-attribute-10-08-46.json", Attribute.class); + public static final Attribute DATE_TIME_ATTRIBUTE_2016_05_20T01_02_46 = + readObjectFromResource("datetime-attribute-2016-05-20T01-02-46.json", Attribute.class); + public static final Attribute PRODUCT_REFERENCE_ATTRIBUTE = + readObjectFromResource("product-reference-attribute.json", Attribute.class); + public static final Attribute CATEGORY_REFERENCE_ATTRIBUTE = + readObjectFromResource("category-reference-attribute.json", Attribute.class); + public static final Attribute LTEXT_SET_ATTRIBUTE = + readObjectFromResource("ltext-set-attribute.json", Attribute.class); + public static final Attribute PRODUCT_REFERENCE_SET_ATTRIBUTE = + readObjectFromResource("product-reference-set-attribute.json", Attribute.class); + public static final Attribute EMPTY_SET_ATTRIBUTE = + readObjectFromResource("empty-set-attribute.json", Attribute.class); - private AttributeFixtures() { - } + private AttributeFixtures() {} } diff --git a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/BuildProductVariantAttributeUpdateActionsTest.java b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/BuildProductVariantAttributeUpdateActionsTest.java index e3d36eea93..bacdbfa6d3 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/BuildProductVariantAttributeUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/BuildProductVariantAttributeUpdateActionsTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes; +import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA; +import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.buildProductVariantAttributeUpdateAction; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.products.AttributeMetaData; import com.fasterxml.jackson.databind.node.JsonNodeFactory; @@ -13,146 +20,160 @@ import io.sphere.sdk.products.attributes.StringAttributeType; import io.sphere.sdk.products.commands.updateactions.SetAttribute; import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.Map; import java.util.Optional; - -import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA; -import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.buildProductVariantAttributeUpdateAction; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.Test; class BuildProductVariantAttributeUpdateActionsTest { - @Test - void withNullOldAndNonNullNew_ShouldBuildSetAction() throws BuildUpdateActionException { - - // Preparation - final int variantId = 1; - final Attribute oldAttribute = null; - final AttributeDraft newAttribute = AttributeDraft.of("foo", JsonNodeFactory.instance.objectNode()); - final Map attributesMetaData = new HashMap<>(); - final AttributeDefinition attributeDefinition = - AttributeDefinitionBuilder.of(newAttribute.getName(), ofEnglish("foo"), StringAttributeType.of()) - .build(); - attributesMetaData.put(newAttribute.getName(), AttributeMetaData.of(attributeDefinition)); - - // Test - final Optional> actionOptional = buildProductVariantAttributeUpdateAction(variantId, - oldAttribute, newAttribute, attributesMetaData); - - // Assertion - assertThat(actionOptional) - .contains(SetAttribute.of(variantId, newAttribute.getName(), newAttribute.getValue(), true)); - } - - @Test - void withNullOldAndNonNullNew_WithSameForAllAttribute_ShouldBuildSetAllAction() - throws BuildUpdateActionException { - - // Preparation - final Attribute oldAttribute = null; - final AttributeDraft newAttribute = AttributeDraft.of("foo", JsonNodeFactory.instance.objectNode()); - final Map attributesMetaData = new HashMap<>(); - final AttributeDefinition attributeDefinition = - AttributeDefinitionBuilder.of(newAttribute.getName(), ofEnglish("foo"), StringAttributeType.of()) - .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) - .build(); - attributesMetaData.put(newAttribute.getName(), AttributeMetaData.of(attributeDefinition)); - - // Test - final Optional> actionOptional = buildProductVariantAttributeUpdateAction(1, - oldAttribute, newAttribute, attributesMetaData); - - // Assertion - assertThat(actionOptional) - .contains(SetAttributeInAllVariants.of(newAttribute.getName(), newAttribute.getValue(), true)); - } - - @Test - void withNullOldAndNonNullNew_WithNoExistingAttributeInMetaData_ShouldThrowException() { - - // Preparation - final Attribute oldAttribute = null; - final AttributeDraft newAttribute = AttributeDraft.of("foo", JsonNodeFactory.instance.objectNode()); - final Map attributesMetaData = new HashMap<>(); - - // Test and assertion - assertThatThrownBy(() -> buildProductVariantAttributeUpdateAction(1, oldAttribute, newAttribute, - attributesMetaData)) - .hasMessage(format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, newAttribute.getName())) - .isExactlyInstanceOf(BuildUpdateActionException.class); - } - - @Test - void withDifferentValues_ShouldBuildSetAction() throws BuildUpdateActionException { - // Preparation - final Integer variantId = 1; - final Attribute oldAttribute = Attribute.of("foo", JsonNodeFactory.instance.textNode("bar")); - final AttributeDraft newAttribute = AttributeDraft.of("foo", JsonNodeFactory.instance.textNode("other-bar")); - final Map attributesMetaData = new HashMap<>(); - final AttributeDefinition attributeDefinition = - AttributeDefinitionBuilder.of(newAttribute.getName(), ofEnglish("foo"), StringAttributeType.of()) - .build(); - attributesMetaData.put(newAttribute.getName(), AttributeMetaData.of(attributeDefinition)); - - // Test - final Optional> actionOptional = buildProductVariantAttributeUpdateAction(variantId, - oldAttribute, newAttribute, attributesMetaData); - - // Assertion - assertThat(actionOptional) - .contains(SetAttribute.of(variantId, newAttribute.getName(), newAttribute.getValue(), true)); - } - - @Test - void withSameValues_ShouldNotBuildAction() throws BuildUpdateActionException { - // Preparation - final Attribute oldAttribute = Attribute.of("foo", JsonNodeFactory.instance.textNode("foo")); - final AttributeDraft newAttribute = AttributeDraft.of("foo", JsonNodeFactory.instance.textNode("foo")); - final Map attributesMetaData = new HashMap<>(); - final AttributeDefinition attributeDefinition = - AttributeDefinitionBuilder.of(newAttribute.getName(), ofEnglish("foo"), StringAttributeType.of()) - .build(); - attributesMetaData.put(newAttribute.getName(), AttributeMetaData.of(attributeDefinition)); - - // Test - final Optional> actionOptional = buildProductVariantAttributeUpdateAction(1, oldAttribute, - newAttribute, attributesMetaData); - - // Assertion - assertThat(actionOptional).isEmpty(); - } - - @Test - void withDifferentValues_WithNoExistingAttributeInMetaData_ShouldThrowException() { - // Preparation - final Attribute oldAttribute = Attribute.of("foo", JsonNodeFactory.instance.textNode("bar")); - final AttributeDraft newAttribute = AttributeDraft.of("foo", JsonNodeFactory.instance.textNode("other-bar")); - final Map attributesMetaData = new HashMap<>(); - - // Test and assertion - assertThatThrownBy(() -> buildProductVariantAttributeUpdateAction(1, oldAttribute, newAttribute, - attributesMetaData)) - .hasMessage(format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, newAttribute.getName())) - .isExactlyInstanceOf(BuildUpdateActionException.class); - } - - @Test - void withSameValues_WithNoExistingAttributeInMetaData_ShouldThrowException() { - // Preparation - final Attribute oldAttribute = Attribute.of("foo", JsonNodeFactory.instance.textNode("foo")); - final AttributeDraft newAttribute = AttributeDraft.of("foo", JsonNodeFactory.instance.textNode("foo")); - final Map attributesMetaData = new HashMap<>(); - - // Test and assertion - assertThatThrownBy(() -> buildProductVariantAttributeUpdateAction(1, oldAttribute, newAttribute, - attributesMetaData)) - .hasMessage(format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, newAttribute.getName())) - .isExactlyInstanceOf(BuildUpdateActionException.class); - } + @Test + void withNullOldAndNonNullNew_ShouldBuildSetAction() throws BuildUpdateActionException { + + // Preparation + final int variantId = 1; + final Attribute oldAttribute = null; + final AttributeDraft newAttribute = + AttributeDraft.of("foo", JsonNodeFactory.instance.objectNode()); + final Map attributesMetaData = new HashMap<>(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + newAttribute.getName(), ofEnglish("foo"), StringAttributeType.of()) + .build(); + attributesMetaData.put(newAttribute.getName(), AttributeMetaData.of(attributeDefinition)); + + // Test + final Optional> actionOptional = + buildProductVariantAttributeUpdateAction( + variantId, oldAttribute, newAttribute, attributesMetaData); + + // Assertion + assertThat(actionOptional) + .contains( + SetAttribute.of(variantId, newAttribute.getName(), newAttribute.getValue(), true)); + } + + @Test + void withNullOldAndNonNullNew_WithSameForAllAttribute_ShouldBuildSetAllAction() + throws BuildUpdateActionException { + + // Preparation + final Attribute oldAttribute = null; + final AttributeDraft newAttribute = + AttributeDraft.of("foo", JsonNodeFactory.instance.objectNode()); + final Map attributesMetaData = new HashMap<>(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + newAttribute.getName(), ofEnglish("foo"), StringAttributeType.of()) + .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) + .build(); + attributesMetaData.put(newAttribute.getName(), AttributeMetaData.of(attributeDefinition)); + + // Test + final Optional> actionOptional = + buildProductVariantAttributeUpdateAction(1, oldAttribute, newAttribute, attributesMetaData); + + // Assertion + assertThat(actionOptional) + .contains( + SetAttributeInAllVariants.of(newAttribute.getName(), newAttribute.getValue(), true)); + } + + @Test + void withNullOldAndNonNullNew_WithNoExistingAttributeInMetaData_ShouldThrowException() { + + // Preparation + final Attribute oldAttribute = null; + final AttributeDraft newAttribute = + AttributeDraft.of("foo", JsonNodeFactory.instance.objectNode()); + final Map attributesMetaData = new HashMap<>(); + + // Test and assertion + assertThatThrownBy( + () -> + buildProductVariantAttributeUpdateAction( + 1, oldAttribute, newAttribute, attributesMetaData)) + .hasMessage(format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, newAttribute.getName())) + .isExactlyInstanceOf(BuildUpdateActionException.class); + } + + @Test + void withDifferentValues_ShouldBuildSetAction() throws BuildUpdateActionException { + // Preparation + final Integer variantId = 1; + final Attribute oldAttribute = Attribute.of("foo", JsonNodeFactory.instance.textNode("bar")); + final AttributeDraft newAttribute = + AttributeDraft.of("foo", JsonNodeFactory.instance.textNode("other-bar")); + final Map attributesMetaData = new HashMap<>(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + newAttribute.getName(), ofEnglish("foo"), StringAttributeType.of()) + .build(); + attributesMetaData.put(newAttribute.getName(), AttributeMetaData.of(attributeDefinition)); + + // Test + final Optional> actionOptional = + buildProductVariantAttributeUpdateAction( + variantId, oldAttribute, newAttribute, attributesMetaData); + + // Assertion + assertThat(actionOptional) + .contains( + SetAttribute.of(variantId, newAttribute.getName(), newAttribute.getValue(), true)); + } + + @Test + void withSameValues_ShouldNotBuildAction() throws BuildUpdateActionException { + // Preparation + final Attribute oldAttribute = Attribute.of("foo", JsonNodeFactory.instance.textNode("foo")); + final AttributeDraft newAttribute = + AttributeDraft.of("foo", JsonNodeFactory.instance.textNode("foo")); + final Map attributesMetaData = new HashMap<>(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + newAttribute.getName(), ofEnglish("foo"), StringAttributeType.of()) + .build(); + attributesMetaData.put(newAttribute.getName(), AttributeMetaData.of(attributeDefinition)); + + // Test + final Optional> actionOptional = + buildProductVariantAttributeUpdateAction(1, oldAttribute, newAttribute, attributesMetaData); + + // Assertion + assertThat(actionOptional).isEmpty(); + } + + @Test + void withDifferentValues_WithNoExistingAttributeInMetaData_ShouldThrowException() { + // Preparation + final Attribute oldAttribute = Attribute.of("foo", JsonNodeFactory.instance.textNode("bar")); + final AttributeDraft newAttribute = + AttributeDraft.of("foo", JsonNodeFactory.instance.textNode("other-bar")); + final Map attributesMetaData = new HashMap<>(); + + // Test and assertion + assertThatThrownBy( + () -> + buildProductVariantAttributeUpdateAction( + 1, oldAttribute, newAttribute, attributesMetaData)) + .hasMessage(format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, newAttribute.getName())) + .isExactlyInstanceOf(BuildUpdateActionException.class); + } + + @Test + void withSameValues_WithNoExistingAttributeInMetaData_ShouldThrowException() { + // Preparation + final Attribute oldAttribute = Attribute.of("foo", JsonNodeFactory.instance.textNode("foo")); + final AttributeDraft newAttribute = + AttributeDraft.of("foo", JsonNodeFactory.instance.textNode("foo")); + final Map attributesMetaData = new HashMap<>(); + + // Test and assertion + assertThatThrownBy( + () -> + buildProductVariantAttributeUpdateAction( + 1, oldAttribute, newAttribute, attributesMetaData)) + .hasMessage(format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, newAttribute.getName())) + .isExactlyInstanceOf(BuildUpdateActionException.class); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/BuildProductVariantAttributesUpdateActionsTest.java b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/BuildProductVariantAttributesUpdateActionsTest.java index 6c246dd213..92748a298d 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/BuildProductVariantAttributesUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/attributes/BuildProductVariantAttributesUpdateActionsTest.java @@ -1,5 +1,23 @@ package com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes; +import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.NULL_PRODUCT_VARIANT_ATTRIBUTE; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantAttributesUpdateActions; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.BOOLEAN_ATTRIBUTE_FALSE; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.BOOLEAN_ATTRIBUTE_TRUE; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.TEXT_ATTRIBUTE_BAR; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.TEXT_ATTRIBUTE_FOO; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.TIME_ATTRIBUTE_10_08_46; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.AttributeMetaData; import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; @@ -20,671 +38,904 @@ import io.sphere.sdk.products.attributes.TimeAttributeType; import io.sphere.sdk.products.commands.updateactions.SetAttribute; import io.sphere.sdk.products.commands.updateactions.SetAttributeInAllVariants; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; - -import static com.commercetools.sync.products.utils.ProductVariantAttributeUpdateActionUtils.ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.NULL_PRODUCT_VARIANT_ATTRIBUTE; -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantAttributesUpdateActions; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.BOOLEAN_ATTRIBUTE_FALSE; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.BOOLEAN_ATTRIBUTE_TRUE; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.TEXT_ATTRIBUTE_BAR; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.TEXT_ATTRIBUTE_FOO; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.attributes.AttributeFixtures.TIME_ATTRIBUTE_10_08_46; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class BuildProductVariantAttributesUpdateActionsTest { - private final Product oldProduct = mock(Product.class); - private final ProductDraft newProductDraft = mock(ProductDraft.class); - private final ProductVariant oldProductVariant = mock(ProductVariant.class); - private final ProductVariantDraft newProductVariant = mock(ProductVariantDraft.class); - private List errorMessages; - private final ProductSyncOptions syncOptions = - ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorMessages.add(exception.getMessage())) + private final Product oldProduct = mock(Product.class); + private final ProductDraft newProductDraft = mock(ProductDraft.class); + private final ProductVariant oldProductVariant = mock(ProductVariant.class); + private final ProductVariantDraft newProductVariant = mock(ProductVariantDraft.class); + private List errorMessages; + private final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorMessages.add(exception.getMessage())) + .build(); + + @BeforeEach + void setupMethod() { + errorMessages = new ArrayList<>(); + } + + @Test + void withNullNewAttributesAndEmptyExistingAttributes_ShouldNotBuildActions() { + // Preparation + when(newProductVariant.getAttributes()).thenReturn(null); + when(oldProductVariant.getAttributes()).thenReturn(emptyList()); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + new HashMap<>(), + syncOptions); + + // Assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void withSomeNullNewAttributesAndExistingAttributes_ShouldBuildActionsAndTriggerErrorCallback() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + asList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), null); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) .build(); - - @BeforeEach - void setupMethod() { - errorMessages = new ArrayList<>(); - } - - @Test - void withNullNewAttributesAndEmptyExistingAttributes_ShouldNotBuildActions() { - // Preparation - when(newProductVariant.getAttributes()).thenReturn(null); - when(oldProductVariant.getAttributes()).thenReturn(emptyList()); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, new HashMap<>(), syncOptions); - - // Assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void withSomeNullNewAttributesAndExistingAttributes_ShouldBuildActionsAndTriggerErrorCallback() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = asList( - AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), null); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly(SetAttribute.ofUnsetAttribute(oldProductVariant.getId(), - TEXT_ATTRIBUTE_BAR.getName(), true)); - assertThat(errorMessages).containsExactly(format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, null, - variantKey, productKey, NULL_PRODUCT_VARIANT_ATTRIBUTE)); - } - - @Test - void withEmptyNewAttributesAndEmptyExistingAttributes_ShouldNotBuildActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - when(oldProductVariant.getAttributes()).thenReturn(emptyList()); - when(oldProductVariant.getKey()).thenReturn(variantKey); - when(newProductVariant.getAttributes()).thenReturn(emptyList()); - when(newProductVariant.getKey()).thenReturn(variantKey); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, new HashMap<>(), syncOptions); - - // Assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void withAllMatchingAttributes_ShouldNotBuildActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = asList( - AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), - AttributeDraftBuilder.of(TEXT_ATTRIBUTE_BAR).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void withNonEmptyNewAttributesButEmptyExistingAttributes_ShouldBuildSetAttributeActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = asList( + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( + SetAttribute.ofUnsetAttribute( + oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true)); + assertThat(errorMessages) + .containsExactly( + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + null, + variantKey, + productKey, + NULL_PRODUCT_VARIANT_ATTRIBUTE)); + } + + @Test + void withEmptyNewAttributesAndEmptyExistingAttributes_ShouldNotBuildActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + when(oldProductVariant.getAttributes()).thenReturn(emptyList()); + when(oldProductVariant.getKey()).thenReturn(variantKey); + when(newProductVariant.getAttributes()).thenReturn(emptyList()); + when(newProductVariant.getKey()).thenReturn(variantKey); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + new HashMap<>(), + syncOptions); + + // Assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void withAllMatchingAttributes_ShouldNotBuildActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + asList( AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), AttributeDraftBuilder.of(TEXT_ATTRIBUTE_BAR).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - when(oldProductVariant.getAttributes()).thenReturn(emptyList()); - when(oldProductVariant.getKey()).thenReturn(variantKey); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void withNonEmptyNewAttributesButEmptyExistingAttributes_ShouldBuildSetAttributeActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + asList( + AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), + AttributeDraftBuilder.of(TEXT_ATTRIBUTE_BAR).build()); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + when(oldProductVariant.getAttributes()).thenReturn(emptyList()); + when(oldProductVariant.getKey()).thenReturn(variantKey); - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( SetAttribute.of(oldProductVariant.getId(), newAttributes.get(0), true), - SetAttribute.of(oldProductVariant.getId(), newAttributes.get(1), true) - ); - } - - @Test - void withSomeNonChangedMatchingAttributesAndNewAttributes_ShouldBuildSetAttributeActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = asList( + SetAttribute.of(oldProductVariant.getId(), newAttributes.get(1), true)); + } + + @Test + void withSomeNonChangedMatchingAttributesAndNewAttributes_ShouldBuildSetAttributeActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + asList( AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), AttributeDraftBuilder.of(TEXT_ATTRIBUTE_BAR).build(), AttributeDraftBuilder.of(TIME_ATTRIBUTE_10_08_46).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - final AttributeDefinition timeAttributeDefinition = - AttributeDefinitionBuilder.of(TIME_ATTRIBUTE_10_08_46.getName(), ofEnglish("label"), - TimeAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - attributesMetaData.put(TIME_ATTRIBUTE_10_08_46.getName(), AttributeMetaData.of(timeAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly( - SetAttribute.of(oldProductVariant.getId(), AttributeDraftBuilder.of(TIME_ATTRIBUTE_10_08_46).build(), true) - ); - } - - @Test - void withNullNewAttributes_ShouldBuildUnSetAttributeActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - when(newProductVariant.getAttributes()).thenReturn(null); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly( - SetAttribute.ofUnsetAttribute(oldProductVariant.getId(), BOOLEAN_ATTRIBUTE_TRUE.getName(), true), - SetAttribute.ofUnsetAttribute(oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true) - ); - } - - @Test - void withNullNewAttributes_WithSameForAllAttributes_ShouldBuildUnSetAllAttributeActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - when(newProductVariant.getAttributes()).thenReturn(null); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()) - .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) - .build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) - .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) - .build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly( + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + final AttributeDefinition timeAttributeDefinition = + AttributeDefinitionBuilder.of( + TIME_ATTRIBUTE_10_08_46.getName(), ofEnglish("label"), TimeAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + attributesMetaData.put( + TIME_ATTRIBUTE_10_08_46.getName(), AttributeMetaData.of(timeAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( + SetAttribute.of( + oldProductVariant.getId(), + AttributeDraftBuilder.of(TIME_ATTRIBUTE_10_08_46).build(), + true)); + } + + @Test + void withNullNewAttributes_ShouldBuildUnSetAttributeActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + when(newProductVariant.getAttributes()).thenReturn(null); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( + SetAttribute.ofUnsetAttribute( + oldProductVariant.getId(), BOOLEAN_ATTRIBUTE_TRUE.getName(), true), + SetAttribute.ofUnsetAttribute( + oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true)); + } + + @Test + void withNullNewAttributes_WithSameForAllAttributes_ShouldBuildUnSetAllAttributeActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + when(newProductVariant.getAttributes()).thenReturn(null); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( SetAttributeInAllVariants.ofUnsetAttribute(BOOLEAN_ATTRIBUTE_TRUE.getName(), true), - SetAttributeInAllVariants.ofUnsetAttribute(TEXT_ATTRIBUTE_BAR.getName(), true) - ); - } - - @Test - void withEmptyNewAttributes_ShouldBuildUnSetAttributeActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - when(newProductVariant.getAttributes()).thenReturn(emptyList()); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly( - SetAttribute.ofUnsetAttribute(oldProductVariant.getId(), BOOLEAN_ATTRIBUTE_TRUE.getName(), true), - SetAttribute.ofUnsetAttribute(oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true) - ); - } - - @Test - void withEmptyNewAttributes_WithNoExistingAttributeInMetaData_ShouldBuildNoActionsAndTriggerErrorCallback() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - when(newProductVariant.getAttributes()).thenReturn(emptyList()); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).isEmpty(); - assertThat(errorMessages).containsExactlyInAnyOrder( - format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, TEXT_ATTRIBUTE_BAR.getName(), variantKey, productKey, + SetAttributeInAllVariants.ofUnsetAttribute(TEXT_ATTRIBUTE_BAR.getName(), true)); + } + + @Test + void withEmptyNewAttributes_ShouldBuildUnSetAttributeActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + when(newProductVariant.getAttributes()).thenReturn(emptyList()); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( + SetAttribute.ofUnsetAttribute( + oldProductVariant.getId(), BOOLEAN_ATTRIBUTE_TRUE.getName(), true), + SetAttribute.ofUnsetAttribute( + oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true)); + } + + @Test + void + withEmptyNewAttributes_WithNoExistingAttributeInMetaData_ShouldBuildNoActionsAndTriggerErrorCallback() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + when(newProductVariant.getAttributes()).thenReturn(emptyList()); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions).isEmpty(); + assertThat(errorMessages) + .containsExactlyInAnyOrder( + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + TEXT_ATTRIBUTE_BAR.getName(), + variantKey, + productKey, format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, TEXT_ATTRIBUTE_BAR.getName())), - format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, BOOLEAN_ATTRIBUTE_TRUE.getName(), variantKey, productKey, + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + BOOLEAN_ATTRIBUTE_TRUE.getName(), + variantKey, + productKey, format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, BOOLEAN_ATTRIBUTE_TRUE.getName()))); - } - - @Test - void withSomeNonChangedMatchingAttributesAndNoNewAttributes_ShouldBuildUnSetAttributeActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = - singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly( - SetAttribute.ofUnsetAttribute(oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true) - ); - } - - @Test - void withNoMatchingAttributes_ShouldBuildUnsetAndSetActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = - singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = singletonList(TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( - SetAttribute.ofUnsetAttribute(oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true), - SetAttribute.of(oldProductVariant.getId(), AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), true) - ); - } - - @Test - void withNoMatchingAttributes_WithSomeNoExistingMetaData_ShouldBuildSomeActionsAndTriggerErrorCallback() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = - singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = singletonList(TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly( - SetAttribute.ofUnsetAttribute(oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true) - ); - assertThat(errorMessages).containsExactly( - format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, BOOLEAN_ATTRIBUTE_TRUE.getName(), variantKey, productKey, + } + + @Test + void withSomeNonChangedMatchingAttributesAndNoNewAttributes_ShouldBuildUnSetAttributeActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( + SetAttribute.ofUnsetAttribute( + oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true)); + } + + @Test + void withNoMatchingAttributes_ShouldBuildUnsetAndSetActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = singletonList(TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( + SetAttribute.ofUnsetAttribute( + oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true), + SetAttribute.of( + oldProductVariant.getId(), + AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), + true)); + } + + @Test + void + withNoMatchingAttributes_WithSomeNoExistingMetaData_ShouldBuildSomeActionsAndTriggerErrorCallback() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = singletonList(TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( + SetAttribute.ofUnsetAttribute( + oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true)); + assertThat(errorMessages) + .containsExactly( + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + BOOLEAN_ATTRIBUTE_TRUE.getName(), + variantKey, + productKey, format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, BOOLEAN_ATTRIBUTE_TRUE.getName()))); - } - - @Test - void withNoMatchingAttributes_WithNoExistingMetaData_ShouldBuildNoActionsAndTriggerErrorCallback() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = - singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = singletonList(TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).isEmpty(); - assertThat(errorMessages).containsExactlyInAnyOrder( - format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, TEXT_ATTRIBUTE_BAR.getName(), variantKey, productKey, + } + + @Test + void + withNoMatchingAttributes_WithNoExistingMetaData_ShouldBuildNoActionsAndTriggerErrorCallback() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = singletonList(TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions).isEmpty(); + assertThat(errorMessages) + .containsExactlyInAnyOrder( + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + TEXT_ATTRIBUTE_BAR.getName(), + variantKey, + productKey, format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, TEXT_ATTRIBUTE_BAR.getName())), - format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, BOOLEAN_ATTRIBUTE_TRUE.getName(), variantKey, productKey, + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + BOOLEAN_ATTRIBUTE_TRUE.getName(), + variantKey, + productKey, format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, BOOLEAN_ATTRIBUTE_TRUE.getName()))); - } - - @Test - void withAllChangedMatchingAttributes_ShouldBuildSetActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = asList( + } + + @Test + void withAllChangedMatchingAttributes_ShouldBuildSetActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + asList( AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_FALSE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( - SetAttribute.of(oldProductVariant.getId(), AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build(), true), - SetAttribute.of(oldProductVariant.getId(), AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), true) - ); - } - - @Test - void withAllChangedMatchingAttrs_WithSomeNoExistingMetaData_ShouldBuildSomeActionsAndTriggerErrorCallback() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = asList( + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_FALSE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( + SetAttribute.of( + oldProductVariant.getId(), + AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build(), + true), + SetAttribute.of( + oldProductVariant.getId(), + AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), + true)); + } + + @Test + void + withAllChangedMatchingAttrs_WithSomeNoExistingMetaData_ShouldBuildSomeActionsAndTriggerErrorCallback() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + asList( AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_FALSE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly( - SetAttribute.of(oldProductVariant.getId(), AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build(), true) - ); - assertThat(errorMessages).containsExactly( - format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, BOOLEAN_ATTRIBUTE_TRUE.getName(), variantKey, productKey, + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_FALSE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( + SetAttribute.of( + oldProductVariant.getId(), + AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build(), + true)); + assertThat(errorMessages) + .containsExactly( + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + BOOLEAN_ATTRIBUTE_TRUE.getName(), + variantKey, + productKey, format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, BOOLEAN_ATTRIBUTE_TRUE.getName()))); - } - - @Test - void withSomeChangedMatchingAttributes_ShouldBuildSetActions() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = asList( + } + + @Test + void withSomeChangedMatchingAttributes_ShouldBuildSetActions() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + asList( AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly( - SetAttribute.of(oldProductVariant.getId(), AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build(), true) - ); - } - - @Test - void withSomeChangedMatchingAttrs_WithNoExistingMetaData_ShouldBuildNoActionsAndTriggerErrorCallback() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = asList( - AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), - AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); - // Assertion - assertThat(updateActions).isEmpty(); - assertThat(errorMessages).containsExactlyInAnyOrder( - format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, TEXT_ATTRIBUTE_BAR.getName(), variantKey, productKey, + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( + SetAttribute.of( + oldProductVariant.getId(), + AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build(), + true)); + } + + @Test + void + withSomeChangedMatchingAttrs_WithNoExistingMetaData_ShouldBuildNoActionsAndTriggerErrorCallback() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + asList( + AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), + AttributeDraftBuilder.of(TEXT_ATTRIBUTE_FOO).build()); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = asList(BOOLEAN_ATTRIBUTE_TRUE, TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions).isEmpty(); + assertThat(errorMessages) + .containsExactlyInAnyOrder( + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + TEXT_ATTRIBUTE_BAR.getName(), + variantKey, + productKey, format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, TEXT_ATTRIBUTE_BAR.getName())), - format(FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, BOOLEAN_ATTRIBUTE_TRUE.getName(), variantKey, productKey, + format( + FAILED_TO_BUILD_ATTRIBUTE_UPDATE_ACTION, + BOOLEAN_ATTRIBUTE_TRUE.getName(), + variantKey, + productKey, format(ATTRIBUTE_NOT_IN_ATTRIBUTE_METADATA, BOOLEAN_ATTRIBUTE_TRUE.getName()))); - } - - @Test - void withNoMatchingAttributes_ShouldBuildUnsetAndSetActionsInCorrectOrder() { - // Preparation - final String productKey = "foo"; - final String variantKey = "foo"; - when(oldProduct.getKey()).thenReturn(productKey); - final List newAttributes = - singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); - when(newProductVariant.getAttributes()).thenReturn(newAttributes); - when(newProductVariant.getKey()).thenReturn(variantKey); - - final List oldAttributes = singletonList(TEXT_ATTRIBUTE_BAR); - when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); - when(oldProductVariant.getKey()).thenReturn(variantKey); - - final HashMap attributesMetaData = new HashMap<>(); - final AttributeDefinition booleanAttributeDefinition = - AttributeDefinitionBuilder.of(BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), - BooleanAttributeType.of()).build(); - final AttributeDefinition textAttributeDefinition = - AttributeDefinitionBuilder.of(TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), - StringAttributeType.of()).build(); - attributesMetaData.put(BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); - attributesMetaData.put(TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); - - // Test - final List> updateActions = buildProductVariantAttributesUpdateActions(oldProduct, - newProductDraft, oldProductVariant, newProductVariant, attributesMetaData, syncOptions); - - // Assertion - assertThat(updateActions).containsExactly( - SetAttribute.ofUnsetAttribute(oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true), - SetAttribute.of(oldProductVariant.getId(), AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), true) - ); - } - + } + + @Test + void withNoMatchingAttributes_ShouldBuildUnsetAndSetActionsInCorrectOrder() { + // Preparation + final String productKey = "foo"; + final String variantKey = "foo"; + when(oldProduct.getKey()).thenReturn(productKey); + final List newAttributes = + singletonList(AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build()); + when(newProductVariant.getAttributes()).thenReturn(newAttributes); + when(newProductVariant.getKey()).thenReturn(variantKey); + + final List oldAttributes = singletonList(TEXT_ATTRIBUTE_BAR); + when(oldProductVariant.getAttributes()).thenReturn(oldAttributes); + when(oldProductVariant.getKey()).thenReturn(variantKey); + + final HashMap attributesMetaData = new HashMap<>(); + final AttributeDefinition booleanAttributeDefinition = + AttributeDefinitionBuilder.of( + BOOLEAN_ATTRIBUTE_TRUE.getName(), ofEnglish("label"), BooleanAttributeType.of()) + .build(); + final AttributeDefinition textAttributeDefinition = + AttributeDefinitionBuilder.of( + TEXT_ATTRIBUTE_BAR.getName(), ofEnglish("label"), StringAttributeType.of()) + .build(); + attributesMetaData.put( + BOOLEAN_ATTRIBUTE_TRUE.getName(), AttributeMetaData.of(booleanAttributeDefinition)); + attributesMetaData.put( + TEXT_ATTRIBUTE_BAR.getName(), AttributeMetaData.of(textAttributeDefinition)); + + // Test + final List> updateActions = + buildProductVariantAttributesUpdateActions( + oldProduct, + newProductDraft, + oldProductVariant, + newProductVariant, + attributesMetaData, + syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( + SetAttribute.ofUnsetAttribute( + oldProductVariant.getId(), TEXT_ATTRIBUTE_BAR.getName(), true), + SetAttribute.of( + oldProductVariant.getId(), + AttributeDraftBuilder.of(BOOLEAN_ATTRIBUTE_TRUE).build(), + true)); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/BuildProductVariantPricesUpdateActionsTest.java b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/BuildProductVariantPricesUpdateActionsTest.java index d0b9a9c8bd..d1682e5a41 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/BuildProductVariantPricesUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/BuildProductVariantPricesUpdateActionsTest.java @@ -1,5 +1,16 @@ package com.commercetools.sync.products.utils.productvariantupdateactionutils.prices; +import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantPricesUpdateActions; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.*; +import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceFixtures.*; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import io.sphere.sdk.client.SphereClient; @@ -15,324 +26,287 @@ import io.sphere.sdk.products.commands.updateactions.RemovePrice; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomField; import io.sphere.sdk.products.commands.updateactions.SetProductPriceCustomType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; - -import static com.commercetools.sync.products.utils.ProductVariantUpdateActionUtils.buildProductVariantPricesUpdateActions; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.*; -import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceFixtures.*; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class BuildProductVariantPricesUpdateActionsTest { - private final Product oldProduct = mock(Product.class); - private final ProductDraft newProductDraft = mock(ProductDraft.class); - private final ProductVariant oldProductVariant = mock(ProductVariant.class); - private final ProductVariantDraft newProductVariant = mock(ProductVariantDraft.class); - private List errorMessages; - private final ProductSyncOptions syncOptions = - ProductSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorMessages.add(exception.getMessage())) - .build(); - - @BeforeEach - void setupMethod() { - errorMessages = new ArrayList<>(); - } - - - @Test - void withNullNewPricesAndEmptyExistingPrices_ShouldNotBuildActions() { - // Preparation - when(newProductVariant.getPrices()).thenReturn(null); - when(oldProductVariant.getPrices()).thenReturn(emptyList()); - - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); - - // Assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void withSomeNullNewPricesAndExistingPrices_ShouldBuildActionsAndTriggerErrorCallback() { - // Preparation - final List newPrices = asList( - DRAFT_US_111_USD, - null, - DRAFT_DE_111_EUR_01_02, - DRAFT_DE_111_USD); - when(newProductVariant.getPrices()).thenReturn(newPrices); - final List oldPrices = asList( - US_111_USD, - DE_111_EUR, - DE_111_EUR_01_02, - DE_111_USD); - when(oldProductVariant.getPrices()).thenReturn(oldPrices); - - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); - - // Assertion - assertThat(updateActions).containsExactly(RemovePrice.of(DE_111_EUR, true)); - assertThat(errorMessages).containsExactly(format("Failed to build prices update actions for one price on the " - + "variant with id '%d' and key '%s'. Reason: %s", oldProductVariant.getId(), newProductVariant.getKey(), - "New price is null.")); - } - - @Test - void withEmptyNewPricesAndEmptyExistingPrices_ShouldNotBuildActions() { - // Preparation - when(oldProductVariant.getPrices()).thenReturn(emptyList()); - when(newProductVariant.getPrices()).thenReturn(emptyList()); - - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); - - // Assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void withAllMatchingPrices_ShouldNotBuildActions() { - // Preparation - final List newPrices = asList( - DRAFT_US_111_USD, - DRAFT_DE_111_EUR, - DRAFT_DE_111_EUR_01_02, - DRAFT_DE_111_USD); - when(newProductVariant.getPrices()).thenReturn(newPrices); - - final List oldPrices = asList( - US_111_USD, - DE_111_EUR, - DE_111_EUR_01_02, - DE_111_USD); - when(oldProductVariant.getPrices()).thenReturn(oldPrices); - - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); - - // Assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void withNonEmptyNewPricesButEmptyExistingPrices_ShouldBuildAddPriceActions() { - // Preparation - final List newPrices = asList( + private final Product oldProduct = mock(Product.class); + private final ProductDraft newProductDraft = mock(ProductDraft.class); + private final ProductVariant oldProductVariant = mock(ProductVariant.class); + private final ProductVariantDraft newProductVariant = mock(ProductVariantDraft.class); + private List errorMessages; + private final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorMessages.add(exception.getMessage())) + .build(); + + @BeforeEach + void setupMethod() { + errorMessages = new ArrayList<>(); + } + + @Test + void withNullNewPricesAndEmptyExistingPrices_ShouldNotBuildActions() { + // Preparation + when(newProductVariant.getPrices()).thenReturn(null); + when(oldProductVariant.getPrices()).thenReturn(emptyList()); + + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); + + // Assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void withSomeNullNewPricesAndExistingPrices_ShouldBuildActionsAndTriggerErrorCallback() { + // Preparation + final List newPrices = + asList(DRAFT_US_111_USD, null, DRAFT_DE_111_EUR_01_02, DRAFT_DE_111_USD); + when(newProductVariant.getPrices()).thenReturn(newPrices); + final List oldPrices = asList(US_111_USD, DE_111_EUR, DE_111_EUR_01_02, DE_111_USD); + when(oldProductVariant.getPrices()).thenReturn(oldPrices); + + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); + + // Assertion + assertThat(updateActions).containsExactly(RemovePrice.of(DE_111_EUR, true)); + assertThat(errorMessages) + .containsExactly( + format( + "Failed to build prices update actions for one price on the " + + "variant with id '%d' and key '%s'. Reason: %s", + oldProductVariant.getId(), newProductVariant.getKey(), "New price is null.")); + } + + @Test + void withEmptyNewPricesAndEmptyExistingPrices_ShouldNotBuildActions() { + // Preparation + when(oldProductVariant.getPrices()).thenReturn(emptyList()); + when(newProductVariant.getPrices()).thenReturn(emptyList()); + + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); + + // Assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void withAllMatchingPrices_ShouldNotBuildActions() { + // Preparation + final List newPrices = + asList(DRAFT_US_111_USD, DRAFT_DE_111_EUR, DRAFT_DE_111_EUR_01_02, DRAFT_DE_111_USD); + when(newProductVariant.getPrices()).thenReturn(newPrices); + + final List oldPrices = asList(US_111_USD, DE_111_EUR, DE_111_EUR_01_02, DE_111_USD); + when(oldProductVariant.getPrices()).thenReturn(oldPrices); + + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); + + // Assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void withNonEmptyNewPricesButEmptyExistingPrices_ShouldBuildAddPriceActions() { + // Preparation + final List newPrices = + asList( DRAFT_US_111_USD, DRAFT_DE_111_EUR, DRAFT_DE_111_EUR_01_02, DRAFT_DE_111_EUR_02_03, DRAFT_DE_111_USD, DRAFT_UK_111_GBP); - when(newProductVariant.getPrices()).thenReturn(newPrices); - when(oldProductVariant.getPrices()).thenReturn(emptyList()); + when(newProductVariant.getPrices()).thenReturn(newPrices); + when(oldProductVariant.getPrices()).thenReturn(emptyList()); - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_US_111_USD, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_EUR, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_EUR_01_02, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_EUR_02_03, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_USD, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_UK_111_GBP, true)); - } - - @Test - void withSomeNonChangedMatchingPricesAndNewPrices_ShouldBuildAddPriceActions() { - // Preparation - final List newPrices = asList( + } + + @Test + void withSomeNonChangedMatchingPricesAndNewPrices_ShouldBuildAddPriceActions() { + // Preparation + final List newPrices = + asList( DRAFT_DE_111_EUR, DRAFT_DE_111_EUR_01_02, DRAFT_DE_111_EUR_02_03, DRAFT_DE_111_USD, DRAFT_UK_111_GBP); - when(newProductVariant.getPrices()).thenReturn(newPrices); + when(newProductVariant.getPrices()).thenReturn(newPrices); - final List oldPrices = asList( - DE_111_EUR, - DE_111_EUR_01_02 - ); - when(oldProductVariant.getPrices()).thenReturn(oldPrices); + final List oldPrices = asList(DE_111_EUR, DE_111_EUR_01_02); + when(oldProductVariant.getPrices()).thenReturn(oldPrices); - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_EUR_02_03, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_USD, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_UK_111_GBP, true)); - } - - @Test - void withNullNewPrices_ShouldBuildRemovePricesAction() { - // Preparation - when(newProductVariant.getPrices()).thenReturn(null); - final List prices = asList( - US_111_USD, - DE_111_EUR, - DE_111_EUR_01_02, - DE_111_USD); - when(oldProductVariant.getPrices()).thenReturn(prices); - - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); - - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + } + + @Test + void withNullNewPrices_ShouldBuildRemovePricesAction() { + // Preparation + when(newProductVariant.getPrices()).thenReturn(null); + final List prices = asList(US_111_USD, DE_111_EUR, DE_111_EUR_01_02, DE_111_USD); + when(oldProductVariant.getPrices()).thenReturn(prices); + + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( RemovePrice.of(US_111_USD.getId(), true), RemovePrice.of(DE_111_EUR.getId(), true), RemovePrice.of(DE_111_EUR_01_02.getId(), true), RemovePrice.of(DE_111_USD.getId(), true)); - } - - @Test - void withEmptyNewPrices_ShouldBuildRemovePricesAction() { - // Preparation - when(newProductVariant.getPrices()).thenReturn(emptyList()); - final List prices = asList( - US_111_USD, - DE_111_EUR, - DE_111_EUR_01_02, - DE_111_USD); - when(oldProductVariant.getPrices()).thenReturn(prices); - - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); - - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + } + + @Test + void withEmptyNewPrices_ShouldBuildRemovePricesAction() { + // Preparation + when(newProductVariant.getPrices()).thenReturn(emptyList()); + final List prices = asList(US_111_USD, DE_111_EUR, DE_111_EUR_01_02, DE_111_USD); + when(oldProductVariant.getPrices()).thenReturn(prices); + + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( RemovePrice.of(US_111_USD.getId(), true), RemovePrice.of(DE_111_EUR.getId(), true), RemovePrice.of(DE_111_EUR_01_02.getId(), true), RemovePrice.of(DE_111_USD.getId(), true)); - } - - @Test - void withSomeNonChangedMatchingPricesAndNoNewPrices_ShouldBuildRemovePriceActions() { - // Preparation - final List newPrices = singletonList(DRAFT_US_111_USD); - when(newProductVariant.getPrices()).thenReturn(newPrices); - final List prices = asList( - US_111_USD, - DE_111_EUR, - DE_111_EUR_01_02, - DE_111_USD); - when(oldProductVariant.getPrices()).thenReturn(prices); - - - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); - - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + } + + @Test + void withSomeNonChangedMatchingPricesAndNoNewPrices_ShouldBuildRemovePriceActions() { + // Preparation + final List newPrices = singletonList(DRAFT_US_111_USD); + when(newProductVariant.getPrices()).thenReturn(newPrices); + final List prices = asList(US_111_USD, DE_111_EUR, DE_111_EUR_01_02, DE_111_USD); + when(oldProductVariant.getPrices()).thenReturn(prices); + + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( RemovePrice.of(DE_111_EUR.getId(), true), RemovePrice.of(DE_111_EUR_01_02.getId(), true), RemovePrice.of(DE_111_USD.getId(), true)); - } - - @Test - void withNoMatchingPrices_ShouldBuildRemoveAndAddPricesActions() { - // Preparation - final List newPrices = asList( - DRAFT_DE_111_EUR, - DRAFT_DE_111_EUR_01_02, - DRAFT_UK_111_GBP); - when(newProductVariant.getPrices()).thenReturn(newPrices); - - final List oldPrices = asList( - DE_111_USD, - DE_111_EUR_02_03, - US_111_USD); - when(oldProductVariant.getPrices()).thenReturn(oldPrices); - - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); - - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + } + + @Test + void withNoMatchingPrices_ShouldBuildRemoveAndAddPricesActions() { + // Preparation + final List newPrices = + asList(DRAFT_DE_111_EUR, DRAFT_DE_111_EUR_01_02, DRAFT_UK_111_GBP); + when(newProductVariant.getPrices()).thenReturn(newPrices); + + final List oldPrices = asList(DE_111_USD, DE_111_EUR_02_03, US_111_USD); + when(oldProductVariant.getPrices()).thenReturn(oldPrices); + + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( RemovePrice.of(DE_111_USD.getId(), true), RemovePrice.of(DE_111_EUR_02_03.getId(), true), RemovePrice.of(US_111_USD.getId(), true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_EUR, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_EUR_01_02, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_UK_111_GBP, true)); - } + } - @Test - void withSomeChangedMatchingPrices_ShouldBuildRemoveAndAddPricesActions() { - // Preparation - final List newPrices = asList( + @Test + void withSomeChangedMatchingPrices_ShouldBuildRemoveAndAddPricesActions() { + // Preparation + final List newPrices = + asList( DRAFT_DE_111_EUR, DRAFT_DE_222_EUR_CUST1, DRAFT_DE_333_USD_CUST1, DRAFT_UK_111_GBP_01_02, DRAFT_UK_111_GBP); - when(newProductVariant.getPrices()).thenReturn(newPrices); + when(newProductVariant.getPrices()).thenReturn(newPrices); - final List oldPrices = asList( - DE_111_EUR, - DE_345_EUR_CUST2, - DE_567_EUR_CUST3, - UK_111_GBP_02_03, - UK_111_GBP_01_02); - when(oldProductVariant.getPrices()).thenReturn(oldPrices); + final List oldPrices = + asList(DE_111_EUR, DE_345_EUR_CUST2, DE_567_EUR_CUST3, UK_111_GBP_02_03, UK_111_GBP_01_02); + when(oldProductVariant.getPrices()).thenReturn(oldPrices); - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( RemovePrice.of(DE_345_EUR_CUST2.getId(), true), RemovePrice.of(DE_567_EUR_CUST3.getId(), true), RemovePrice.of(UK_111_GBP_02_03.getId(), true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_222_EUR_CUST1, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_333_USD_CUST1, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_UK_111_GBP, true)); - } + } - @Test - void withPricesWithOverlappingValidityDates_ShouldBuildRemoveAndAddPricesActions() { - // Preparation - final List newPrices = asList( + @Test + void withPricesWithOverlappingValidityDates_ShouldBuildRemoveAndAddPricesActions() { + // Preparation + final List newPrices = + asList( DRAFT_DE_111_EUR_01_02, DRAFT_DE_222_EUR_03_04, DRAFT_UK_333_GBP_01_04, @@ -342,25 +316,26 @@ void withPricesWithOverlappingValidityDates_ShouldBuildRemoveAndAddPricesActions DRAFT_FR_999_EUR_03_06, DRAFT_NE_777_EUR_01_04, DRAFT_NE_777_EUR_05_07); - when(newProductVariant.getPrices()).thenReturn(newPrices); + when(newProductVariant.getPrices()).thenReturn(newPrices); - final List oldPrices = asList( + final List oldPrices = + asList( DE_111_EUR, UK_333_GBP_03_05, US_555_USD_CUST2_01_02, FR_777_EUR_01_04, NE_123_EUR_01_04, - NE_321_EUR_04_06 - ); - when(oldProductVariant.getPrices()).thenReturn(oldPrices); + NE_321_EUR_04_06); + when(oldProductVariant.getPrices()).thenReturn(oldPrices); - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( RemovePrice.of(DE_111_EUR.getId(), true), RemovePrice.of(UK_333_GBP_03_05.getId(), true), RemovePrice.of(US_555_USD_CUST2_01_02.getId(), true), @@ -374,14 +349,14 @@ void withPricesWithOverlappingValidityDates_ShouldBuildRemoveAndAddPricesActions AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_FR_888_EUR_01_03, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_FR_999_EUR_03_06, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_NE_777_EUR_05_07, true), - ChangePrice.of(NE_123_EUR_01_04, DRAFT_NE_777_EUR_01_04, true) - ); - } - - @Test - void withAllMatchingChangedPrices_ShouldBuildChangePriceActions() { - // Preparation - final List newPrices = asList( + ChangePrice.of(NE_123_EUR_01_04, DRAFT_NE_777_EUR_01_04, true)); + } + + @Test + void withAllMatchingChangedPrices_ShouldBuildChangePriceActions() { + // Preparation + final List newPrices = + asList( DRAFT_DE_111_EUR, DRAFT_DE_111_USD, DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, @@ -389,9 +364,10 @@ void withAllMatchingChangedPrices_ShouldBuildChangePriceActions() { DRAFT_UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDX, DRAFT_UK_22_USD_CUSTOMTYPE1_CUSTOMFIELDX, DRAFT_UK_666_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX); - when(newProductVariant.getPrices()).thenReturn(newPrices); + when(newProductVariant.getPrices()).thenReturn(newPrices); - final List oldPrices = asList( + final List oldPrices = + asList( DE_666_EUR, DE_111_USD, DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, @@ -399,69 +375,84 @@ void withAllMatchingChangedPrices_ShouldBuildChangePriceActions() { UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDY, UK_22_USD_CUSTOMTYPE2_CUSTOMFIELDX, UK_1_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX); - when(oldProductVariant.getPrices()).thenReturn(oldPrices); + when(oldProductVariant.getPrices()).thenReturn(oldPrices); - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( ChangePrice.of(DE_666_EUR, DRAFT_DE_111_EUR, true), - ChangePrice.of(DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, - DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, true), - SetProductPriceCustomField.ofJson("foo", - DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getFields().get("foo"), - DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY.getId(), true), - ChangePrice.of(DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX, - DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX, true), + ChangePrice.of( + DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, + DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, + true), + SetProductPriceCustomField.ofJson( + "foo", + DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX + .getCustom() + .getFields() + .get("foo"), + DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY.getId(), + true), + ChangePrice.of( + DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX, + DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX, + true), SetProductPriceCustomType.ofTypeIdAndJson( - DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getType().getId(), + DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX + .getCustom() + .getType() + .getId(), DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getFields(), - DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX.getId(), true), - SetProductPriceCustomField.ofJson("foo", + DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX.getId(), + true), + SetProductPriceCustomField.ofJson( + "foo", DRAFT_UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getFields().get("foo"), - UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDY.getId(), true), + UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDY.getId(), + true), SetProductPriceCustomType.ofTypeIdAndJson( DRAFT_UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getType().getId(), DRAFT_UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getFields(), - UK_22_USD_CUSTOMTYPE2_CUSTOMFIELDX.getId(), true), - ChangePrice.of(UK_1_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, - DRAFT_UK_666_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, true)); - } - - @Test - void withDifferentPriceActions_ShouldBuildActionsInCorrectOrder() { - // Preparation - final List newPrices = asList( - DRAFT_DE_111_EUR_01_02, - DRAFT_NE_777_EUR_01_04); - when(newProductVariant.getPrices()).thenReturn(newPrices); - - final List oldPrices = asList( - DE_111_EUR, - NE_123_EUR_01_04 - ); - when(oldProductVariant.getPrices()).thenReturn(oldPrices); - - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); - - - // Assertion - assertThat(updateActions).containsExactly( + UK_22_USD_CUSTOMTYPE2_CUSTOMFIELDX.getId(), + true), + ChangePrice.of( + UK_1_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, + DRAFT_UK_666_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, + true)); + } + + @Test + void withDifferentPriceActions_ShouldBuildActionsInCorrectOrder() { + // Preparation + final List newPrices = asList(DRAFT_DE_111_EUR_01_02, DRAFT_NE_777_EUR_01_04); + when(newProductVariant.getPrices()).thenReturn(newPrices); + + final List oldPrices = asList(DE_111_EUR, NE_123_EUR_01_04); + when(oldProductVariant.getPrices()).thenReturn(oldPrices); + + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); + + // Assertion + assertThat(updateActions) + .containsExactly( RemovePrice.of(DE_111_EUR.getId(), true), ChangePrice.of(NE_123_EUR_01_04, DRAFT_NE_777_EUR_01_04, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_EUR_01_02, true)); - } + } - @Test - void withMixedCasesOfPriceMatches_ShouldBuildActions() { - // Preparation - final List newPrices = asList( + @Test + void withMixedCasesOfPriceMatches_ShouldBuildActions() { + // Preparation + final List newPrices = + asList( DRAFT_DE_111_EUR, DRAFT_DE_222_EUR_CUST1, DRAFT_DE_111_EUR_01_02, @@ -477,9 +468,10 @@ void withMixedCasesOfPriceMatches_ShouldBuildActions() { DRAFT_FR_999_EUR_03_06, DRAFT_NE_777_EUR_01_04, DRAFT_NE_777_EUR_05_07); - when(newProductVariant.getPrices()).thenReturn(newPrices); + when(newProductVariant.getPrices()).thenReturn(newPrices); - final List oldPrices = asList( + final List oldPrices = + asList( DE_111_EUR, DE_345_EUR_CUST2, DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, @@ -490,33 +482,46 @@ void withMixedCasesOfPriceMatches_ShouldBuildActions() { UK_333_GBP_03_05, FR_777_EUR_01_04, NE_123_EUR_01_04, - NE_321_EUR_04_06 - ); - when(oldProductVariant.getPrices()).thenReturn(oldPrices); + NE_321_EUR_04_06); + when(oldProductVariant.getPrices()).thenReturn(oldPrices); - // Test - final List> updateActions = - buildProductVariantPricesUpdateActions(oldProduct, newProductDraft, oldProductVariant, newProductVariant, - syncOptions); + // Test + final List> updateActions = + buildProductVariantPricesUpdateActions( + oldProduct, newProductDraft, oldProductVariant, newProductVariant, syncOptions); - // Assertion - assertThat(updateActions).containsExactlyInAnyOrder( + // Assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( RemovePrice.of(DE_345_EUR_CUST2.getId(), true), RemovePrice.of(UK_111_GBP_02_03.getId(), true), RemovePrice.of(UK_333_GBP_03_05.getId(), true), RemovePrice.of(FR_777_EUR_01_04.getId(), true), RemovePrice.of(NE_321_EUR_04_06.getId(), true), - ChangePrice.of(DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, - DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, true), - SetProductPriceCustomField.ofJson("foo", - DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getFields().get("foo"), - DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY.getId(), true), - ChangePrice.of(DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX, - DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX, true), + ChangePrice.of( + DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY, + DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX, + true), + SetProductPriceCustomField.ofJson( + "foo", + DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX + .getCustom() + .getFields() + .get("foo"), + DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY.getId(), + true), + ChangePrice.of( + DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX, + DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX, + true), SetProductPriceCustomType.ofTypeIdAndJson( - DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getType().getId(), + DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX + .getCustom() + .getType() + .getId(), DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX.getCustom().getFields(), - DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX.getId(), true), + DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX.getId(), + true), ChangePrice.of(NE_123_EUR_01_04, DRAFT_NE_777_EUR_01_04, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_222_EUR_CUST1, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_DE_111_EUR_01_02, true), @@ -526,9 +531,6 @@ void withMixedCasesOfPriceMatches_ShouldBuildActions() { AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_US_666_USD_CUST2_01_02, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_FR_888_EUR_01_03, true), AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_FR_999_EUR_03_06, true), - AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_NE_777_EUR_05_07, true) - ); - - - } + AddPrice.ofVariantId(oldProductVariant.getId(), DRAFT_NE_777_EUR_05_07, true)); + } } diff --git a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/PriceDraftFixtures.java b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/PriceDraftFixtures.java index e6cd2d45d5..c92cb2df94 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/PriceDraftFixtures.java +++ b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/PriceDraftFixtures.java @@ -1,5 +1,14 @@ package com.commercetools.sync.products.utils.productvariantupdateactionutils.prices; +import static com.neovisionaries.i18n.CountryCode.DE; +import static com.neovisionaries.i18n.CountryCode.FR; +import static com.neovisionaries.i18n.CountryCode.NE; +import static com.neovisionaries.i18n.CountryCode.UK; +import static com.neovisionaries.i18n.CountryCode.US; +import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; +import static io.sphere.sdk.models.DefaultCurrencyUnits.USD; +import static java.util.Optional.ofNullable; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.neovisionaries.i18n.CountryCode; @@ -11,276 +20,300 @@ import io.sphere.sdk.products.PriceDraftBuilder; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.MoneyImpl; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.money.CurrencyUnit; import java.math.BigDecimal; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.HashMap; import java.util.Map; - -import static com.neovisionaries.i18n.CountryCode.DE; -import static com.neovisionaries.i18n.CountryCode.FR; -import static com.neovisionaries.i18n.CountryCode.NE; -import static com.neovisionaries.i18n.CountryCode.UK; -import static com.neovisionaries.i18n.CountryCode.US; -import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; -import static io.sphere.sdk.models.DefaultCurrencyUnits.USD; - -import static java.util.Optional.ofNullable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.money.CurrencyUnit; public final class PriceDraftFixtures { - public static final PriceDraft DRAFT_111_USD = getPriceDraft(BigDecimal.valueOf(111), USD, null, - null, null, null, null, null); - - public static final PriceDraft DRAFT_111_USD_CHANNEL1 = getPriceDraft(BigDecimal.valueOf(111), USD, null, - null, null, null, "channel1", null); - - public static final PriceDraft DRAFT_US_111_USD = getPriceDraft(BigDecimal.valueOf(111), USD, US, - null, null, null, null, null); - - public static final PriceDraft DRAFT_US_111_USD_CHANNEL1 = getPriceDraft(BigDecimal.valueOf(111), USD, US, null, - null, null, "channel1", null); - - public static final PriceDraft DRAFT_111_USD_CUST1 = getPriceDraft(BigDecimal.valueOf(111), USD, null, - "cust1", null, null, null, null); - - public static final PriceDraft DRAFT_111_USD_CHANNEL1_CUST1 = getPriceDraft(BigDecimal.valueOf(111), USD, null, - "cust1", null, null, "channel1", null); - - public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_CUST1 = getPriceDraft(BigDecimal.valueOf(111), USD, US, - "cust1", null, null, "channel1", null); - - public static final PriceDraft DRAFT_111_USD_FROM_01 = getPriceDraft(BigDecimal.valueOf(111), USD, null, - null, byMonth(1), null, null, null); - - public static final PriceDraft DRAFT_US_111_USD_FROM_01 = getPriceDraft(BigDecimal.valueOf(111), USD, US, - null, byMonth(1), null, null, null); + public static final PriceDraft DRAFT_111_USD = + getPriceDraft(BigDecimal.valueOf(111), USD, null, null, null, null, null, null); - public static final PriceDraft DRAFT_111_USD_CHANNEL1_FROM_01 = getPriceDraft(BigDecimal.valueOf(111), USD, null, - null, byMonth(1), null, "channel1", null); + public static final PriceDraft DRAFT_111_USD_CHANNEL1 = + getPriceDraft(BigDecimal.valueOf(111), USD, null, null, null, null, "channel1", null); - public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_FROM_01 = getPriceDraft(BigDecimal.valueOf(111), USD, US, - null, byMonth(1), null, "channel1", null); + public static final PriceDraft DRAFT_US_111_USD = + getPriceDraft(BigDecimal.valueOf(111), USD, US, null, null, null, null, null); - public static final PriceDraft DRAFT_111_USD_CUST1_FROM_01 = getPriceDraft(BigDecimal.valueOf(111), USD, null, - "cust1", byMonth(1), null, null, null); + public static final PriceDraft DRAFT_US_111_USD_CHANNEL1 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, null, null, null, "channel1", null); - public static final PriceDraft DRAFT_US_111_USD_CUST1_FROM_01 = getPriceDraft(BigDecimal.valueOf(111), USD, US, - "cust1", byMonth(1), null, null, null); + public static final PriceDraft DRAFT_111_USD_CUST1 = + getPriceDraft(BigDecimal.valueOf(111), USD, null, "cust1", null, null, null, null); - public static final PriceDraft DRAFT_111_USD_CHANNEL1_CUST1_FROM_01 = getPriceDraft(BigDecimal.valueOf(111), USD, - null, - "cust1", byMonth(1), null, "channel1", null); + public static final PriceDraft DRAFT_111_USD_CHANNEL1_CUST1 = + getPriceDraft(BigDecimal.valueOf(111), USD, null, "cust1", null, null, "channel1", null); - public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_CUST1_FROM_01 = getPriceDraft(BigDecimal.valueOf(111), USD, - US, - "cust1", byMonth(1), null, "channel1", null); + public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_CUST1 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, "cust1", null, null, "channel1", null); - public static final PriceDraft DRAFT_111_USD_UNTIL_01 = getPriceDraft(BigDecimal.valueOf(111), USD, null, - null, null, byMonth(1), null, null); + public static final PriceDraft DRAFT_111_USD_FROM_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, null, null, byMonth(1), null, null, null); - public static final PriceDraft DRAFT_US_111_USD_UNTIL_01 = getPriceDraft(BigDecimal.valueOf(111), USD, US, - null, null, byMonth(1), null, null); + public static final PriceDraft DRAFT_US_111_USD_FROM_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, null, byMonth(1), null, null, null); - public static final PriceDraft DRAFT_111_USD_CHANNEL1_UNTIL_01 = getPriceDraft(BigDecimal.valueOf(111), USD, null, - null, null, byMonth(1), "channel1", null); + public static final PriceDraft DRAFT_111_USD_CHANNEL1_FROM_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, null, null, byMonth(1), null, "channel1", null); - public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_UNTIL_01 = getPriceDraft(BigDecimal.valueOf(111), USD, US, - null, null, byMonth(1), "channel1", null); + public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_FROM_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, null, byMonth(1), null, "channel1", null); - public static final PriceDraft DRAFT_111_USD_CUST1_UNTIL_01 = getPriceDraft(BigDecimal.valueOf(111), USD, null, - "cust1", null, byMonth(1), null, null); + public static final PriceDraft DRAFT_111_USD_CUST1_FROM_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, null, "cust1", byMonth(1), null, null, null); - public static final PriceDraft DRAFT_US_111_USD_CUST1_UNTIL_01 = getPriceDraft(BigDecimal.valueOf(111), USD, US, - "cust1", null, byMonth(1), null, null); + public static final PriceDraft DRAFT_US_111_USD_CUST1_FROM_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, "cust1", byMonth(1), null, null, null); - public static final PriceDraft DRAFT_111_USD_CHANNEL1_CUST1_UNTIL_01 = getPriceDraft(BigDecimal.valueOf(111), USD, - null, - "cust1", null, byMonth(1), "channel1", null); + public static final PriceDraft DRAFT_111_USD_CHANNEL1_CUST1_FROM_01 = + getPriceDraft( + BigDecimal.valueOf(111), USD, null, "cust1", byMonth(1), null, "channel1", null); - public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_CUST1_UNTIL_01 = getPriceDraft(BigDecimal.valueOf(111), - USD, US, - "cust1", null, byMonth(1), "channel1", null); + public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_CUST1_FROM_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, "cust1", byMonth(1), null, "channel1", null); - public static final PriceDraft DRAFT_DE_111_EUR = getPriceDraft(BigDecimal.valueOf(111), EUR, DE, - null, null, null, null, null); + public static final PriceDraft DRAFT_111_USD_UNTIL_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, null, null, null, byMonth(1), null, null); - public static final PriceDraft DRAFT_111_EUR_01_02 = getPriceDraft(BigDecimal.valueOf(111), EUR, null, null, - byMonth(1), byMonth(2), null, null); + public static final PriceDraft DRAFT_US_111_USD_UNTIL_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, null, null, byMonth(1), null, null); - public static final PriceDraft DRAFT_111_EUR_CHANNEL1_01_02 = getPriceDraft(BigDecimal.valueOf(111), EUR, null, - null, - byMonth(1), byMonth(2), "channel1", null); + public static final PriceDraft DRAFT_111_USD_CHANNEL1_UNTIL_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, null, null, null, byMonth(1), "channel1", null); - public static final PriceDraft DRAFT_DE_111_EUR_01_02 = getPriceDraft(BigDecimal.valueOf(111), EUR, DE, null, - byMonth(1), byMonth(2), null, null); + public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_UNTIL_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, null, null, byMonth(1), "channel1", null); - public static final PriceDraft DRAFT_DE_111_EUR_02_03 = getPriceDraft(BigDecimal.valueOf(111), EUR, DE, null, - byMonth(2), byMonth(3), null, null); + public static final PriceDraft DRAFT_111_USD_CUST1_UNTIL_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, null, "cust1", null, byMonth(1), null, null); - public static final PriceDraft DRAFT_DE_111_EUR_03_04 = getPriceDraft(BigDecimal.valueOf(111), EUR, DE, null, - byMonth(3), byMonth(4), null, null); + public static final PriceDraft DRAFT_US_111_USD_CUST1_UNTIL_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, "cust1", null, byMonth(1), null, null); - public static final PriceDraft DRAFT_DE_222_EUR_03_04 = getPriceDraft(BigDecimal.valueOf(222), EUR, DE, null, - byMonth(3), byMonth(4), null, null); + public static final PriceDraft DRAFT_111_USD_CHANNEL1_CUST1_UNTIL_01 = + getPriceDraft( + BigDecimal.valueOf(111), USD, null, "cust1", null, byMonth(1), "channel1", null); - public static final PriceDraft DRAFT_DE_111_USD = getPriceDraft(BigDecimal.valueOf(111), USD, DE, - null, null, null, null, null); + public static final PriceDraft DRAFT_US_111_USD_CHANNEL1_CUST1_UNTIL_01 = + getPriceDraft(BigDecimal.valueOf(111), USD, US, "cust1", null, byMonth(1), "channel1", null); - public static final CurrencyUnit GBP = MoneyImpl.createCurrencyByCode("GBP"); + public static final PriceDraft DRAFT_DE_111_EUR = + getPriceDraft(BigDecimal.valueOf(111), EUR, DE, null, null, null, null, null); - public static final PriceDraft DRAFT_UK_111_GBP = getPriceDraft(BigDecimal.valueOf(111), GBP, - UK, null, null, null, null, null); + public static final PriceDraft DRAFT_111_EUR_01_02 = + getPriceDraft(BigDecimal.valueOf(111), EUR, null, null, byMonth(1), byMonth(2), null, null); - public static final PriceDraft DRAFT_DE_222_EUR_CUST1 = getPriceDraft(BigDecimal.valueOf(222), EUR, - DE, "cust1", null, null, null, null); + public static final PriceDraft DRAFT_111_EUR_CHANNEL1_01_02 = + getPriceDraft( + BigDecimal.valueOf(111), EUR, null, null, byMonth(1), byMonth(2), "channel1", null); - public static final PriceDraft DRAFT_DE_333_USD_CUST1 = getPriceDraft(BigDecimal.valueOf(333), USD, - DE, "cust1", null, null, null, null); + public static final PriceDraft DRAFT_DE_111_EUR_01_02 = + getPriceDraft(BigDecimal.valueOf(111), EUR, DE, null, byMonth(1), byMonth(2), null, null); - public static final PriceDraft DRAFT_UK_111_GBP_01_02 = getPriceDraft(BigDecimal.valueOf(111), GBP, UK, null, - byMonth(1), byMonth(2), null, null); + public static final PriceDraft DRAFT_DE_111_EUR_02_03 = + getPriceDraft(BigDecimal.valueOf(111), EUR, DE, null, byMonth(2), byMonth(3), null, null); - public static final PriceDraft DRAFT_UK_111_GBP_02_03 = getPriceDraft(BigDecimal.valueOf(111), GBP, UK, null, - byMonth(2), byMonth(3), null, null); + public static final PriceDraft DRAFT_DE_111_EUR_03_04 = + getPriceDraft(BigDecimal.valueOf(111), EUR, DE, null, byMonth(3), byMonth(4), null, null); - public static final PriceDraft DRAFT_UK_333_GBP_03_05 = getPriceDraft(BigDecimal.valueOf(333), GBP, UK, null, - byMonth(3), byMonth(5), null, null); + public static final PriceDraft DRAFT_DE_222_EUR_03_04 = + getPriceDraft(BigDecimal.valueOf(222), EUR, DE, null, byMonth(3), byMonth(4), null, null); - public static final PriceDraft DRAFT_UK_333_GBP_01_04 = getPriceDraft(BigDecimal.valueOf(333), GBP, UK, null, - byMonth(1), byMonth(4), null, null); + public static final PriceDraft DRAFT_DE_111_USD = + getPriceDraft(BigDecimal.valueOf(111), USD, DE, null, null, null, null, null); - public static final PriceDraft DRAFT_UK_444_GBP_04_06 = getPriceDraft(BigDecimal.valueOf(444), GBP, UK, null, - byMonth(4), byMonth(6), null, null); + public static final CurrencyUnit GBP = MoneyImpl.createCurrencyByCode("GBP"); - public static final PriceDraft DRAFT_666_USD_CUST1_01_02 = getPriceDraft(BigDecimal.valueOf(666), USD, null, - "cust1", byMonth(1), byMonth(2), null, null); + public static final PriceDraft DRAFT_UK_111_GBP = + getPriceDraft(BigDecimal.valueOf(111), GBP, UK, null, null, null, null, null); - public static final PriceDraft DRAFT_US_666_USD_CUST1_01_02 = getPriceDraft(BigDecimal.valueOf(666), USD, US, - "cust1", byMonth(1), byMonth(2), null, null); + public static final PriceDraft DRAFT_DE_222_EUR_CUST1 = + getPriceDraft(BigDecimal.valueOf(222), EUR, DE, "cust1", null, null, null, null); - public static final PriceDraft DRAFT_US_666_USD_CUST2_01_02 = getPriceDraft(BigDecimal.valueOf(666), USD, US, - "cust2", byMonth(1), byMonth(2), null, null); + public static final PriceDraft DRAFT_DE_333_USD_CUST1 = + getPriceDraft(BigDecimal.valueOf(333), USD, DE, "cust1", null, null, null, null); - public static final PriceDraft DRAFT_FR_888_EUR_01_03 = getPriceDraft(BigDecimal.valueOf(888), EUR, FR, null, - byMonth(1), byMonth(3), null, null); + public static final PriceDraft DRAFT_UK_111_GBP_01_02 = + getPriceDraft(BigDecimal.valueOf(111), GBP, UK, null, byMonth(1), byMonth(2), null, null); - public static final PriceDraft DRAFT_FR_777_EUR_01_04 = getPriceDraft(BigDecimal.valueOf(777), EUR, FR, null, - byMonth(1), byMonth(4), null, null); + public static final PriceDraft DRAFT_UK_111_GBP_02_03 = + getPriceDraft(BigDecimal.valueOf(111), GBP, UK, null, byMonth(2), byMonth(3), null, null); - public static final PriceDraft DRAFT_FR_999_EUR_03_06 = getPriceDraft(BigDecimal.valueOf(999), EUR, FR, null, - byMonth(3), byMonth(6), null, null); + public static final PriceDraft DRAFT_UK_333_GBP_03_05 = + getPriceDraft(BigDecimal.valueOf(333), GBP, UK, null, byMonth(3), byMonth(5), null, null); - public static final PriceDraft DRAFT_NE_777_EUR_01_04 = getPriceDraft(BigDecimal.valueOf(777), EUR, NE, null, - byMonth(1), byMonth(4), null, null); + public static final PriceDraft DRAFT_UK_333_GBP_01_04 = + getPriceDraft(BigDecimal.valueOf(333), GBP, UK, null, byMonth(1), byMonth(4), null, null); - public static final PriceDraft DRAFT_NE_777_EUR_05_07 = getPriceDraft(BigDecimal.valueOf(777), EUR, NE, null, - byMonth(5), byMonth(7), null, null); - - public static final PriceDraft DRAFT_NE_123_EUR_01_04 = getPriceDraft(BigDecimal.valueOf(123), EUR, NE, null, - byMonth(1), byMonth(4), null, null); - - public static final PriceDraft DRAFT_NE_321_EUR_04_06 = getPriceDraft(BigDecimal.valueOf(321), EUR, NE, null, - byMonth(4), byMonth(6), null, null); - - public static final PriceDraft DRAFT_NE_777_CUST1_CHANNEL1_EUR_05_07 = getPriceDraft(BigDecimal.valueOf(777), EUR, - NE, "cust1", - byMonth(5), byMonth(7), "channel1", null); - - public static final PriceDraft DRAFT_777_CUST1_CHANNEL1_EUR_05_07 = getPriceDraft(BigDecimal.valueOf(777), EUR, - null, "cust1", byMonth(5), byMonth(7), "channel1", null); - - public static final PriceDraft DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX = - getPriceDraft(BigDecimal.valueOf(100), EUR, DE, null, byMonth(1), byMonth(2), - "channel1", CustomFieldsDraft.ofTypeIdAndJson("customType1", - createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); - - public static final PriceDraft DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX = - getPriceDraft(BigDecimal.valueOf(100), EUR, DE, null, - byMonth(1), byMonth(2), "channel2", CustomFieldsDraft.ofTypeIdAndJson("customType1", - createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); - - public static final PriceDraft DRAFT_UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDX = - getPriceDraft(BigDecimal.valueOf(22), GBP, UK, null, null, null, null, - CustomFieldsDraft.ofTypeIdAndJson("customType1", - createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); - - public static final PriceDraft DRAFT_UK_22_USD_CUSTOMTYPE1_CUSTOMFIELDX = - getPriceDraft(BigDecimal.valueOf(22), USD, UK, null, null, null, null, - CustomFieldsDraft.ofTypeIdAndJson("customType1", - createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); - - public static final PriceDraft DRAFT_UK_666_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX = - getPriceDraft(BigDecimal.valueOf(666), GBP, UK, null, null, null, "channel1", - CustomFieldsDraft.ofTypeIdAndJson("customType1", - createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); - - - public static final PriceDraft DRAFT_DE_22_USD = getPriceDraft(BigDecimal.valueOf(22), USD, DE, - null, null, null, null, null); - - public static final PriceDraft DRAFT_UK_999_GBP = getPriceDraft(BigDecimal.valueOf(999), GBP, - UK, null, null, null, null, null); - - @Nonnull - public static ZonedDateTime byMonth(final int month) { - return ZonedDateTime.of(2018, month, 1, 0, 0, 0, 0, ZoneId.of("Z")); - } - - @Nonnull - public static Map createCustomFieldsJsonMap(@Nonnull final String fieldName, - @Nonnull final JsonNode fieldValue) { - final Map customFieldsJsons = new HashMap<>(); - customFieldsJsons.put(fieldName, fieldValue); - return customFieldsJsons; - } - - - @Nonnull - public static PriceDraft getPriceDraft(@Nonnull final BigDecimal value, - @Nonnull final CurrencyUnit currencyUnits, - @Nullable final CountryCode countryCode, - @Nullable final String customerGroupId, - @Nullable final ZonedDateTime validFrom, - @Nullable final ZonedDateTime validUntil, - @Nullable final String channelId, - @Nullable final CustomFieldsDraft customFieldsDraft) { - return PriceDraftBuilder.of(Price.of(value, currencyUnits)) - .country(countryCode) - .customerGroup( - ofNullable(customerGroupId).map(CustomerGroup::referenceOfId).orElse(null)) - .validFrom(validFrom) - .validUntil(validUntil) - .channel(ofNullable(channelId).map(ResourceIdentifier::ofId) - .orElse(null)) - .custom(customFieldsDraft) - .build(); - } - - @Nonnull - public static PriceDraft getPriceDraftWithChannelKey(@Nonnull final BigDecimal value, - @Nonnull final CurrencyUnit currencyUnits, - @Nullable final CountryCode countryCode, - @Nullable final String customerGroupId, - @Nullable final ZonedDateTime validFrom, - @Nullable final ZonedDateTime validUntil, - @Nullable final String channelKey, - @Nullable final CustomFieldsDraft customFieldsDraft) { - return PriceDraftBuilder.of(Price.of(value, currencyUnits)) - .country(countryCode) - .customerGroup( - ofNullable(customerGroupId).map(CustomerGroup::referenceOfId).orElse(null)) - .validFrom(validFrom) - .validUntil(validUntil) - .channel(ofNullable(channelKey).map(ResourceIdentifier::ofKey) - .orElse(null)) - .custom(customFieldsDraft) - .build(); - } - - private PriceDraftFixtures() { - } + public static final PriceDraft DRAFT_UK_444_GBP_04_06 = + getPriceDraft(BigDecimal.valueOf(444), GBP, UK, null, byMonth(4), byMonth(6), null, null); + + public static final PriceDraft DRAFT_666_USD_CUST1_01_02 = + getPriceDraft( + BigDecimal.valueOf(666), USD, null, "cust1", byMonth(1), byMonth(2), null, null); + + public static final PriceDraft DRAFT_US_666_USD_CUST1_01_02 = + getPriceDraft(BigDecimal.valueOf(666), USD, US, "cust1", byMonth(1), byMonth(2), null, null); + + public static final PriceDraft DRAFT_US_666_USD_CUST2_01_02 = + getPriceDraft(BigDecimal.valueOf(666), USD, US, "cust2", byMonth(1), byMonth(2), null, null); + + public static final PriceDraft DRAFT_FR_888_EUR_01_03 = + getPriceDraft(BigDecimal.valueOf(888), EUR, FR, null, byMonth(1), byMonth(3), null, null); + + public static final PriceDraft DRAFT_FR_777_EUR_01_04 = + getPriceDraft(BigDecimal.valueOf(777), EUR, FR, null, byMonth(1), byMonth(4), null, null); + + public static final PriceDraft DRAFT_FR_999_EUR_03_06 = + getPriceDraft(BigDecimal.valueOf(999), EUR, FR, null, byMonth(3), byMonth(6), null, null); + + public static final PriceDraft DRAFT_NE_777_EUR_01_04 = + getPriceDraft(BigDecimal.valueOf(777), EUR, NE, null, byMonth(1), byMonth(4), null, null); + + public static final PriceDraft DRAFT_NE_777_EUR_05_07 = + getPriceDraft(BigDecimal.valueOf(777), EUR, NE, null, byMonth(5), byMonth(7), null, null); + + public static final PriceDraft DRAFT_NE_123_EUR_01_04 = + getPriceDraft(BigDecimal.valueOf(123), EUR, NE, null, byMonth(1), byMonth(4), null, null); + + public static final PriceDraft DRAFT_NE_321_EUR_04_06 = + getPriceDraft(BigDecimal.valueOf(321), EUR, NE, null, byMonth(4), byMonth(6), null, null); + + public static final PriceDraft DRAFT_NE_777_CUST1_CHANNEL1_EUR_05_07 = + getPriceDraft( + BigDecimal.valueOf(777), EUR, NE, "cust1", byMonth(5), byMonth(7), "channel1", null); + + public static final PriceDraft DRAFT_777_CUST1_CHANNEL1_EUR_05_07 = + getPriceDraft( + BigDecimal.valueOf(777), EUR, null, "cust1", byMonth(5), byMonth(7), "channel1", null); + + public static final PriceDraft DRAFT_DE_100_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX = + getPriceDraft( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + "channel1", + CustomFieldsDraft.ofTypeIdAndJson( + "customType1", + createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); + + public static final PriceDraft DRAFT_DE_100_EUR_01_02_CHANNEL2_CUSTOMTYPE1_CUSTOMFIELDX = + getPriceDraft( + BigDecimal.valueOf(100), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + "channel2", + CustomFieldsDraft.ofTypeIdAndJson( + "customType1", + createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); + + public static final PriceDraft DRAFT_UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDX = + getPriceDraft( + BigDecimal.valueOf(22), + GBP, + UK, + null, + null, + null, + null, + CustomFieldsDraft.ofTypeIdAndJson( + "customType1", + createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); + + public static final PriceDraft DRAFT_UK_22_USD_CUSTOMTYPE1_CUSTOMFIELDX = + getPriceDraft( + BigDecimal.valueOf(22), + USD, + UK, + null, + null, + null, + null, + CustomFieldsDraft.ofTypeIdAndJson( + "customType1", + createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); + + public static final PriceDraft DRAFT_UK_666_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX = + getPriceDraft( + BigDecimal.valueOf(666), + GBP, + UK, + null, + null, + null, + "channel1", + CustomFieldsDraft.ofTypeIdAndJson( + "customType1", + createCustomFieldsJsonMap("foo", JsonNodeFactory.instance.textNode("X")))); + + public static final PriceDraft DRAFT_DE_22_USD = + getPriceDraft(BigDecimal.valueOf(22), USD, DE, null, null, null, null, null); + + public static final PriceDraft DRAFT_UK_999_GBP = + getPriceDraft(BigDecimal.valueOf(999), GBP, UK, null, null, null, null, null); + + @Nonnull + public static ZonedDateTime byMonth(final int month) { + return ZonedDateTime.of(2018, month, 1, 0, 0, 0, 0, ZoneId.of("Z")); + } + + @Nonnull + public static Map createCustomFieldsJsonMap( + @Nonnull final String fieldName, @Nonnull final JsonNode fieldValue) { + final Map customFieldsJsons = new HashMap<>(); + customFieldsJsons.put(fieldName, fieldValue); + return customFieldsJsons; + } + + @Nonnull + public static PriceDraft getPriceDraft( + @Nonnull final BigDecimal value, + @Nonnull final CurrencyUnit currencyUnits, + @Nullable final CountryCode countryCode, + @Nullable final String customerGroupId, + @Nullable final ZonedDateTime validFrom, + @Nullable final ZonedDateTime validUntil, + @Nullable final String channelId, + @Nullable final CustomFieldsDraft customFieldsDraft) { + return PriceDraftBuilder.of(Price.of(value, currencyUnits)) + .country(countryCode) + .customerGroup(ofNullable(customerGroupId).map(CustomerGroup::referenceOfId).orElse(null)) + .validFrom(validFrom) + .validUntil(validUntil) + .channel(ofNullable(channelId).map(ResourceIdentifier::ofId).orElse(null)) + .custom(customFieldsDraft) + .build(); + } + + @Nonnull + public static PriceDraft getPriceDraftWithChannelKey( + @Nonnull final BigDecimal value, + @Nonnull final CurrencyUnit currencyUnits, + @Nullable final CountryCode countryCode, + @Nullable final String customerGroupId, + @Nullable final ZonedDateTime validFrom, + @Nullable final ZonedDateTime validUntil, + @Nullable final String channelKey, + @Nullable final CustomFieldsDraft customFieldsDraft) { + return PriceDraftBuilder.of(Price.of(value, currencyUnits)) + .country(countryCode) + .customerGroup(ofNullable(customerGroupId).map(CustomerGroup::referenceOfId).orElse(null)) + .validFrom(validFrom) + .validUntil(validUntil) + .channel(ofNullable(channelKey).map(ResourceIdentifier::ofKey).orElse(null)) + .custom(customFieldsDraft) + .build(); + } + + private PriceDraftFixtures() {} } diff --git a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/PriceFixtures.java b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/PriceFixtures.java index 9ed5a6528c..a6217a3fed 100644 --- a/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/PriceFixtures.java +++ b/src/test/java/com/commercetools/sync/products/utils/productvariantupdateactionutils/prices/PriceFixtures.java @@ -1,20 +1,5 @@ package com.commercetools.sync.products.utils.productvariantupdateactionutils.prices; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.neovisionaries.i18n.CountryCode; -import io.sphere.sdk.channels.Channel; -import io.sphere.sdk.customergroups.CustomerGroup; -import io.sphere.sdk.products.Price; -import io.sphere.sdk.products.PriceBuilder; -import io.sphere.sdk.types.CustomFields; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.money.CurrencyUnit; -import java.math.BigDecimal; -import java.time.ZonedDateTime; -import java.util.UUID; - import static com.commercetools.sync.commons.MockUtils.getMockCustomFields; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.GBP; import static com.commercetools.sync.products.utils.productvariantupdateactionutils.prices.PriceDraftFixtures.byMonth; @@ -25,186 +10,233 @@ import static com.neovisionaries.i18n.CountryCode.US; import static io.sphere.sdk.models.DefaultCurrencyUnits.EUR; import static io.sphere.sdk.models.DefaultCurrencyUnits.USD; - import static java.util.Optional.ofNullable; -public final class PriceFixtures { - public static final Price USD_111 = getPrice(BigDecimal.valueOf(111), USD, null, - null, null, null, null, null); - - public static final Price US_111_USD = getPrice(BigDecimal.valueOf(111), USD, US, - null, null, null, null, null); - - public static final Price DE_111_EUR = getPrice(BigDecimal.valueOf(111), EUR, DE, null, null, null, null, null); - - public static final Price DE_111_EUR_01_02 = getPrice(BigDecimal.valueOf(111), EUR, - DE, null, byMonth(1), byMonth(2), null, null); - - public static final Price DE_111_EUR_02_03 = getPrice(BigDecimal.valueOf(111), EUR, - DE, null, byMonth(2), byMonth(3), null, null); - - public static final Price DE_111_USD = getPrice(BigDecimal.valueOf(111), USD, DE, - null, null, null, null, null); - - public static final Price EUR_345_CHANNEL1 = getPrice(BigDecimal.valueOf(345), EUR, null, null, - null, null, "channel1", null); - - public static final Price EUR_345_CHANNEL1_CUST1 = getPrice(BigDecimal.valueOf(345), EUR, null, "cust1", - null, null, "channel1", null); - - public static final Price DE_345_EUR_CHANNEL1_CUST1 = getPrice(BigDecimal.valueOf(345), EUR, DE, "cust1", - null, null, "channel1", null); - - public static final Price DE_345_EUR_CHANNEL1 = getPrice(BigDecimal.valueOf(345), EUR, DE, null, - null, null, "channel1", null); - - public static final Price EUR_345_CUST2 = getPrice(BigDecimal.valueOf(345), EUR, null, "cust2", - null, null, null, null); - public static final Price DE_345_EUR_CUST2 = getPrice(BigDecimal.valueOf(345), EUR, DE, "cust2", - null, null, null, null); - public static final Price DE_567_EUR_CUST3 = getPrice(BigDecimal.valueOf(567), EUR, DE, "cust3", - null, null, null, null); - - public static final Price USD_111_FROM_01 = getPrice(BigDecimal.valueOf(111), USD, null, - null, byMonth(1), null, null, null); - - public static final Price US_111_USD_FROM_01 = getPrice(BigDecimal.valueOf(111), USD, US, - null, byMonth(1), null, null, null); - - public static final Price USD_111_CHANNEL1_FROM_01 = getPrice(BigDecimal.valueOf(111), USD, null, - null, byMonth(1), null, "channel1", null); - - public static final Price US_111_USD_CHANNEL1_FROM_01 = getPrice(BigDecimal.valueOf(111), USD, US, - null, byMonth(1), null, "channel1", null); - - public static final Price USD_111_CUST1_FROM_01 = getPrice(BigDecimal.valueOf(111), USD, null, - "cust1", byMonth(1), null, null, null); - - public static final Price US_111_USD_CUST1_FROM_01 = getPrice(BigDecimal.valueOf(111), USD, US, - "cust1", byMonth(1), null, null, null); - - public static final Price USD_111_CHANNEL1_CUST1_FROM_01 = getPrice(BigDecimal.valueOf(111), USD, null, - "cust1", byMonth(1), null, "channel1", null); - - public static final Price US_111_USD_CHANNEL1_CUST1_FROM_01 = getPrice(BigDecimal.valueOf(111), USD, US, - "cust1", byMonth(1), null, "channel1", null); - - public static final Price USD_111_UNTIL_01 = getPrice(BigDecimal.valueOf(111), USD, null, - null, null, byMonth(1), null, null); - - public static final Price US_111_USD_UNTIL_01 = getPrice(BigDecimal.valueOf(111), USD, US, - null, null, byMonth(1), null, null); - - public static final Price USD_111_CHANNEL1_UNTIL_01 = getPrice(BigDecimal.valueOf(111), USD, null, - null, null, byMonth(1), "channel1", null); +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.neovisionaries.i18n.CountryCode; +import io.sphere.sdk.channels.Channel; +import io.sphere.sdk.customergroups.CustomerGroup; +import io.sphere.sdk.products.Price; +import io.sphere.sdk.products.PriceBuilder; +import io.sphere.sdk.types.CustomFields; +import java.math.BigDecimal; +import java.time.ZonedDateTime; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.money.CurrencyUnit; - public static final Price US_111_USD_CHANNEL1_UNTIL_01 = getPrice(BigDecimal.valueOf(111), USD, US, - null, null, byMonth(1), "channel1", null); +public final class PriceFixtures { + public static final Price USD_111 = + getPrice(BigDecimal.valueOf(111), USD, null, null, null, null, null, null); - public static final Price USD_111_CUST1_UNTIL_01 = getPrice(BigDecimal.valueOf(111), USD, null, - "cust1", null, byMonth(1), null, null); + public static final Price US_111_USD = + getPrice(BigDecimal.valueOf(111), USD, US, null, null, null, null, null); - public static final Price US_111_USD_CUST1_UNTIL_01 = getPrice(BigDecimal.valueOf(111), USD, US, - "cust1", null, byMonth(1), null, null); + public static final Price DE_111_EUR = + getPrice(BigDecimal.valueOf(111), EUR, DE, null, null, null, null, null); - public static final Price USD_111_CHANNEL1_CUST1_UNTIL_01 = getPrice(BigDecimal.valueOf(111), USD, null, - "cust1", null, byMonth(1), "channel1", null); + public static final Price DE_111_EUR_01_02 = + getPrice(BigDecimal.valueOf(111), EUR, DE, null, byMonth(1), byMonth(2), null, null); - public static final Price US_111_USD_CHANNEL1_CUST1_UNTIL_01 = getPrice(BigDecimal.valueOf(111), USD, US, - "cust1", null, byMonth(1), "channel1", null); + public static final Price DE_111_EUR_02_03 = + getPrice(BigDecimal.valueOf(111), EUR, DE, null, byMonth(2), byMonth(3), null, null); - public static final Price USD_111_01_02 = getPrice(BigDecimal.valueOf(111), USD, null, - null, byMonth(1), byMonth(2), null, null); + public static final Price DE_111_USD = + getPrice(BigDecimal.valueOf(111), USD, DE, null, null, null, null, null); - public static final Price US_111_USD_01_02 = getPrice(BigDecimal.valueOf(111), USD, US, - null, byMonth(1), byMonth(2), null, null); + public static final Price EUR_345_CHANNEL1 = + getPrice(BigDecimal.valueOf(345), EUR, null, null, null, null, "channel1", null); - public static final Price USD_111_CUST1_01_02 = getPrice(BigDecimal.valueOf(111), USD, null, - "cust1", byMonth(1), byMonth(2), null, null); + public static final Price EUR_345_CHANNEL1_CUST1 = + getPrice(BigDecimal.valueOf(345), EUR, null, "cust1", null, null, "channel1", null); - public static final Price US_111_USD_CUST1_01_02 = getPrice(BigDecimal.valueOf(111), USD, US, - "cust1", byMonth(1), byMonth(2), null, null); + public static final Price DE_345_EUR_CHANNEL1_CUST1 = + getPrice(BigDecimal.valueOf(345), EUR, DE, "cust1", null, null, "channel1", null); - public static final Price USD_111_CHANNEL1_01_02 = getPrice(BigDecimal.valueOf(111), USD, null, - null, byMonth(1), byMonth(2), "channel1", null); + public static final Price DE_345_EUR_CHANNEL1 = + getPrice(BigDecimal.valueOf(345), EUR, DE, null, null, null, "channel1", null); - public static final Price UK_111_GBP_01_02 = getPrice(BigDecimal.valueOf(111), GBP, UK, - null, byMonth(1), byMonth(2), null, null); + public static final Price EUR_345_CUST2 = + getPrice(BigDecimal.valueOf(345), EUR, null, "cust2", null, null, null, null); + public static final Price DE_345_EUR_CUST2 = + getPrice(BigDecimal.valueOf(345), EUR, DE, "cust2", null, null, null, null); + public static final Price DE_567_EUR_CUST3 = + getPrice(BigDecimal.valueOf(567), EUR, DE, "cust3", null, null, null, null); - public static final Price UK_111_GBP_02_03 = getPrice(BigDecimal.valueOf(111), GBP, UK, - null, byMonth(2), byMonth(3), null, null); + public static final Price USD_111_FROM_01 = + getPrice(BigDecimal.valueOf(111), USD, null, null, byMonth(1), null, null, null); - public static final Price UK_333_GBP_03_05 = getPrice(BigDecimal.valueOf(333), GBP, UK, - null, byMonth(3), byMonth(5), null, null); + public static final Price US_111_USD_FROM_01 = + getPrice(BigDecimal.valueOf(111), USD, US, null, byMonth(1), null, null, null); - public static final Price US_555_USD_CUST2_01_02 = getPrice(BigDecimal.valueOf(555), USD, US, - "cust2", byMonth(1), byMonth(2), null, null); + public static final Price USD_111_CHANNEL1_FROM_01 = + getPrice(BigDecimal.valueOf(111), USD, null, null, byMonth(1), null, "channel1", null); - public static final Price USD_555_CUST2_CHANNEL1_01_02 = getPrice(BigDecimal.valueOf(555), USD, null, - "cust2", byMonth(1), byMonth(2), "channel1", null); + public static final Price US_111_USD_CHANNEL1_FROM_01 = + getPrice(BigDecimal.valueOf(111), USD, US, null, byMonth(1), null, "channel1", null); - public static final Price US_555_USD_CUST2_CHANNEL1_01_02 = getPrice(BigDecimal.valueOf(555), USD, US, - "cust2", byMonth(1), byMonth(2), "channel1", null); + public static final Price USD_111_CUST1_FROM_01 = + getPrice(BigDecimal.valueOf(111), USD, null, "cust1", byMonth(1), null, null, null); - public static final Price FR_777_EUR_01_04 = getPrice(BigDecimal.valueOf(777), EUR, FR, - null, byMonth(1), byMonth(4), null, null); + public static final Price US_111_USD_CUST1_FROM_01 = + getPrice(BigDecimal.valueOf(111), USD, US, "cust1", byMonth(1), null, null, null); - public static final Price NE_123_EUR_01_04 = getPrice(BigDecimal.valueOf(123), EUR, NE, - null, byMonth(1), byMonth(4), null, null); + public static final Price USD_111_CHANNEL1_CUST1_FROM_01 = + getPrice(BigDecimal.valueOf(111), USD, null, "cust1", byMonth(1), null, "channel1", null); - public static final Price NE_321_EUR_04_06 = getPrice(BigDecimal.valueOf(321), EUR, NE, - null, byMonth(4), byMonth(6), null, null); + public static final Price US_111_USD_CHANNEL1_CUST1_FROM_01 = + getPrice(BigDecimal.valueOf(111), USD, US, "cust1", byMonth(1), null, "channel1", null); - public static final Price DE_666_EUR = getPrice(BigDecimal.valueOf(666), EUR, DE, null, null, null, null, null); + public static final Price USD_111_UNTIL_01 = + getPrice(BigDecimal.valueOf(111), USD, null, null, null, byMonth(1), null, null); - public static final Price DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY = getPrice(BigDecimal.valueOf(222), - EUR, DE, null, byMonth(1), byMonth(2), - "channel1", getMockCustomFields("customType1", "foo", JsonNodeFactory.instance.textNode("Y"))); + public static final Price US_111_USD_UNTIL_01 = + getPrice(BigDecimal.valueOf(111), USD, US, null, null, byMonth(1), null, null); - public static final Price DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX = getPrice(BigDecimal.valueOf(222), - EUR, - DE, null, - byMonth(1), byMonth(2), - "channel2", getMockCustomFields("customType2", "foo", JsonNodeFactory.instance.textNode("X"))); + public static final Price USD_111_CHANNEL1_UNTIL_01 = + getPrice(BigDecimal.valueOf(111), USD, null, null, null, byMonth(1), "channel1", null); - public static final Price UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDY = getPrice(BigDecimal.valueOf(22), GBP, - UK, null, null, null, null, - getMockCustomFields("customType1", "foo", JsonNodeFactory.instance.textNode("Y"))); + public static final Price US_111_USD_CHANNEL1_UNTIL_01 = + getPrice(BigDecimal.valueOf(111), USD, US, null, null, byMonth(1), "channel1", null); - public static final Price UK_22_USD_CUSTOMTYPE2_CUSTOMFIELDX = getPrice(BigDecimal.valueOf(22), USD, - UK, null, null, null, null, - getMockCustomFields("customType2", "foo", JsonNodeFactory.instance.textNode("X"))); + public static final Price USD_111_CUST1_UNTIL_01 = + getPrice(BigDecimal.valueOf(111), USD, null, "cust1", null, byMonth(1), null, null); - public static final Price UK_1_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX = getPrice(BigDecimal.valueOf(1), GBP, - UK, null, null, null, "channel1", - getMockCustomFields("customType1", "foo", JsonNodeFactory.instance.textNode("X"))); + public static final Price US_111_USD_CUST1_UNTIL_01 = + getPrice(BigDecimal.valueOf(111), USD, US, "cust1", null, byMonth(1), null, null); - public static final Price DE_22_USD = getPrice(BigDecimal.valueOf(22), USD, DE, - null, null, null, null, null); + public static final Price USD_111_CHANNEL1_CUST1_UNTIL_01 = + getPrice(BigDecimal.valueOf(111), USD, null, "cust1", null, byMonth(1), "channel1", null); + public static final Price US_111_USD_CHANNEL1_CUST1_UNTIL_01 = + getPrice(BigDecimal.valueOf(111), USD, US, "cust1", null, byMonth(1), "channel1", null); - @Nonnull - private static Price getPrice(@Nonnull final BigDecimal value, - @Nonnull final CurrencyUnit currencyUnits, - @Nullable final CountryCode countryCode, - @Nullable final String customerGroupId, - @Nullable final ZonedDateTime validFrom, - @Nullable final ZonedDateTime validUntil, - @Nullable final String channelId, - @Nullable final CustomFields customFields) { + public static final Price USD_111_01_02 = + getPrice(BigDecimal.valueOf(111), USD, null, null, byMonth(1), byMonth(2), null, null); - return PriceBuilder.of(Price.of(value, currencyUnits)) - .id(UUID.randomUUID().toString()) - .customerGroup(ofNullable(customerGroupId).map(CustomerGroup::referenceOfId).orElse(null)) - .country(countryCode) - .validFrom(validFrom) - .validUntil(validUntil) - .channel(ofNullable(channelId).map(Channel::referenceOfId).orElse(null)) - .custom(customFields) - .build(); - } + public static final Price US_111_USD_01_02 = + getPrice(BigDecimal.valueOf(111), USD, US, null, byMonth(1), byMonth(2), null, null); + public static final Price USD_111_CUST1_01_02 = + getPrice(BigDecimal.valueOf(111), USD, null, "cust1", byMonth(1), byMonth(2), null, null); - private PriceFixtures() { - } + public static final Price US_111_USD_CUST1_01_02 = + getPrice(BigDecimal.valueOf(111), USD, US, "cust1", byMonth(1), byMonth(2), null, null); + + public static final Price USD_111_CHANNEL1_01_02 = + getPrice(BigDecimal.valueOf(111), USD, null, null, byMonth(1), byMonth(2), "channel1", null); + + public static final Price UK_111_GBP_01_02 = + getPrice(BigDecimal.valueOf(111), GBP, UK, null, byMonth(1), byMonth(2), null, null); + + public static final Price UK_111_GBP_02_03 = + getPrice(BigDecimal.valueOf(111), GBP, UK, null, byMonth(2), byMonth(3), null, null); + + public static final Price UK_333_GBP_03_05 = + getPrice(BigDecimal.valueOf(333), GBP, UK, null, byMonth(3), byMonth(5), null, null); + + public static final Price US_555_USD_CUST2_01_02 = + getPrice(BigDecimal.valueOf(555), USD, US, "cust2", byMonth(1), byMonth(2), null, null); + + public static final Price USD_555_CUST2_CHANNEL1_01_02 = + getPrice( + BigDecimal.valueOf(555), USD, null, "cust2", byMonth(1), byMonth(2), "channel1", null); + + public static final Price US_555_USD_CUST2_CHANNEL1_01_02 = + getPrice(BigDecimal.valueOf(555), USD, US, "cust2", byMonth(1), byMonth(2), "channel1", null); + + public static final Price FR_777_EUR_01_04 = + getPrice(BigDecimal.valueOf(777), EUR, FR, null, byMonth(1), byMonth(4), null, null); + + public static final Price NE_123_EUR_01_04 = + getPrice(BigDecimal.valueOf(123), EUR, NE, null, byMonth(1), byMonth(4), null, null); + + public static final Price NE_321_EUR_04_06 = + getPrice(BigDecimal.valueOf(321), EUR, NE, null, byMonth(4), byMonth(6), null, null); + + public static final Price DE_666_EUR = + getPrice(BigDecimal.valueOf(666), EUR, DE, null, null, null, null, null); + + public static final Price DE_222_EUR_01_02_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDY = + getPrice( + BigDecimal.valueOf(222), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + "channel1", + getMockCustomFields("customType1", "foo", JsonNodeFactory.instance.textNode("Y"))); + + public static final Price DE_222_EUR_01_02_CHANNEL2_CUSTOMTYPE2_CUSTOMFIELDX = + getPrice( + BigDecimal.valueOf(222), + EUR, + DE, + null, + byMonth(1), + byMonth(2), + "channel2", + getMockCustomFields("customType2", "foo", JsonNodeFactory.instance.textNode("X"))); + + public static final Price UK_22_GBP_CUSTOMTYPE1_CUSTOMFIELDY = + getPrice( + BigDecimal.valueOf(22), + GBP, + UK, + null, + null, + null, + null, + getMockCustomFields("customType1", "foo", JsonNodeFactory.instance.textNode("Y"))); + + public static final Price UK_22_USD_CUSTOMTYPE2_CUSTOMFIELDX = + getPrice( + BigDecimal.valueOf(22), + USD, + UK, + null, + null, + null, + null, + getMockCustomFields("customType2", "foo", JsonNodeFactory.instance.textNode("X"))); + + public static final Price UK_1_GBP_CHANNEL1_CUSTOMTYPE1_CUSTOMFIELDX = + getPrice( + BigDecimal.valueOf(1), + GBP, + UK, + null, + null, + null, + "channel1", + getMockCustomFields("customType1", "foo", JsonNodeFactory.instance.textNode("X"))); + + public static final Price DE_22_USD = + getPrice(BigDecimal.valueOf(22), USD, DE, null, null, null, null, null); + + @Nonnull + private static Price getPrice( + @Nonnull final BigDecimal value, + @Nonnull final CurrencyUnit currencyUnits, + @Nullable final CountryCode countryCode, + @Nullable final String customerGroupId, + @Nullable final ZonedDateTime validFrom, + @Nullable final ZonedDateTime validUntil, + @Nullable final String channelId, + @Nullable final CustomFields customFields) { + + return PriceBuilder.of(Price.of(value, currencyUnits)) + .id(UUID.randomUUID().toString()) + .customerGroup(ofNullable(customerGroupId).map(CustomerGroup::referenceOfId).orElse(null)) + .country(countryCode) + .validFrom(validFrom) + .validUntil(validUntil) + .channel(ofNullable(channelId).map(Channel::referenceOfId).orElse(null)) + .custom(customFields) + .build(); + } + + private PriceFixtures() {} } diff --git a/src/test/java/com/commercetools/sync/producttypes/ProductTypeSyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/producttypes/ProductTypeSyncOptionsBuilderTest.java index 99bf2f95f5..9a3243d80f 100644 --- a/src/test/java/com/commercetools/sync/producttypes/ProductTypeSyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/ProductTypeSyncOptionsBuilderTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.producttypes; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -10,280 +19,278 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.ProductTypeDraftBuilder; import io.sphere.sdk.producttypes.commands.updateactions.ChangeName; -import org.junit.jupiter.api.Test; - import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductTypeSyncOptionsBuilderTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private ProductTypeSyncOptionsBuilder productTypeSyncOptionsBuilder = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT); - - @Test - void of_WithClient_ShouldCreateProductTypeSyncOptionsBuilder() { - final ProductTypeSyncOptionsBuilder builder = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT); - assertThat(builder).isNotNull(); - } - - @Test - void build_WithClient_ShouldBuildProductSyncOptions() { - final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); - assertThat(productTypeSyncOptions).isNotNull(); - assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNull(); - assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNull(); - assertThat(productTypeSyncOptions.getErrorCallback()).isNull(); - assertThat(productTypeSyncOptions.getWarningCallback()).isNull(); - assertThat(productTypeSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); - assertThat(productTypeSyncOptions.getBatchSize()).isEqualTo(ProductTypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(productTypeSyncOptions.getCacheSize()).isEqualTo(10_000); - } - - @Test - void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { - final TriFunction>, - ProductTypeDraft, ProductType, List>> - beforeUpdateCallback = (updateActions, newProductType, oldProductType) -> Collections.emptyList(); - productTypeSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - - final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); - assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNotNull(); - } - - @Test - void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { - productTypeSyncOptionsBuilder.beforeCreateCallback((newProductType) -> null); - - final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); - assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNotNull(); - } - - @Test - public void errorCallBack_WithCallBack_ShouldSetCallBack() { - final QuadConsumer, Optional, - List>> mockErrorCallback = (exception, newDraft, old, actions) -> { - }; - productTypeSyncOptionsBuilder.errorCallback(mockErrorCallback); - - final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); - assertThat(productTypeSyncOptions.getErrorCallback()).isNotNull(); - } - - @Test - public void warningCallBack_WithCallBack_ShouldSetCallBack() { - final TriConsumer, Optional> mockWarningCallBack = - (exception, newDraft, old) -> { - }; - productTypeSyncOptionsBuilder.warningCallback(mockWarningCallBack); - - final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); - assertThat(productTypeSyncOptions.getWarningCallback()).isNotNull(); - } - - @Test - void getThis_ShouldReturnCorrectInstance() { - final ProductTypeSyncOptionsBuilder instance = productTypeSyncOptionsBuilder.getThis(); - assertThat(instance).isNotNull(); - assertThat(instance).isInstanceOf(ProductTypeSyncOptionsBuilder.class); - assertThat(instance).isEqualTo(productTypeSyncOptionsBuilder); - } - - @Test - void productTypeSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(CTP_CLIENT) + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private ProductTypeSyncOptionsBuilder productTypeSyncOptionsBuilder = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateProductTypeSyncOptionsBuilder() { + final ProductTypeSyncOptionsBuilder builder = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT); + assertThat(builder).isNotNull(); + } + + @Test + void build_WithClient_ShouldBuildProductSyncOptions() { + final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); + assertThat(productTypeSyncOptions).isNotNull(); + assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNull(); + assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNull(); + assertThat(productTypeSyncOptions.getErrorCallback()).isNull(); + assertThat(productTypeSyncOptions.getWarningCallback()).isNull(); + assertThat(productTypeSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); + assertThat(productTypeSyncOptions.getBatchSize()) + .isEqualTo(ProductTypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + assertThat(productTypeSyncOptions.getCacheSize()).isEqualTo(10_000); + } + + @Test + void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { + final TriFunction< + List>, + ProductTypeDraft, + ProductType, + List>> + beforeUpdateCallback = + (updateActions, newProductType, oldProductType) -> Collections.emptyList(); + productTypeSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); + + final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); + assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNotNull(); + } + + @Test + void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { + productTypeSyncOptionsBuilder.beforeCreateCallback((newProductType) -> null); + + final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); + assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNotNull(); + } + + @Test + public void errorCallBack_WithCallBack_ShouldSetCallBack() { + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + mockErrorCallback = (exception, newDraft, old, actions) -> {}; + productTypeSyncOptionsBuilder.errorCallback(mockErrorCallback); + + final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); + assertThat(productTypeSyncOptions.getErrorCallback()).isNotNull(); + } + + @Test + public void warningCallBack_WithCallBack_ShouldSetCallBack() { + final TriConsumer, Optional> + mockWarningCallBack = (exception, newDraft, old) -> {}; + productTypeSyncOptionsBuilder.warningCallback(mockWarningCallBack); + + final ProductTypeSyncOptions productTypeSyncOptions = productTypeSyncOptionsBuilder.build(); + assertThat(productTypeSyncOptions.getWarningCallback()).isNotNull(); + } + + @Test + void getThis_ShouldReturnCorrectInstance() { + final ProductTypeSyncOptionsBuilder instance = productTypeSyncOptionsBuilder.getThis(); + assertThat(instance).isNotNull(); + assertThat(instance).isInstanceOf(ProductTypeSyncOptionsBuilder.class); + assertThat(instance).isEqualTo(productTypeSyncOptionsBuilder); + } + + @Test + void productTypeSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) .batchSize(30) .beforeCreateCallback((newProduct) -> null) - .beforeUpdateCallback((updateActions, newProductType, oldProductType) -> Collections.emptyList()) + .beforeUpdateCallback( + (updateActions, newProductType, oldProductType) -> Collections.emptyList()) + .build(); + assertThat(productTypeSyncOptions).isNotNull(); + } + + @Test + void batchSize_WithPositiveValue_ShouldSetBatchSize() { + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); + assertThat(productTypeSyncOptions.getBatchSize()).isEqualTo(10); + } + + @Test + void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final ProductTypeSyncOptions productTypeSyncOptionsWithZeroBatchSize = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); + + assertThat(productTypeSyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(ProductTypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + + final ProductTypeSyncOptions productTypeSyncOptionsWithNegativeBatchSize = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); + + assertThat(productTypeSyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(ProductTypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + + @Test + void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNull(); + + final List> updateActions = singletonList(ChangeName.of("name")); + final List> filteredList = + productTypeSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ProductTypeDraft.class), mock(ProductType.class)); + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { + final TriFunction< + List>, + ProductTypeDraft, + ProductType, + List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) .build(); - assertThat(productTypeSyncOptions).isNotNull(); - } - - @Test - void batchSize_WithPositiveValue_ShouldSetBatchSize() { - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(10) - .build(); - assertThat(productTypeSyncOptions.getBatchSize()).isEqualTo(10); - } - - @Test - void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final ProductTypeSyncOptions productTypeSyncOptionsWithZeroBatchSize = - ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(0) - .build(); - - assertThat(productTypeSyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(ProductTypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - - final ProductTypeSyncOptions productTypeSyncOptionsWithNegativeBatchSize = ProductTypeSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(-100) + assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = singletonList(ChangeName.of("name")); + final List> filteredList = + productTypeSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ProductTypeDraft.class), mock(ProductType.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + private interface MockTriFunction + extends TriFunction< + List>, + ProductTypeDraft, + ProductType, + List>> {} + + @Test + void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); + + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) .build(); - assertThat(productTypeSyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(ProductTypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } + assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = emptyList(); + final List> filteredList = + productTypeSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ProductTypeDraft.class), mock(ProductType.class)); + + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { + final TriFunction< + List>, + ProductTypeDraft, + ProductType, + List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> Collections.emptyList(); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = singletonList(ChangeName.of("name")); + final List> filteredList = + productTypeSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ProductTypeDraft.class), mock(ProductType.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { + final Function draftFunction = + productTypeDraft -> + ProductTypeDraftBuilder.of(productTypeDraft) + .key(productTypeDraft.getKey() + "_filteredKey") + .build(); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNotNull(); - @Test - void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .build(); - assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNull(); - - final List> updateActions = singletonList(ChangeName.of("name")); - final List> filteredList = productTypeSyncOptions - .applyBeforeUpdateCallback(updateActions, mock(ProductTypeDraft.class), mock(ProductType.class)); - assertThat(filteredList).isSameAs(updateActions); - } - - @Test - void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { - final TriFunction>, - ProductTypeDraft, ProductType, List>> - beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of("name")); - final List> filteredList = - productTypeSyncOptions.applyBeforeUpdateCallback(updateActions, - mock(ProductTypeDraft.class), mock(ProductType.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - private interface MockTriFunction extends - TriFunction>, ProductTypeDraft, ProductType, List>> { - } - - @Test - void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); - - final ProductTypeSyncOptions productTypeSyncOptions = - ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = emptyList(); - final List> filteredList = - productTypeSyncOptions.applyBeforeUpdateCallback(updateActions, - mock(ProductTypeDraft.class), mock(ProductType.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - @Test - void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { - final TriFunction>, - ProductTypeDraft, ProductType, List>> - beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> Collections.emptyList(); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(productTypeSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of("name")); - final List> filteredList = - productTypeSyncOptions.applyBeforeUpdateCallback(updateActions, - mock(ProductTypeDraft.class), mock(ProductType.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { - final Function draftFunction = productTypeDraft -> - ProductTypeDraftBuilder.of(productTypeDraft).key(productTypeDraft.getKey() + "_filteredKey").build(); - - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNotNull(); + final ProductTypeDraft resourceDraft = mock(ProductTypeDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); - final ProductTypeDraft resourceDraft = mock(ProductTypeDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); + final Optional filteredDraft = + productTypeSyncOptions.applyBeforeCreateCallback(resourceDraft); + assertThat(filteredDraft) + .hasValueSatisfying( + productTypeDraft -> + assertThat(productTypeDraft.getKey()).isEqualTo("myKey_filteredKey")); + } - final Optional filteredDraft = - productTypeSyncOptions.applyBeforeCreateCallback(resourceDraft); + @Test + void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNull(); - assertThat(filteredDraft).hasValueSatisfying(productTypeDraft -> - assertThat(productTypeDraft.getKey()).isEqualTo("myKey_filteredKey")); + final ProductTypeDraft resourceDraft = mock(ProductTypeDraft.class); + final Optional filteredDraft = + productTypeSyncOptions.applyBeforeCreateCallback(resourceDraft); - } + assertThat(filteredDraft).containsSame(resourceDraft); + } - @Test - void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).build(); - assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNull(); + @Test + void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { + final Function draftFunction = productDraft -> null; + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNotNull(); - final ProductTypeDraft resourceDraft = mock(ProductTypeDraft.class); - final Optional filteredDraft = - productTypeSyncOptions.applyBeforeCreateCallback(resourceDraft); + final ProductTypeDraft resourceDraft = mock(ProductTypeDraft.class); + final Optional filteredDraft = + productTypeSyncOptions.applyBeforeCreateCallback(resourceDraft); - assertThat(filteredDraft).containsSame(resourceDraft); - } + assertThat(filteredDraft).isEmpty(); + } - @Test - void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { - final Function draftFunction = productDraft -> null; - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - assertThat(productTypeSyncOptions.getBeforeCreateCallback()).isNotNull(); - - final ProductTypeDraft resourceDraft = mock(ProductTypeDraft.class); - final Optional filteredDraft = - productTypeSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isEmpty(); - } - - @Test - void cacheSize_WithPositiveValue_ShouldSetCacheSize() { - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(10) - .build(); - assertThat(productTypeSyncOptions.getCacheSize()).isEqualTo(10); - } - - @Test - void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final ProductTypeSyncOptions productTypeSyncOptionsWithZeroCacheSize = - ProductTypeSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(0) - .build(); - - assertThat(productTypeSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - - final ProductTypeSyncOptions productTypeSyncOptionsWithNegativeCacheSize = ProductTypeSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(-100) - .build(); + @Test + void cacheSize_WithPositiveValue_ShouldSetCacheSize() { + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); + assertThat(productTypeSyncOptions.getCacheSize()).isEqualTo(10); + } + + @Test + void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final ProductTypeSyncOptions productTypeSyncOptionsWithZeroCacheSize = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); - assertThat(productTypeSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } + assertThat(productTypeSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); + final ProductTypeSyncOptions productTypeSyncOptionsWithNegativeCacheSize = + ProductTypeSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); + assertThat(productTypeSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/ProductTypeSyncTest.java b/src/test/java/com/commercetools/sync/producttypes/ProductTypeSyncTest.java index ff0faa865f..32e19c9e3d 100644 --- a/src/test/java/com/commercetools/sync/producttypes/ProductTypeSyncTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/ProductTypeSyncTest.java @@ -1,5 +1,26 @@ package com.commercetools.sync.producttypes; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static io.sphere.sdk.producttypes.ProductType.referenceOfId; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Lists.list; +import static org.assertj.core.util.Sets.newLinkedHashSet; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.producttypes.helpers.ProductTypeSyncStatistics; @@ -21,8 +42,6 @@ import io.sphere.sdk.producttypes.commands.updateactions.ChangeName; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; import io.sphere.sdk.queries.PagedQueryResult; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -30,615 +49,598 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static io.sphere.sdk.producttypes.ProductType.referenceOfId; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.CompletableFuture.supplyAsync; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.util.Lists.list; -import static org.assertj.core.util.Sets.newLinkedHashSet; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductTypeSyncTest { - @Test - void sync_WithEmptyAttributeDefinitions_ShouldSyncCorrectly() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - "foo", - "name", - "desc", - emptyList() - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + @Test + void sync_WithEmptyAttributeDefinitions_ShouldSyncCorrectly() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts("foo", "name", "desc", emptyList()); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - - final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); - - final ProductType existingProductType = mock(ProductType.class); - when(existingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); - - when(mockProductTypeService.fetchMatchingProductTypesByKeys(singleton(newProductTypeDraft.getKey()))) - .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); - when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) - .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); - when(mockProductTypeService.updateProductType(any(), any())) - .thenReturn(CompletableFuture.completedFuture(existingProductType)); - when(mockProductTypeService.cacheKeysToIds(anySet())) - .thenReturn(CompletableFuture.completedFuture(emptyMap())); - - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions, mockProductTypeService); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); - } - - @Test - void sync_WithNullAttributeDefinitions_ShouldSyncCorrectly() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - "foo", - "name", - "desc", - null - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final List actions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .beforeUpdateCallback((generatedActions, draft, productType) -> { - actions.addAll(generatedActions); - return generatedActions; - }) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); + + final ProductType existingProductType = mock(ProductType.class); + when(existingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); + + when(mockProductTypeService.fetchMatchingProductTypesByKeys( + singleton(newProductTypeDraft.getKey()))) + .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); + when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) + .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); + when(mockProductTypeService.updateProductType(any(), any())) + .thenReturn(CompletableFuture.completedFuture(existingProductType)); + when(mockProductTypeService.cacheKeysToIds(anySet())) + .thenReturn(CompletableFuture.completedFuture(emptyMap())); + + final ProductTypeSync productTypeSync = + new ProductTypeSync(syncOptions, mockProductTypeService); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + } + + @Test + void sync_WithNullAttributeDefinitions_ShouldSyncCorrectly() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts("foo", "name", "desc", null); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final List actions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .beforeUpdateCallback( + (generatedActions, draft, productType) -> { + actions.addAll(generatedActions); + return generatedActions; + }) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - - final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); - - final ProductType existingProductType = mock(ProductType.class); - when(existingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); - - when(mockProductTypeService.fetchMatchingProductTypesByKeys(singleton(newProductTypeDraft.getKey()))) - .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); - when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) - .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); - when(mockProductTypeService.updateProductType(any(), any())) - .thenReturn(CompletableFuture.completedFuture(existingProductType)); - when(mockProductTypeService.cacheKeysToIds(anySet())) - .thenReturn(CompletableFuture.completedFuture(emptyMap())); - - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions, mockProductTypeService); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - assertThat(actions).containsExactly( + final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); + + final ProductType existingProductType = mock(ProductType.class); + when(existingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); + + when(mockProductTypeService.fetchMatchingProductTypesByKeys( + singleton(newProductTypeDraft.getKey()))) + .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); + when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) + .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); + when(mockProductTypeService.updateProductType(any(), any())) + .thenReturn(CompletableFuture.completedFuture(existingProductType)); + when(mockProductTypeService.cacheKeysToIds(anySet())) + .thenReturn(CompletableFuture.completedFuture(emptyMap())); + + final ProductTypeSync productTypeSync = + new ProductTypeSync(syncOptions, mockProductTypeService); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + assertThat(actions) + .containsExactly( ChangeName.of(newProductTypeDraft.getName()), ChangeDescription.of(newProductTypeDraft.getDescription())); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); - } - - @Test - void sync_WithNullInAttributeDefinitions_ShouldSyncCorrectly() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - "foo", - "name", - "desc", - singletonList(null) - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + } + + @Test + void sync_WithNullInAttributeDefinitions_ShouldSyncCorrectly() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts("foo", "name", "desc", singletonList(null)); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - - final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); - - final ProductType existingProductType = mock(ProductType.class); - when(existingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); - - when(mockProductTypeService.fetchMatchingProductTypesByKeys(singleton(newProductTypeDraft.getKey()))) - .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); - when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) - .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); - when(mockProductTypeService.updateProductType(any(), any())) - .thenReturn(CompletableFuture.completedFuture(existingProductType)); - when(mockProductTypeService.cacheKeysToIds(anySet())) - .thenReturn(CompletableFuture.completedFuture(emptyMap())); - - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions, mockProductTypeService); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages).isEmpty(); - assertThat(exceptions).isEmpty(); - - assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); - } - - @Test - void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - "foo", - "name", - "desc", - emptyList() - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); + + final ProductType existingProductType = mock(ProductType.class); + when(existingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); + + when(mockProductTypeService.fetchMatchingProductTypesByKeys( + singleton(newProductTypeDraft.getKey()))) + .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); + when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) + .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); + when(mockProductTypeService.updateProductType(any(), any())) + .thenReturn(CompletableFuture.completedFuture(existingProductType)); + when(mockProductTypeService.cacheKeysToIds(anySet())) + .thenReturn(CompletableFuture.completedFuture(emptyMap())); + + final ProductTypeSync productTypeSync = + new ProductTypeSync(syncOptions, mockProductTypeService); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages).isEmpty(); + assertThat(exceptions).isEmpty(); + + assertThat(productTypeSyncStatistics).hasValues(1, 0, 1, 0, 0); + } + + @Test + void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts("foo", "name", "desc", emptyList()); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - - final ProductTypeService mockProductTypeService = mock(ProductTypeService.class); - - when(mockProductTypeService.fetchMatchingProductTypesByKeys(singleton(newProductTypeDraft.getKey()))) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - when(mockProductTypeService.cacheKeysToIds(anySet())) - .thenReturn(CompletableFuture.completedFuture(emptyMap())); - - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions, mockProductTypeService); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo("Failed to fetch existing product types with keys: '[foo]'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(SyncException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); - assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(SphereException.class); + final ProductTypeService mockProductTypeService = mock(ProductTypeService.class); + + when(mockProductTypeService.fetchMatchingProductTypesByKeys( + singleton(newProductTypeDraft.getKey()))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + when(mockProductTypeService.cacheKeysToIds(anySet())) + .thenReturn(CompletableFuture.completedFuture(emptyMap())); + + final ProductTypeSync productTypeSync = + new ProductTypeSync(syncOptions, mockProductTypeService); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .isEqualTo("Failed to fetch existing product types with keys: '[foo]'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(SyncException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); + assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(SphereException.class); }); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } - - @Test - void sync_WithErrorsOnSyncing_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - "foo", - "name", - "desc", - emptyList() - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } + + @Test + void sync_WithErrorsOnSyncing_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts("foo", "name", "desc", emptyList()); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - - final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); - - final ProductType existingProductType = mock(ProductType.class); - when(existingProductType.getKey()).thenReturn(null); - - when(mockProductTypeService.fetchMatchingProductTypesByKeys(singleton(newProductTypeDraft.getKey()))) - .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); - when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) - .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); - - - when(mockProductTypeService.cacheKeysToIds(anySet())) - .thenReturn(CompletableFuture.completedFuture(emptyMap())); - - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions, mockProductTypeService); - - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to process the productTypeDraft with key:'foo'." - + " Reason: java.lang.NullPointerException") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(SyncException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); - assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(NullPointerException.class); + final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); + + final ProductType existingProductType = mock(ProductType.class); + when(existingProductType.getKey()).thenReturn(null); + + when(mockProductTypeService.fetchMatchingProductTypesByKeys( + singleton(newProductTypeDraft.getKey()))) + .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); + when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) + .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); + + when(mockProductTypeService.cacheKeysToIds(anySet())) + .thenReturn(CompletableFuture.completedFuture(emptyMap())); + + final ProductTypeSync productTypeSync = + new ProductTypeSync(syncOptions, mockProductTypeService); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .contains( + "Failed to process the productTypeDraft with key:'foo'." + + " Reason: java.lang.NullPointerException")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(SyncException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); + assertThat(throwable.getCause()) + .hasCauseExactlyInstanceOf(NullPointerException.class); }); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } - - @Test - void sync_WithErrorCachingKeysButNoKeysToCache_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - "foo", - "name", - "desc", - emptyList() - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final SphereClient sphereClient = mock(SphereClient.class); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(sphereClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) - .build(); - - - final ProductTypeService mockProductTypeService = new ProductTypeServiceImpl(syncOptions); + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } + @Test + void + sync_WithErrorCachingKeysButNoKeysToCache_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation - when(sphereClient.execute(any(ProductTypeQuery.class))) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts("foo", "name", "desc", emptyList()); - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions, mockProductTypeService); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo("Failed to fetch existing product types with keys: '[foo]'.") - ); + final SphereClient sphereClient = mock(SphereClient.class); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(sphereClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .build(); - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(SyncException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); - assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(SphereException.class); + final ProductTypeService mockProductTypeService = new ProductTypeServiceImpl(syncOptions); + + when(sphereClient.execute(any(ProductTypeQuery.class))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final ProductTypeSync productTypeSync = + new ProductTypeSync(syncOptions, mockProductTypeService); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .isEqualTo("Failed to fetch existing product types with keys: '[foo]'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(SyncException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); + assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(SphereException.class); }); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } - - - @Test - void sync_WithInvalidAttributeDefinitions_ShouldThrowError() { - // preparation - String nestedAttributeTypeId = "attributeId"; - NestedAttributeType nestedAttributeType = spy(NestedAttributeType.of(referenceOfId(nestedAttributeTypeId))); - Reference reference = spy(Reference.class); - when(reference.getId()) - .thenReturn(nestedAttributeTypeId) - .thenReturn(null); - - when(nestedAttributeType.getTypeReference()).thenReturn(reference); - final AttributeDefinitionDraft nestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(nestedAttributeType, "validNested", ofEnglish("koko"), true) + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } + + @Test + void sync_WithInvalidAttributeDefinitions_ShouldThrowError() { + // preparation + String nestedAttributeTypeId = "attributeId"; + NestedAttributeType nestedAttributeType = + spy(NestedAttributeType.of(referenceOfId(nestedAttributeTypeId))); + Reference reference = spy(Reference.class); + when(reference.getId()).thenReturn(nestedAttributeTypeId).thenReturn(null); + + when(nestedAttributeType.getTypeReference()).thenReturn(reference); + final AttributeDefinitionDraft nestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + nestedAttributeType, "validNested", ofEnglish("koko"), true) .build(); - - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - "foo", - "name", - "desc", - singletonList(nestedTypeAttrDefDraft) - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( + "foo", "name", "desc", singletonList(nestedTypeAttrDefDraft)); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - - final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); - - final ProductType existingProductType = mock(ProductType.class); - when(existingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); - - when(mockProductTypeService.fetchMatchingProductTypesByKeys(singleton(newProductTypeDraft.getKey()))) - .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); - when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) - .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); - when(mockProductTypeService.updateProductType(any(), any())) - .thenReturn(CompletableFuture.completedFuture(existingProductType)); - when(mockProductTypeService.cacheKeysToIds(anySet())) - .thenReturn(CompletableFuture.completedFuture(emptyMap())); - - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions, mockProductTypeService); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages.get(0)) - .contains("This exception is unexpectedly thrown since the draft batch has been" - + "already validated for blank keys" ); - assertThat(errorMessages.get(1)) - .contains("Failed to process the productTypeDraft with key:'foo'" ); - assertThat(exceptions.size()).isEqualTo(2); - - - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 2, 0); - } - - @Test - void sync_WithErrorUpdatingProductType_ShouldCallErrorCallback() { - String draftKey = "key2"; - - // preparation - final ProductTypeDraft newProductTypeDraft2 = ProductTypeDraft.ofAttributeDefinitionDrafts( - draftKey, - "name", - "desc", - emptyList() - ); - NestedAttributeType nestedTypeAttrDefDraft1 = NestedAttributeType - .of(referenceOfId(draftKey)); - final AttributeDefinitionDraft nestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(nestedTypeAttrDefDraft1, "validNested", ofEnglish("koko"), true) + final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); + + final ProductType existingProductType = mock(ProductType.class); + when(existingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); + + when(mockProductTypeService.fetchMatchingProductTypesByKeys( + singleton(newProductTypeDraft.getKey()))) + .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); + when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) + .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); + when(mockProductTypeService.updateProductType(any(), any())) + .thenReturn(CompletableFuture.completedFuture(existingProductType)); + when(mockProductTypeService.cacheKeysToIds(anySet())) + .thenReturn(CompletableFuture.completedFuture(emptyMap())); + + final ProductTypeSync productTypeSync = + new ProductTypeSync(syncOptions, mockProductTypeService); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages.get(0)) + .contains( + "This exception is unexpectedly thrown since the draft batch has been" + + "already validated for blank keys"); + assertThat(errorMessages.get(1)) + .contains("Failed to process the productTypeDraft with key:'foo'"); + assertThat(exceptions.size()).isEqualTo(2); + + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 2, 0); + } + + @Test + void sync_WithErrorUpdatingProductType_ShouldCallErrorCallback() { + String draftKey = "key2"; + + // preparation + final ProductTypeDraft newProductTypeDraft2 = + ProductTypeDraft.ofAttributeDefinitionDrafts(draftKey, "name", "desc", emptyList()); + NestedAttributeType nestedTypeAttrDefDraft1 = NestedAttributeType.of(referenceOfId(draftKey)); + final AttributeDefinitionDraft nestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + nestedTypeAttrDefDraft1, "validNested", ofEnglish("koko"), true) .build(); - final ProductTypeDraft newProductTypeDraft1 = ProductTypeDraft.ofAttributeDefinitionDrafts( - "key1", - "name", - "desc", - singletonList(nestedTypeAttrDefDraft) - ); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + final ProductTypeDraft newProductTypeDraft1 = + ProductTypeDraft.ofAttributeDefinitionDrafts( + "key1", "name", "desc", singletonList(nestedTypeAttrDefDraft)); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); - final ProductType existingProductType = mock(ProductType.class); - when(existingProductType.getKey()).thenReturn(newProductTypeDraft1.getKey()); - when(mockProductTypeService.fetchMatchingProductTypesByKeys(newLinkedHashSet(newProductTypeDraft2.getKey(), - newProductTypeDraft1.getKey()))).thenReturn(CompletableFuture.completedFuture(Collections.emptySet()), + final ProductTypeService mockProductTypeService = mock(ProductTypeServiceImpl.class); + final ProductType existingProductType = mock(ProductType.class); + when(existingProductType.getKey()).thenReturn(newProductTypeDraft1.getKey()); + when(mockProductTypeService.fetchMatchingProductTypesByKeys( + newLinkedHashSet(newProductTypeDraft2.getKey(), newProductTypeDraft1.getKey()))) + .thenReturn( + CompletableFuture.completedFuture(Collections.emptySet()), CompletableFuture.completedFuture(singleton(existingProductType))); - when(mockProductTypeService.fetchMatchingProductTypesByKeys(newLinkedHashSet(newProductTypeDraft1.getKey()))) - .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); - when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) - .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); - when(mockProductTypeService.updateProductType(any(), any())) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - when(mockProductTypeService.cacheKeysToIds(anySet())) - .thenReturn(CompletableFuture.completedFuture(emptyMap())); - when(mockProductTypeService.createProductType(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of(existingProductType))); - when(mockProductTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("key1"))); - - // test - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions, mockProductTypeService); - productTypeSync.sync(list(newProductTypeDraft2, newProductTypeDraft1)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages.get(0)) - .contains("Failed to update product type with key: 'key1'. Reason: io.sphere.sdk.models.SphereException:"); - - } - - @Test - void sync_WithErrorCachingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final AttributeDefinitionDraft nestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(referenceOfId("x")), "validNested", ofEnglish("koko"), true) + when(mockProductTypeService.fetchMatchingProductTypesByKeys( + newLinkedHashSet(newProductTypeDraft1.getKey()))) + .thenReturn(CompletableFuture.completedFuture(singleton(existingProductType))); + when(mockProductTypeService.fetchMatchingProductTypesByKeys(emptySet())) + .thenReturn(CompletableFuture.completedFuture(Collections.emptySet())); + when(mockProductTypeService.updateProductType(any(), any())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + when(mockProductTypeService.cacheKeysToIds(anySet())) + .thenReturn(CompletableFuture.completedFuture(emptyMap())); + when(mockProductTypeService.createProductType(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of(existingProductType))); + when(mockProductTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("key1"))); + + // test + final ProductTypeSync productTypeSync = + new ProductTypeSync(syncOptions, mockProductTypeService); + productTypeSync + .sync(list(newProductTypeDraft2, newProductTypeDraft1)) + .toCompletableFuture() + .join(); + + // assertions + assertThat(errorMessages.get(0)) + .contains( + "Failed to update product type with key: 'key1'. Reason: io.sphere.sdk.models.SphereException:"); + } + + @Test + void sync_WithErrorCachingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final AttributeDefinitionDraft nestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(referenceOfId("x")), "validNested", ofEnglish("koko"), true) .build(); - - final ProductTypeDraft newProductTypeDraft = ProductTypeDraft.ofAttributeDefinitionDrafts( - "foo", - "name", - "desc", - singletonList(nestedTypeAttrDefDraft) - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final SphereClient sphereClient = mock(SphereClient.class); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(sphereClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraft.ofAttributeDefinitionDrafts( + "foo", "name", "desc", singletonList(nestedTypeAttrDefDraft)); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final SphereClient sphereClient = mock(SphereClient.class); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(sphereClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) .build(); - - final ProductTypeService mockProductTypeService = new ProductTypeServiceImpl(syncOptions); - - - when(sphereClient.execute(any(ResourceKeyIdGraphQlRequest.class))) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - - final ProductTypeSync productTypeSync = new ProductTypeSync(syncOptions, mockProductTypeService); - - // test - final ProductTypeSyncStatistics productTypeSyncStatistics = productTypeSync - .sync(singletonList(newProductTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo("Failed to build a cache of keys to ids.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(SyncException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); - assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(SphereException.class); + final ProductTypeService mockProductTypeService = new ProductTypeServiceImpl(syncOptions); + + when(sphereClient.execute(any(ResourceKeyIdGraphQlRequest.class))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final ProductTypeSync productTypeSync = + new ProductTypeSync(syncOptions, mockProductTypeService); + + // test + final ProductTypeSyncStatistics productTypeSyncStatistics = + productTypeSync.sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).isEqualTo("Failed to build a cache of keys to ids.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(SyncException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(CompletionException.class); + assertThat(throwable.getCause()).hasCauseExactlyInstanceOf(SphereException.class); }); - assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraftBuilder - .of("newProductType", "productType", "a cool type", emptyList()) - .build(); - - final SphereClient sphereClient = mock(SphereClient.class); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(sphereClient) - .build(); + assertThat(productTypeSyncStatistics).hasValues(1, 0, 0, 1, 0); + } - final ProductTypeService productTypeService = new ProductTypeServiceImpl(productTypeSyncOptions); - when(sphereClient.execute(any(ProductTypeQuery.class))).thenReturn(completedFuture(PagedQueryResult.empty())); - final ProductType createdProductType = mock(ProductType.class); - when(createdProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); - when(createdProductType.getId()).thenReturn(UUID.randomUUID().toString()); - when(sphereClient.execute(any(ProductTypeCreateCommand.class))).thenReturn(completedFuture(createdProductType)); - - final ProductTypeSyncOptions spyProductTypeSyncOptions = spy(productTypeSyncOptions); - - // test - new ProductTypeSync(spyProductTypeSyncOptions, productTypeService) - .sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - - // assertion - verify(spyProductTypeSyncOptions).applyBeforeCreateCallback(newProductTypeDraft); - verify(spyProductTypeSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - @Test - void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { - // preparation - final ProductTypeDraft newProductTypeDraft = ProductTypeDraftBuilder - .of("newProductType", "productType", "a cool type", emptyList()) + @Test + void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraftBuilder.of("newProductType", "productType", "a cool type", emptyList()) .build(); - final SphereClient sphereClient = mock(SphereClient.class); - final ProductTypeSyncOptions productTypeSyncOptions = ProductTypeSyncOptionsBuilder - .of(sphereClient) + final SphereClient sphereClient = mock(SphereClient.class); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(sphereClient).build(); + + final ProductTypeService productTypeService = + new ProductTypeServiceImpl(productTypeSyncOptions); + when(sphereClient.execute(any(ProductTypeQuery.class))) + .thenReturn(completedFuture(PagedQueryResult.empty())); + final ProductType createdProductType = mock(ProductType.class); + when(createdProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); + when(createdProductType.getId()).thenReturn(UUID.randomUUID().toString()); + when(sphereClient.execute(any(ProductTypeCreateCommand.class))) + .thenReturn(completedFuture(createdProductType)); + + final ProductTypeSyncOptions spyProductTypeSyncOptions = spy(productTypeSyncOptions); + + // test + new ProductTypeSync(spyProductTypeSyncOptions, productTypeService) + .sync(singletonList(newProductTypeDraft)) + .toCompletableFuture() + .join(); + + // assertion + verify(spyProductTypeSyncOptions).applyBeforeCreateCallback(newProductTypeDraft); + verify(spyProductTypeSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { + // preparation + final ProductTypeDraft newProductTypeDraft = + ProductTypeDraftBuilder.of("newProductType", "productType", "a cool type", emptyList()) .build(); - final ProductType mockedExistingProductType = mock(ProductType.class); - when(mockedExistingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); - when(mockedExistingProductType.getId()).thenReturn(UUID.randomUUID().toString()); - - final ProductTypeService productTypeService = new ProductTypeServiceImpl(productTypeSyncOptions); - final PagedQueryResult productTypePagedQueryResult = spy(PagedQueryResult.empty()); - when(productTypePagedQueryResult.getResults()).thenReturn(singletonList(mockedExistingProductType)); - when(sphereClient.execute(any(ProductTypeQuery.class))) - .thenReturn(completedFuture(productTypePagedQueryResult)); - when(sphereClient.execute(any(ProductTypeUpdateCommand.class))) - .thenReturn(completedFuture(mockedExistingProductType)); - - final ProductTypeSyncOptions spyProductTypeSyncOptions = spy(productTypeSyncOptions); - - // test - new ProductTypeSync(spyProductTypeSyncOptions, productTypeService) - .sync(singletonList(newProductTypeDraft)).toCompletableFuture().join(); - - // assertion - verify(spyProductTypeSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spyProductTypeSyncOptions, never()).applyBeforeCreateCallback(newProductTypeDraft); - } - + final SphereClient sphereClient = mock(SphereClient.class); + final ProductTypeSyncOptions productTypeSyncOptions = + ProductTypeSyncOptionsBuilder.of(sphereClient).build(); + + final ProductType mockedExistingProductType = mock(ProductType.class); + when(mockedExistingProductType.getKey()).thenReturn(newProductTypeDraft.getKey()); + when(mockedExistingProductType.getId()).thenReturn(UUID.randomUUID().toString()); + + final ProductTypeService productTypeService = + new ProductTypeServiceImpl(productTypeSyncOptions); + final PagedQueryResult productTypePagedQueryResult = spy(PagedQueryResult.empty()); + when(productTypePagedQueryResult.getResults()) + .thenReturn(singletonList(mockedExistingProductType)); + when(sphereClient.execute(any(ProductTypeQuery.class))) + .thenReturn(completedFuture(productTypePagedQueryResult)); + when(sphereClient.execute(any(ProductTypeUpdateCommand.class))) + .thenReturn(completedFuture(mockedExistingProductType)); + + final ProductTypeSyncOptions spyProductTypeSyncOptions = spy(productTypeSyncOptions); + + // test + new ProductTypeSync(spyProductTypeSyncOptions, productTypeService) + .sync(singletonList(newProductTypeDraft)) + .toCompletableFuture() + .join(); + + // assertion + verify(spyProductTypeSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spyProductTypeSyncOptions, never()).applyBeforeCreateCallback(newProductTypeDraft); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/helpers/AttributeDefinitionReferenceResolverTest.java b/src/test/java/com/commercetools/sync/producttypes/helpers/AttributeDefinitionReferenceResolverTest.java index 80b118ac8b..7ab7d18029 100644 --- a/src/test/java/com/commercetools/sync/producttypes/helpers/AttributeDefinitionReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/helpers/AttributeDefinitionReferenceResolverTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.producttypes.helpers; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.producttypes.ProductTypeSyncOptions; import com.commercetools.sync.producttypes.ProductTypeSyncOptionsBuilder; @@ -11,204 +18,206 @@ import io.sphere.sdk.products.attributes.SetAttributeType; import io.sphere.sdk.products.attributes.StringAttributeType; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.Test; - import java.util.Optional; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class AttributeDefinitionReferenceResolverTest { - @Test - void resolveReferences_WithNoNestedTypeReferences_ShouldNotResolveReferences() { - // preparation - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(StringAttributeType.of(), "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = - new AttributeDefinitionReferenceResolver(syncOptions, mock(ProductTypeService.class)); - - // test and assertion - assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) - .isCompletedWithValue(attributeDefinitionDraft); - } - - @Test - void resolveReferences_WithOneNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { - // preparation - final NestedAttributeType nestedAttributeType = NestedAttributeType.of(ProductType.reference("x")); - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - - final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = - new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); - - final NestedAttributeType expectedResolvedNestedAttributeType = - NestedAttributeType.of(ProductType.referenceOfId("foo")); - final AttributeDefinitionDraft expectedResolvedAttrDef = - AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) - .attributeType(expectedResolvedNestedAttributeType) - .build(); - - // test and assertion - assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) - .isCompletedWithValue(expectedResolvedAttrDef); - } - - @Test - void resolveReferences_WithOneNestedTypeWithNonExistingProductTypeReference_ShouldNotResolveReferences() { - // preparation - final NestedAttributeType nestedAttributeType = NestedAttributeType.of(ProductType.reference("x")); - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = - new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); - - // test and assertion - assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) - .isCompletedWithValue(attributeDefinitionDraft); - } - - @Test - void resolveReferences_WithOneNestedTypeWithInvalidProductTypeReference_ShouldNotResolveReferences() { - // preparation - final NestedAttributeType nestedAttributeType = NestedAttributeType.of(ProductType.reference("")); - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = - new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); - - // test and assertion - final ReferenceResolutionException expectedRootCause = - new ReferenceResolutionException(BLANK_ID_VALUE_ON_REFERENCE); - assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) - .hasFailedWithThrowableThat() - .hasMessageContaining("Failed to resolve references on attribute definition with name 'foo'") - .hasCause(new ReferenceResolutionException( + @Test + void resolveReferences_WithNoNestedTypeReferences_ShouldNotResolveReferences() { + // preparation + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(StringAttributeType.of(), "foo", ofEnglish("foo"), true) + .build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = + new AttributeDefinitionReferenceResolver(syncOptions, mock(ProductTypeService.class)); + + // test and assertion + assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) + .isCompletedWithValue(attributeDefinitionDraft); + } + + @Test + void + resolveReferences_WithOneNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { + // preparation + final NestedAttributeType nestedAttributeType = + NestedAttributeType.of(ProductType.reference("x")); + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) + .build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + + final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = + new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); + + final NestedAttributeType expectedResolvedNestedAttributeType = + NestedAttributeType.of(ProductType.referenceOfId("foo")); + final AttributeDefinitionDraft expectedResolvedAttrDef = + AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) + .attributeType(expectedResolvedNestedAttributeType) + .build(); + + // test and assertion + assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) + .isCompletedWithValue(expectedResolvedAttrDef); + } + + @Test + void + resolveReferences_WithOneNestedTypeWithNonExistingProductTypeReference_ShouldNotResolveReferences() { + // preparation + final NestedAttributeType nestedAttributeType = + NestedAttributeType.of(ProductType.reference("x")); + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) + .build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = + new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); + + // test and assertion + assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) + .isCompletedWithValue(attributeDefinitionDraft); + } + + @Test + void + resolveReferences_WithOneNestedTypeWithInvalidProductTypeReference_ShouldNotResolveReferences() { + // preparation + final NestedAttributeType nestedAttributeType = + NestedAttributeType.of(ProductType.reference("")); + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) + .build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = + new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); + + // test and assertion + final ReferenceResolutionException expectedRootCause = + new ReferenceResolutionException(BLANK_ID_VALUE_ON_REFERENCE); + assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) + .hasFailedWithThrowableThat() + .hasMessageContaining( + "Failed to resolve references on attribute definition with name 'foo'") + .hasCause( + new ReferenceResolutionException( "Failed to resolve NestedType productType reference.", expectedRootCause)); - } - - @Test - void resolveReferences_WithSetOfNonNestedType_ShouldResolveReferences() { - // preparation - final SetAttributeType setAttributeType = SetAttributeType.of(StringAttributeType.of()); - - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - - final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = - new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); - - - // test and assertion - assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) - .isCompletedWithValue(attributeDefinitionDraft); - } - - @Test - void resolveReferences_WithSetOfNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { - // preparation - final NestedAttributeType nestedAttributeType = NestedAttributeType.of(ProductType.reference("x")); - final SetAttributeType setAttributeType = SetAttributeType.of(nestedAttributeType); - - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - - final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = - new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); - - final NestedAttributeType expectedResolvedNestedAttributeType = - NestedAttributeType.of(ProductType.referenceOfId("foo")); - final SetAttributeType expectedResolvedSetAttributeType = - SetAttributeType.of(expectedResolvedNestedAttributeType); - final AttributeDefinitionDraft expectedResolvedAttrDef = - AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) - .attributeType(expectedResolvedSetAttributeType) - .build(); - - // test and assertion - assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) - .isCompletedWithValue(expectedResolvedAttrDef); - } - - @Test - void resolveReferences_WithSetOfNestedTypeWithInvalidProductTypeReference_ShouldNotResolveReferences() { - // preparation - final NestedAttributeType nestedAttributeType = NestedAttributeType.of(ProductType.reference("")); - final SetAttributeType setAttributeType = SetAttributeType.of(nestedAttributeType); - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = - new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); - - // test and assertion - final ReferenceResolutionException expectedRootCause = - new ReferenceResolutionException(BLANK_ID_VALUE_ON_REFERENCE); - assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) - .hasFailedWithThrowableThat() - .hasMessageContaining("Failed to resolve references on attribute definition with name 'foo'") - .hasCause(new ReferenceResolutionException( + } + + @Test + void resolveReferences_WithSetOfNonNestedType_ShouldResolveReferences() { + // preparation + final SetAttributeType setAttributeType = SetAttributeType.of(StringAttributeType.of()); + + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true).build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + + final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = + new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); + + // test and assertion + assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) + .isCompletedWithValue(attributeDefinitionDraft); + } + + @Test + void + resolveReferences_WithSetOfNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { + // preparation + final NestedAttributeType nestedAttributeType = + NestedAttributeType.of(ProductType.reference("x")); + final SetAttributeType setAttributeType = SetAttributeType.of(nestedAttributeType); + + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true).build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + + final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = + new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); + + final NestedAttributeType expectedResolvedNestedAttributeType = + NestedAttributeType.of(ProductType.referenceOfId("foo")); + final SetAttributeType expectedResolvedSetAttributeType = + SetAttributeType.of(expectedResolvedNestedAttributeType); + final AttributeDefinitionDraft expectedResolvedAttrDef = + AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) + .attributeType(expectedResolvedSetAttributeType) + .build(); + + // test and assertion + assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) + .isCompletedWithValue(expectedResolvedAttrDef); + } + + @Test + void + resolveReferences_WithSetOfNestedTypeWithInvalidProductTypeReference_ShouldNotResolveReferences() { + // preparation + final NestedAttributeType nestedAttributeType = + NestedAttributeType.of(ProductType.reference("")); + final SetAttributeType setAttributeType = SetAttributeType.of(nestedAttributeType); + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true).build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final AttributeDefinitionReferenceResolver attributeDefinitionReferenceResolver = + new AttributeDefinitionReferenceResolver(syncOptions, productTypeService); + + // test and assertion + final ReferenceResolutionException expectedRootCause = + new ReferenceResolutionException(BLANK_ID_VALUE_ON_REFERENCE); + assertThat(attributeDefinitionReferenceResolver.resolveReferences(attributeDefinitionDraft)) + .hasFailedWithThrowableThat() + .hasMessageContaining( + "Failed to resolve references on attribute definition with name 'foo'") + .hasCause( + new ReferenceResolutionException( "Failed to resolve NestedType productType reference.", expectedRootCause)); - } + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeBatchValidatorTest.java b/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeBatchValidatorTest.java index ff7d3d4852..fbdb437ba2 100644 --- a/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeBatchValidatorTest.java @@ -1,5 +1,19 @@ package com.commercetools.sync.producttypes.helpers; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; +import static com.commercetools.sync.producttypes.helpers.ProductTypeBatchValidator.PRODUCT_TYPE_DRAFT_IS_NULL; +import static com.commercetools.sync.producttypes.helpers.ProductTypeBatchValidator.PRODUCT_TYPE_DRAFT_KEY_NOT_SET; +import static com.commercetools.sync.producttypes.helpers.ProductTypeBatchValidator.PRODUCT_TYPE_HAS_INVALID_REFERENCES; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.InvalidReferenceException; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.producttypes.ProductTypeSyncOptions; @@ -13,322 +27,373 @@ import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.ProductTypeDraftBuilder; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.function.Predicate; - -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; -import static com.commercetools.sync.producttypes.helpers.ProductTypeBatchValidator.PRODUCT_TYPE_DRAFT_IS_NULL; -import static com.commercetools.sync.producttypes.helpers.ProductTypeBatchValidator.PRODUCT_TYPE_DRAFT_KEY_NOT_SET; -import static com.commercetools.sync.producttypes.helpers.ProductTypeBatchValidator.PRODUCT_TYPE_HAS_INVALID_REFERENCES; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductTypeBatchValidatorTest { - private List errorCallBackMessages; - private List errorCallBackExceptions; - private ProductTypeSyncOptions syncOptions; - private ProductTypeSyncStatistics syncStatistics; - - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - errorCallBackExceptions = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = ProductTypeSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - errorCallBackExceptions.add(exception); - }) + private List errorCallBackMessages; + private List errorCallBackExceptions; + private ProductTypeSyncOptions syncOptions; + private ProductTypeSyncStatistics syncStatistics; + + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + errorCallBackExceptions = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); + syncOptions = + ProductTypeSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + errorCallBackExceptions.add(exception); + }) .build(); - syncStatistics = new ProductTypeSyncStatistics(); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(emptyList()); - - assertThat(validDrafts).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullProductTypeDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.singletonList(null)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(PRODUCT_TYPE_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithProductTypeDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final ProductTypeDraft productTypeDraft = mock(ProductTypeDraft.class); - final Set validDrafts = getValidDrafts(Collections.singletonList(productTypeDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(PRODUCT_TYPE_DRAFT_KEY_NOT_SET, productTypeDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithProductTypeDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { - final ProductTypeDraft productTypeDraft = mock(ProductTypeDraft.class); - when(productTypeDraft.getKey()).thenReturn(EMPTY); - final Set validDrafts = getValidDrafts(Collections.singletonList(productTypeDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(PRODUCT_TYPE_DRAFT_KEY_NOT_SET, productTypeDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithADraftWithAValidNestedReference_ShouldNotResultInAnError() { - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "foo", ofEnglish("koko"), true) + syncStatistics = new ProductTypeSyncStatistics(); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(emptyList()); + + assertThat(validDrafts).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullProductTypeDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.singletonList(null)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(PRODUCT_TYPE_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithProductTypeDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final ProductTypeDraft productTypeDraft = mock(ProductTypeDraft.class); + final Set validDrafts = + getValidDrafts(Collections.singletonList(productTypeDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(PRODUCT_TYPE_DRAFT_KEY_NOT_SET, productTypeDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithProductTypeDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { + final ProductTypeDraft productTypeDraft = mock(ProductTypeDraft.class); + when(productTypeDraft.getKey()).thenReturn(EMPTY); + final Set validDrafts = + getValidDrafts(Collections.singletonList(productTypeDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(PRODUCT_TYPE_DRAFT_KEY_NOT_SET, productTypeDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithADraftWithAValidNestedReference_ShouldNotResultInAnError() { + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(StringAttributeType.of(), "foo", ofEnglish("koko"), true) .build(); - final AttributeDefinitionDraft nestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(ProductType.referenceOfId("x")), "nested", ofEnglish("koko"), true) - .build(); - - final List attributes = new ArrayList<>(); - attributes.add(attributeDefinitionDraft); - attributes.add(nestedTypeAttrDefDraft); - attributes.add(null); - - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("mainProductType", "foo", "foo", attributes) - .build(); - - final ProductTypeBatchValidator batchValidator = new ProductTypeBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = - batchValidator.validateAndCollectReferencedKeys(singletonList(productTypeDraft)); - - assertThat(pair.getLeft()).containsExactly(productTypeDraft); - assertThat(pair.getRight()).containsExactlyInAnyOrder("x"); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithADraftWithAnInvalidNestedReference_ShouldResultInAnError() { - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "foo", ofEnglish("koko"), true) + final AttributeDefinitionDraft nestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("x")), + "nested", + ofEnglish("koko"), + true) .build(); - final AttributeDefinitionDraft invalidNestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(ProductType.referenceOfId("")), "invalidNested", ofEnglish("koko"), true) + final List attributes = new ArrayList<>(); + attributes.add(attributeDefinitionDraft); + attributes.add(nestedTypeAttrDefDraft); + attributes.add(null); + + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("mainProductType", "foo", "foo", attributes).build(); + + final ProductTypeBatchValidator batchValidator = + new ProductTypeBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + batchValidator.validateAndCollectReferencedKeys(singletonList(productTypeDraft)); + + assertThat(pair.getLeft()).containsExactly(productTypeDraft); + assertThat(pair.getRight()).containsExactlyInAnyOrder("x"); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithADraftWithAnInvalidNestedReference_ShouldResultInAnError() { + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(StringAttributeType.of(), "foo", ofEnglish("koko"), true) .build(); - final List attributes = new ArrayList<>(); - attributes.add(attributeDefinitionDraft); - attributes.add(invalidNestedTypeAttrDefDraft); - - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "foo", "foo", attributes) + final AttributeDefinitionDraft invalidNestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("")), + "invalidNested", + ofEnglish("koko"), + true) .build(); - final ProductTypeBatchValidator batchValidator = new ProductTypeBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = - batchValidator.validateAndCollectReferencedKeys(singletonList(productTypeDraft)); - - assertThat(pair.getLeft()).isEmpty(); - assertThat(pair.getRight()).isEmpty(); - - final String expectedExceptionMessage = - format(PRODUCT_TYPE_HAS_INVALID_REFERENCES, productTypeDraft.getKey(), "[invalidNested]"); - assertThat(errorCallBackMessages).containsExactly(expectedExceptionMessage); - assertThat(errorCallBackExceptions) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isInstanceOf(SyncException.class); - assertThat(throwable.getMessage()).isEqualTo(expectedExceptionMessage); - assertThat(throwable.getCause()).isInstanceOf(InvalidReferenceException.class); - assertThat(throwable.getCause().getMessage()).isEqualTo(BLANK_ID_VALUE_ON_REFERENCE); + final List attributes = new ArrayList<>(); + attributes.add(attributeDefinitionDraft); + attributes.add(invalidNestedTypeAttrDefDraft); + + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "foo", "foo", attributes).build(); + + final ProductTypeBatchValidator batchValidator = + new ProductTypeBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + batchValidator.validateAndCollectReferencedKeys(singletonList(productTypeDraft)); + + assertThat(pair.getLeft()).isEmpty(); + assertThat(pair.getRight()).isEmpty(); + + final String expectedExceptionMessage = + format(PRODUCT_TYPE_HAS_INVALID_REFERENCES, productTypeDraft.getKey(), "[invalidNested]"); + assertThat(errorCallBackMessages).containsExactly(expectedExceptionMessage); + assertThat(errorCallBackExceptions) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isInstanceOf(SyncException.class); + assertThat(throwable.getMessage()).isEqualTo(expectedExceptionMessage); + assertThat(throwable.getCause()).isInstanceOf(InvalidReferenceException.class); + assertThat(throwable.getCause().getMessage()).isEqualTo(BLANK_ID_VALUE_ON_REFERENCE); }); - } - - @Test - void validateAndCollectReferencedKeys_WithADraftWithMultipleInvalidNestedReferences_ShouldResultInAnError() { - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "foo", ofEnglish("koko"), true) - .build(); + } - final AttributeDefinitionDraft nestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(ProductType.referenceOfId("foo")), "validNested", ofEnglish("koko"), true) + @Test + void + validateAndCollectReferencedKeys_WithADraftWithMultipleInvalidNestedReferences_ShouldResultInAnError() { + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(StringAttributeType.of(), "foo", ofEnglish("koko"), true) .build(); - final AttributeDefinitionDraft setOfNestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("foo"))), "setOfNested", - ofEnglish("koko"), true) + final AttributeDefinitionDraft nestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("foo")), + "validNested", + ofEnglish("koko"), + true) .build(); - final AttributeDefinitionDraft invalidNestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(ProductType.referenceOfId("")), "invalidNested", ofEnglish("koko"), true) + final AttributeDefinitionDraft setOfNestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("foo"))), + "setOfNested", + ofEnglish("koko"), + true) .build(); - final AttributeDefinitionDraft setOfInvalidNestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId(null))), "setOfInvalidNested", - ofEnglish("koko"), true) + final AttributeDefinitionDraft invalidNestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("")), + "invalidNested", + ofEnglish("koko"), + true) .build(); - final List attributes = new ArrayList<>(); - attributes.add(attributeDefinitionDraft); - attributes.add(nestedTypeAttrDefDraft); - attributes.add(setOfNestedTypeAttrDefDraft); - attributes.add(invalidNestedTypeAttrDefDraft); - attributes.add(setOfInvalidNestedTypeAttrDefDraft); - attributes.add(null); - - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "foo", "foo", attributes) + final AttributeDefinitionDraft setOfInvalidNestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId(null))), + "setOfInvalidNested", + ofEnglish("koko"), + true) .build(); - final ProductTypeBatchValidator batchValidator = new ProductTypeBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = - batchValidator.validateAndCollectReferencedKeys(singletonList(productTypeDraft)); - - assertThat(pair.getLeft()).isEmpty(); - assertThat(pair.getRight()).isEmpty(); - - final String expectedExceptionMessage = format(PRODUCT_TYPE_HAS_INVALID_REFERENCES, - productTypeDraft.getKey(), "[invalidNested, setOfInvalidNested]"); - assertThat(errorCallBackMessages).containsExactly(expectedExceptionMessage); - assertThat(errorCallBackExceptions) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isInstanceOf(SyncException.class); - assertThat(throwable.getMessage()).isEqualTo(expectedExceptionMessage); - assertThat(throwable.getCause()).isInstanceOf(InvalidReferenceException.class); - assertThat(throwable.getCause().getMessage()).isEqualTo(BLANK_ID_VALUE_ON_REFERENCE); + final List attributes = new ArrayList<>(); + attributes.add(attributeDefinitionDraft); + attributes.add(nestedTypeAttrDefDraft); + attributes.add(setOfNestedTypeAttrDefDraft); + attributes.add(invalidNestedTypeAttrDefDraft); + attributes.add(setOfInvalidNestedTypeAttrDefDraft); + attributes.add(null); + + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "foo", "foo", attributes).build(); + + final ProductTypeBatchValidator batchValidator = + new ProductTypeBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + batchValidator.validateAndCollectReferencedKeys(singletonList(productTypeDraft)); + + assertThat(pair.getLeft()).isEmpty(); + assertThat(pair.getRight()).isEmpty(); + + final String expectedExceptionMessage = + format( + PRODUCT_TYPE_HAS_INVALID_REFERENCES, + productTypeDraft.getKey(), + "[invalidNested, setOfInvalidNested]"); + assertThat(errorCallBackMessages).containsExactly(expectedExceptionMessage); + assertThat(errorCallBackExceptions) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isInstanceOf(SyncException.class); + assertThat(throwable.getMessage()).isEqualTo(expectedExceptionMessage); + assertThat(throwable.getCause()).isInstanceOf(InvalidReferenceException.class); + assertThat(throwable.getCause().getMessage()).isEqualTo(BLANK_ID_VALUE_ON_REFERENCE); }); - } + } - @Test - void validateAndCollectReferencedKeys_WithMixOfValidAndInvalidDrafts_ShouldValidateCorrectly() { - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "foo", ofEnglish("koko"), true) + @Test + void validateAndCollectReferencedKeys_WithMixOfValidAndInvalidDrafts_ShouldValidateCorrectly() { + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(StringAttributeType.of(), "foo", ofEnglish("koko"), true) .build(); - final AttributeDefinitionDraft nestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(ProductType.referenceOfId("x")), "validNested", ofEnglish("koko"), true) + final AttributeDefinitionDraft nestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("x")), + "validNested", + ofEnglish("koko"), + true) .build(); - final AttributeDefinitionDraft setOfNestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("y"))), "setOfNested", - ofEnglish("koko"), true) + final AttributeDefinitionDraft setOfNestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("y"))), + "setOfNested", + ofEnglish("koko"), + true) .build(); - final AttributeDefinitionDraft invalidNestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of(ProductType.referenceOfId("")), "invalidNested", ofEnglish("koko"), true) + final AttributeDefinitionDraft invalidNestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("")), + "invalidNested", + ofEnglish("koko"), + true) .build(); - final AttributeDefinitionDraft setOfInvalidNestedTypeAttrDefDraft = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId(""))), "setOfInvalidNested", - ofEnglish("koko"), true) + final AttributeDefinitionDraft setOfInvalidNestedTypeAttrDefDraft = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId(""))), + "setOfInvalidNested", + ofEnglish("koko"), + true) .build(); - final List attributes = new ArrayList<>(); - attributes.add(attributeDefinitionDraft); - attributes.add(nestedTypeAttrDefDraft); - attributes.add(setOfNestedTypeAttrDefDraft); - attributes.add(invalidNestedTypeAttrDefDraft); - attributes.add(setOfInvalidNestedTypeAttrDefDraft); - - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "foo", "foo", attributes) - .build(); - - final ProductTypeDraft productTypeDraftWithEmptyKey = ProductTypeDraftBuilder - .of("", "foo", "foo", attributes) + final List attributes = new ArrayList<>(); + attributes.add(attributeDefinitionDraft); + attributes.add(nestedTypeAttrDefDraft); + attributes.add(setOfNestedTypeAttrDefDraft); + attributes.add(invalidNestedTypeAttrDefDraft); + attributes.add(setOfInvalidNestedTypeAttrDefDraft); + + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "foo", "foo", attributes).build(); + + final ProductTypeDraft productTypeDraftWithEmptyKey = + ProductTypeDraftBuilder.of("", "foo", "foo", attributes).build(); + + final ProductTypeDraft validProductTypeDraftWithReferences = + ProductTypeDraftBuilder.of( + "bar", + "bar", + "bar", + asList( + attributeDefinitionDraft, nestedTypeAttrDefDraft, setOfNestedTypeAttrDefDraft)) .build(); - final ProductTypeDraft validProductTypeDraftWithReferences = ProductTypeDraftBuilder - .of("bar", "bar", "bar", - asList(attributeDefinitionDraft, nestedTypeAttrDefDraft, setOfNestedTypeAttrDefDraft)) - .build(); - - final ProductTypeDraft draftWithEmptyAttributes = ProductTypeDraftBuilder - .of("bar", "bar", "bar", emptyList()) - .build(); - - final ProductTypeDraft draftWithNullAttributes = ProductTypeDraftBuilder - .of("bar", "bar", "bar", null) - .build(); - - final List productTypeDrafts = new ArrayList<>(); - productTypeDrafts.add(productTypeDraft); - productTypeDrafts.add(null); - productTypeDrafts.add(productTypeDraftWithEmptyKey); - productTypeDrafts.add(validProductTypeDraftWithReferences); - productTypeDrafts.add(draftWithEmptyAttributes); - productTypeDrafts.add(draftWithNullAttributes); - - final ProductTypeBatchValidator batchValidator = new ProductTypeBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = - batchValidator.validateAndCollectReferencedKeys(productTypeDrafts); - - assertThat(pair.getLeft()).containsExactlyInAnyOrder(validProductTypeDraftWithReferences, - draftWithEmptyAttributes, draftWithNullAttributes); - assertThat(pair.getRight()).containsExactlyInAnyOrder("x", "y"); - - final String expectedExceptionMessage = format(PRODUCT_TYPE_HAS_INVALID_REFERENCES, - productTypeDraft.getKey(), "[invalidNested, setOfInvalidNested]"); - assertThat(errorCallBackMessages).containsExactlyInAnyOrderElementsOf( - asList(expectedExceptionMessage, PRODUCT_TYPE_DRAFT_IS_NULL, + final ProductTypeDraft draftWithEmptyAttributes = + ProductTypeDraftBuilder.of("bar", "bar", "bar", emptyList()).build(); + + final ProductTypeDraft draftWithNullAttributes = + ProductTypeDraftBuilder.of("bar", "bar", "bar", null).build(); + + final List productTypeDrafts = new ArrayList<>(); + productTypeDrafts.add(productTypeDraft); + productTypeDrafts.add(null); + productTypeDrafts.add(productTypeDraftWithEmptyKey); + productTypeDrafts.add(validProductTypeDraftWithReferences); + productTypeDrafts.add(draftWithEmptyAttributes); + productTypeDrafts.add(draftWithNullAttributes); + + final ProductTypeBatchValidator batchValidator = + new ProductTypeBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + batchValidator.validateAndCollectReferencedKeys(productTypeDrafts); + + assertThat(pair.getLeft()) + .containsExactlyInAnyOrder( + validProductTypeDraftWithReferences, draftWithEmptyAttributes, draftWithNullAttributes); + assertThat(pair.getRight()).containsExactlyInAnyOrder("x", "y"); + + final String expectedExceptionMessage = + format( + PRODUCT_TYPE_HAS_INVALID_REFERENCES, + productTypeDraft.getKey(), + "[invalidNested, setOfInvalidNested]"); + assertThat(errorCallBackMessages) + .containsExactlyInAnyOrderElementsOf( + asList( + expectedExceptionMessage, + PRODUCT_TYPE_DRAFT_IS_NULL, format(PRODUCT_TYPE_DRAFT_KEY_NOT_SET, productTypeDraftWithEmptyKey.getName()))); - final Predicate invalidReferencePredicate = throwable -> + final Predicate invalidReferencePredicate = + throwable -> expectedExceptionMessage.equals(throwable.getMessage()) && BLANK_ID_VALUE_ON_REFERENCE.equals(throwable.getCause().getMessage()); - final Condition invalidReferenceCondition = new Condition<>(invalidReferencePredicate, + final Condition invalidReferenceCondition = + new Condition<>( + invalidReferencePredicate, "ReferenceResolutionException: " + "ProductTypeDraft with Key 'foo' has invalid references on attributeDraft with name 'nested'."); - final Predicate nullDraftPredicate = throwable -> + final Predicate nullDraftPredicate = + throwable -> PRODUCT_TYPE_DRAFT_IS_NULL.equals(throwable.getMessage()) && throwable instanceof SyncException; - final Condition nullDraftCondition = new Condition<>(nullDraftPredicate, - "SyncException: ProductTypeDraft is null."); + final Condition nullDraftCondition = + new Condition<>(nullDraftPredicate, "SyncException: ProductTypeDraft is null."); - final Predicate blankProductTypeKeyPredicate = throwable -> + final Predicate blankProductTypeKeyPredicate = + throwable -> format(PRODUCT_TYPE_DRAFT_KEY_NOT_SET, productTypeDraftWithEmptyKey.getName()) - .equals(throwable.getMessage()) && throwable instanceof SyncException; - - final Condition blankKeyCondition = new Condition<>(blankProductTypeKeyPredicate, - "SyncException: ProductTypeDraft has blank key."); - - - assertThat(errorCallBackExceptions) - .hasSize(3) - .haveExactly(1, invalidReferenceCondition) - .haveExactly(1, nullDraftCondition) - .haveExactly(1, blankKeyCondition); - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List productTypeDrafts) { - final ProductTypeBatchValidator batchValidator = new ProductTypeBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = - batchValidator.validateAndCollectReferencedKeys(productTypeDrafts); - return pair.getLeft(); - } + .equals(throwable.getMessage()) + && throwable instanceof SyncException; + + final Condition blankKeyCondition = + new Condition<>( + blankProductTypeKeyPredicate, "SyncException: ProductTypeDraft has blank key."); + + assertThat(errorCallBackExceptions) + .hasSize(3) + .haveExactly(1, invalidReferenceCondition) + .haveExactly(1, nullDraftCondition) + .haveExactly(1, blankKeyCondition); + } + + @Nonnull + private Set getValidDrafts( + @Nonnull final List productTypeDrafts) { + final ProductTypeBatchValidator batchValidator = + new ProductTypeBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + batchValidator.validateAndCollectReferencedKeys(productTypeDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeReferenceResolverTest.java b/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeReferenceResolverTest.java index 0a4d0df5fd..960c284518 100644 --- a/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeReferenceResolverTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.producttypes.helpers; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.producttypes.ProductTypeSyncOptions; import com.commercetools.sync.producttypes.ProductTypeSyncOptionsBuilder; import com.commercetools.sync.services.ProductTypeService; @@ -13,282 +22,268 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.ProductTypeDraftBuilder; import io.sphere.sdk.producttypes.ProductTypeDraftDsl; -import org.junit.jupiter.api.Test; - import java.util.Optional; import java.util.concurrent.CompletableFuture; - -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductTypeReferenceResolverTest { - @Test - void resolveReferences_WithNullAttributes_ShouldNotResolveReferences() { - // preparation - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("foo", "foo", "desc", null) - .build(); + @Test + void resolveReferences_WithNullAttributes_ShouldNotResolveReferences() { + // preparation + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "foo", "desc", null).build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + + final ProductTypeReferenceResolver productTypeReferenceResolver = + new ProductTypeReferenceResolver(syncOptions, productTypeService); + + final ProductTypeDraftDsl expectedResolvedProductTypeDraft = + ProductTypeDraftBuilder.of(productTypeDraft).build(); + + // test and assertion + assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) + .isCompletedWithValue(expectedResolvedProductTypeDraft); + } + + @Test + void resolveReferences_WithNoAttributes_ShouldNotResolveReferences() { + // preparation + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "foo", "desc", emptyList()).build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + + final ProductTypeReferenceResolver productTypeReferenceResolver = + new ProductTypeReferenceResolver(syncOptions, productTypeService); + + final ProductTypeDraftDsl expectedResolvedProductTypeDraft = + ProductTypeDraftBuilder.of(productTypeDraft).attributes(emptyList()).build(); + + // test and assertion + assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) + .isCompletedWithValue(expectedResolvedProductTypeDraft); + } + + @Test + void resolveReferences_WithNoNestedTypeReferences_ShouldResolveReferences() { + // preparation + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), "string attr", ofEnglish("string attr label"), true) + .build(); + + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "foo", "desc", singletonList(attributeDefinitionDraft)) + .build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + final ProductTypeReferenceResolver productTypeReferenceResolver = + new ProductTypeReferenceResolver(syncOptions, productTypeService); + + final ProductTypeDraftDsl expectedResolvedProductTypeDraft = + ProductTypeDraftBuilder.of(productTypeDraft) + .attributes(singletonList(attributeDefinitionDraft)) + .build(); + + // test and assertion + assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) + .isCompletedWithValue(expectedResolvedProductTypeDraft); + } + + @Test + void + resolveReferences_WithOneNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { + // preparation + final NestedAttributeType nestedAttributeType = + NestedAttributeType.of(ProductType.reference("x")); + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) + .build(); + + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "foo", "desc", singletonList(attributeDefinitionDraft)) + .build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + + final ProductTypeReferenceResolver productTypeReferenceResolver = + new ProductTypeReferenceResolver(syncOptions, productTypeService); + + final NestedAttributeType expectedResolvedNestedAttributeType = + NestedAttributeType.of(ProductType.referenceOfId("foo")); + final AttributeDefinitionDraft expectedResolvedAttrDef = + AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) + .attributeType(expectedResolvedNestedAttributeType) + .build(); + final ProductTypeDraftDsl expectedResolvedProductTypeDraft = + ProductTypeDraftBuilder.of(productTypeDraft) + .attributes(singletonList(expectedResolvedAttrDef)) + .build(); + + // test and assertion + assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) + .isCompletedWithValue(expectedResolvedProductTypeDraft); + } + + @Test + void + resolveReferences_WithManyNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { + // preparation + final NestedAttributeType nestedAttributeType = + NestedAttributeType.of(ProductType.reference("x")); + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) + .build(); - final ProductTypeReferenceResolver productTypeReferenceResolver = new ProductTypeReferenceResolver(syncOptions, - productTypeService); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of( + "foo", "foo", "desc", asList(attributeDefinitionDraft, attributeDefinitionDraft)) + .build(); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final ProductTypeDraftDsl expectedResolvedProductTypeDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .build(); + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - // test and assertion - assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) - .isCompletedWithValue(expectedResolvedProductTypeDraft); - } + final ProductTypeReferenceResolver productTypeReferenceResolver = + new ProductTypeReferenceResolver(syncOptions, productTypeService); - @Test - void resolveReferences_WithNoAttributes_ShouldNotResolveReferences() { - // preparation - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("foo", "foo", "desc", emptyList()) - .build(); + final NestedAttributeType expectedResolvedNestedAttributeType = + NestedAttributeType.of(ProductType.referenceOfId("foo")); + final AttributeDefinitionDraft expectedResolvedAttrDef = + AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) + .attributeType(expectedResolvedNestedAttributeType) + .build(); + final ProductTypeDraftDsl expectedResolvedProductTypeDraft = + ProductTypeDraftBuilder.of(productTypeDraft) + .attributes(asList(expectedResolvedAttrDef, expectedResolvedAttrDef)) + .build(); - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + // test and assertion + assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) + .isCompletedWithValue(expectedResolvedProductTypeDraft); + } + + @Test + void + resolveReferences_WithOneSetOfNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { + // preparation + final NestedAttributeType nestedAttributeType = + NestedAttributeType.of(ProductType.reference("x")); + final SetAttributeType setAttributeType = SetAttributeType.of(nestedAttributeType); + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true).build(); + + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "foo", "desc", singletonList(attributeDefinitionDraft)) + .build(); - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final ProductTypeReferenceResolver productTypeReferenceResolver = new ProductTypeReferenceResolver(syncOptions, - productTypeService); + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + final ProductTypeReferenceResolver productTypeReferenceResolver = + new ProductTypeReferenceResolver(syncOptions, productTypeService); - final ProductTypeDraftDsl expectedResolvedProductTypeDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .attributes(emptyList()) - .build(); + final NestedAttributeType expectedResolvedNestedAttributeType = + NestedAttributeType.of(ProductType.referenceOfId("foo")); + final SetAttributeType expectedSetAttributeType = + SetAttributeType.of(expectedResolvedNestedAttributeType); - // test and assertion - assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) - .isCompletedWithValue(expectedResolvedProductTypeDraft); - } + final AttributeDefinitionDraft expectedResolvedAttrDef = + AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) + .attributeType(expectedSetAttributeType) + .build(); + final ProductTypeDraftDsl expectedResolvedProductTypeDraft = + ProductTypeDraftBuilder.of(productTypeDraft) + .attributes(singletonList(expectedResolvedAttrDef)) + .build(); - @Test - void resolveReferences_WithNoNestedTypeReferences_ShouldResolveReferences() { - // preparation - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "string attr", ofEnglish("string attr label"), true) + // test and assertion + assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) + .isCompletedWithValue(expectedResolvedProductTypeDraft); + } + + @Test + void + resolveReferences_WithNestedAndSetOfNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { + // preparation + final NestedAttributeType nestedAttributeType = + NestedAttributeType.of(ProductType.reference("x")); + final SetAttributeType setAttributeType = SetAttributeType.of(nestedAttributeType); + final AttributeDefinitionDraft setAttributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true).build(); + final AttributeDefinitionDraft nestedAttributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) .build(); - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("foo", "foo", "desc", singletonList(attributeDefinitionDraft)) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - - final ProductTypeReferenceResolver productTypeReferenceResolver = new ProductTypeReferenceResolver(syncOptions, - productTypeService); - - - final ProductTypeDraftDsl expectedResolvedProductTypeDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .attributes(singletonList(attributeDefinitionDraft)) - .build(); - - // test and assertion - assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) - .isCompletedWithValue(expectedResolvedProductTypeDraft); - } - - @Test - void resolveReferences_WithOneNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { - // preparation - final NestedAttributeType nestedAttributeType = NestedAttributeType.of(ProductType.reference("x")); - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("foo", "foo", "desc", - singletonList(attributeDefinitionDraft)) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - - final ProductTypeReferenceResolver productTypeReferenceResolver = new ProductTypeReferenceResolver(syncOptions, - productTypeService); - - - final NestedAttributeType expectedResolvedNestedAttributeType = - NestedAttributeType.of(ProductType.referenceOfId("foo")); - final AttributeDefinitionDraft expectedResolvedAttrDef = - AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) - .attributeType(expectedResolvedNestedAttributeType) - .build(); - final ProductTypeDraftDsl expectedResolvedProductTypeDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .attributes(singletonList(expectedResolvedAttrDef)) - .build(); - - // test and assertion - assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) - .isCompletedWithValue(expectedResolvedProductTypeDraft); - } - - @Test - void resolveReferences_WithManyNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { - // preparation - final NestedAttributeType nestedAttributeType = NestedAttributeType.of(ProductType.reference("x")); - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("foo", "foo", "desc", - asList(attributeDefinitionDraft, attributeDefinitionDraft)) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - - final ProductTypeReferenceResolver productTypeReferenceResolver = new ProductTypeReferenceResolver(syncOptions, - productTypeService); - - - final NestedAttributeType expectedResolvedNestedAttributeType = - NestedAttributeType.of(ProductType.referenceOfId("foo")); - final AttributeDefinitionDraft expectedResolvedAttrDef = - AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) - .attributeType(expectedResolvedNestedAttributeType) - .build(); - final ProductTypeDraftDsl expectedResolvedProductTypeDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .attributes(asList(expectedResolvedAttrDef, expectedResolvedAttrDef)) - .build(); - - // test and assertion - assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) - .isCompletedWithValue(expectedResolvedProductTypeDraft); - } - - @Test - void resolveReferences_WithOneSetOfNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { - // preparation - final NestedAttributeType nestedAttributeType = NestedAttributeType.of(ProductType.reference("x")); - final SetAttributeType setAttributeType = SetAttributeType.of(nestedAttributeType); - final AttributeDefinitionDraft attributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("foo", "foo", "desc", - singletonList(attributeDefinitionDraft)) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - - final ProductTypeReferenceResolver productTypeReferenceResolver = new ProductTypeReferenceResolver(syncOptions, - productTypeService); - - - final NestedAttributeType expectedResolvedNestedAttributeType = - NestedAttributeType.of(ProductType.referenceOfId("foo")); - final SetAttributeType expectedSetAttributeType = - SetAttributeType.of(expectedResolvedNestedAttributeType); - - final AttributeDefinitionDraft expectedResolvedAttrDef = - AttributeDefinitionDraftBuilder.of(attributeDefinitionDraft) - .attributeType(expectedSetAttributeType) - .build(); - final ProductTypeDraftDsl expectedResolvedProductTypeDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .attributes(singletonList(expectedResolvedAttrDef)) - .build(); - - // test and assertion - assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) - .isCompletedWithValue(expectedResolvedProductTypeDraft); - } - - @Test - void resolveReferences_WithNestedAndSetOfNestedTypeWithExistingProductTypeReference_ShouldResolveReferences() { - // preparation - final NestedAttributeType nestedAttributeType = NestedAttributeType.of(ProductType.reference("x")); - final SetAttributeType setAttributeType = SetAttributeType.of(nestedAttributeType); - final AttributeDefinitionDraft setAttributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(setAttributeType, "foo", ofEnglish("foo"), true) - .build(); - final AttributeDefinitionDraft nestedAttributeDefinitionDraft = - AttributeDefinitionDraftBuilder.of(nestedAttributeType, "foo", ofEnglish("foo"), true) - .build(); - - final ProductTypeDraft productTypeDraft = - ProductTypeDraftBuilder.of("foo", "foo", "desc", + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of( + "foo", + "foo", + "desc", asList(setAttributeDefinitionDraft, nestedAttributeDefinitionDraft)) - .build(); - - final ProductTypeSyncOptions syncOptions = - ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ProductTypeService productTypeService = mock(ProductTypeService.class); - when(productTypeService.fetchCachedProductTypeId(any())) - .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); - - final ProductTypeReferenceResolver productTypeReferenceResolver = new ProductTypeReferenceResolver(syncOptions, - productTypeService); - - - final NestedAttributeType expectedResolvedNestedAttributeType = - NestedAttributeType.of(ProductType.referenceOfId("foo")); - final SetAttributeType expectedResolvedSetAttributeType = - SetAttributeType.of(expectedResolvedNestedAttributeType); - - final AttributeDefinitionDraft expectedResolvedSetAttrDef = - AttributeDefinitionDraftBuilder.of(setAttributeDefinitionDraft) - .attributeType(expectedResolvedSetAttributeType) - .build(); - final AttributeDefinitionDraft expectedResolvedNestedAttrDef = - AttributeDefinitionDraftBuilder.of(nestedAttributeDefinitionDraft) - .attributeType(expectedResolvedNestedAttributeType) - .build(); - - final ProductTypeDraftDsl expectedResolvedProductTypeDraft = - ProductTypeDraftBuilder.of(productTypeDraft) - .attributes(asList(expectedResolvedSetAttrDef, expectedResolvedNestedAttrDef)) - .build(); - - // test and assertion - assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) - .isCompletedWithValue(expectedResolvedProductTypeDraft); - } -} \ No newline at end of file + .build(); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ProductTypeService productTypeService = mock(ProductTypeService.class); + when(productTypeService.fetchCachedProductTypeId(any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of("foo"))); + + final ProductTypeReferenceResolver productTypeReferenceResolver = + new ProductTypeReferenceResolver(syncOptions, productTypeService); + + final NestedAttributeType expectedResolvedNestedAttributeType = + NestedAttributeType.of(ProductType.referenceOfId("foo")); + final SetAttributeType expectedResolvedSetAttributeType = + SetAttributeType.of(expectedResolvedNestedAttributeType); + + final AttributeDefinitionDraft expectedResolvedSetAttrDef = + AttributeDefinitionDraftBuilder.of(setAttributeDefinitionDraft) + .attributeType(expectedResolvedSetAttributeType) + .build(); + final AttributeDefinitionDraft expectedResolvedNestedAttrDef = + AttributeDefinitionDraftBuilder.of(nestedAttributeDefinitionDraft) + .attributeType(expectedResolvedNestedAttributeType) + .build(); + + final ProductTypeDraftDsl expectedResolvedProductTypeDraft = + ProductTypeDraftBuilder.of(productTypeDraft) + .attributes(asList(expectedResolvedSetAttrDef, expectedResolvedNestedAttrDef)) + .build(); + + // test and assertion + assertThat(productTypeReferenceResolver.resolveReferences(productTypeDraft)) + .isCompletedWithValue(expectedResolvedProductTypeDraft); + } +} diff --git a/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeSyncStatisticsTest.java b/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeSyncStatisticsTest.java index 1c33a2f77f..e29ec8e9ad 100644 --- a/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeSyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/helpers/ProductTypeSyncStatisticsTest.java @@ -1,561 +1,666 @@ package com.commercetools.sync.producttypes.helpers; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.products.attributes.AttributeDefinitionDraft; import io.sphere.sdk.products.attributes.AttributeDefinitionDraftBuilder; import io.sphere.sdk.products.attributes.NestedAttributeType; import io.sphere.sdk.producttypes.ProductType; -import org.junit.jupiter.api.Test; - import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; - -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductTypeSyncStatisticsTest { - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - // preparation - final ProductTypeSyncStatistics productTypeSyncStatistics = mock(ProductTypeSyncStatistics.class); - when(productTypeSyncStatistics.getCreated()).thenReturn(new AtomicInteger(1)); - when(productTypeSyncStatistics.getFailed()).thenReturn(new AtomicInteger(2)); - when(productTypeSyncStatistics.getUpdated()).thenReturn(new AtomicInteger(3)); - when(productTypeSyncStatistics.getProcessed()).thenReturn(new AtomicInteger(6)); - when(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()).thenReturn(0); - when(productTypeSyncStatistics.getReportMessage()).thenCallRealMethod(); - - // test and assertion - assertThat(productTypeSyncStatistics.getReportMessage()) - .isEqualTo("Summary: 6 product types were processed in total " + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + // preparation + final ProductTypeSyncStatistics productTypeSyncStatistics = + mock(ProductTypeSyncStatistics.class); + when(productTypeSyncStatistics.getCreated()).thenReturn(new AtomicInteger(1)); + when(productTypeSyncStatistics.getFailed()).thenReturn(new AtomicInteger(2)); + when(productTypeSyncStatistics.getUpdated()).thenReturn(new AtomicInteger(3)); + when(productTypeSyncStatistics.getProcessed()).thenReturn(new AtomicInteger(6)); + when(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()) + .thenReturn(0); + when(productTypeSyncStatistics.getReportMessage()).thenCallRealMethod(); + + // test and assertion + assertThat(productTypeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 6 product types were processed in total " + "(1 created, 3 updated, 2 failed to sync and 0 product types with at least one NestedType or a Set of" + " NestedType attribute definition(s) referencing a missing product type)."); - } - - @Test - void getNumberOfProductTypesWithMissingNestedProductTypes_WithEmptyMap_ShouldReturn0() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - // test and assertion - assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()).isEqualTo(0); - } - - @Test - void getNumberOfProductTypesWithMissingNestedProductTypes_WithMissingButNoReferencingProductType_ShouldBe0() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - missingProductTypeReferences.put("missing1", new ConcurrentHashMap<>()); - missingProductTypeReferences.put("missing2", new ConcurrentHashMap<>()); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - // test and assertion - assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()).isEqualTo(0); - } - - @Test - void getNumberOfProductTypesWithMissingNestedProductTypes_WithOneReferencingProdTypes_ShouldBe1() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - productTypesReferencingMissing1.put("referencing-product-type-1", ConcurrentHashMap.newKeySet()); - - missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - // test and assertion - assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()).isEqualTo(1); - } - - @Test - void getNumberOfProductTypesWithMissingNestedProductTypes_WithSameReferencingProdTypes_CountDistinct() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - productTypesReferencingMissing1.put("referencing-product-type-1", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing1.put("referencing-product-type-2", ConcurrentHashMap.newKeySet()); - - final ConcurrentHashMap> - productTypesReferencingMissing2 = new ConcurrentHashMap<>(); - productTypesReferencingMissing2.put("referencing-product-type-1", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing2.put("referencing-product-type-2", ConcurrentHashMap.newKeySet()); - - missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); - missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - // test and assertion - assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()).isEqualTo(2); - } - - @Test - void getNumberOfProductTypesWithMissingNestedProductTypes_WithDifferentReferencingProdTypes_CountDistinct() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - productTypesReferencingMissing1.put("referencing-product-type-1", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing1.put("referencing-product-type-2", ConcurrentHashMap.newKeySet()); - - final ConcurrentHashMap> - productTypesReferencingMissing2 = new ConcurrentHashMap<>(); - productTypesReferencingMissing2.put("referencing-product-type-3", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing2.put("referencing-product-type-4", ConcurrentHashMap.newKeySet()); - - missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); - missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - // test and assertion - assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()).isEqualTo(4); - } - - - @Test - void putMissingNestedProductType_WithNullExistingReferencingProductTypes_ShouldCreateANewMap() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - - final AttributeDefinitionDraft referencingAttributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missingPT")), "attr-name", ofEnglish("label"), true) + } + + @Test + void getNumberOfProductTypesWithMissingNestedProductTypes_WithEmptyMap_ShouldReturn0() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test and assertion + assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()) + .isEqualTo(0); + } + + @Test + void + getNumberOfProductTypesWithMissingNestedProductTypes_WithMissingButNoReferencingProductType_ShouldBe0() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + missingProductTypeReferences.put("missing1", new ConcurrentHashMap<>()); + missingProductTypeReferences.put("missing2", new ConcurrentHashMap<>()); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test and assertion + assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()) + .isEqualTo(0); + } + + @Test + void + getNumberOfProductTypesWithMissingNestedProductTypes_WithOneReferencingProdTypes_ShouldBe1() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + productTypesReferencingMissing1.put( + "referencing-product-type-1", ConcurrentHashMap.newKeySet()); + + missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test and assertion + assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()) + .isEqualTo(1); + } + + @Test + void + getNumberOfProductTypesWithMissingNestedProductTypes_WithSameReferencingProdTypes_CountDistinct() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + productTypesReferencingMissing1.put( + "referencing-product-type-1", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing1.put( + "referencing-product-type-2", ConcurrentHashMap.newKeySet()); + + final ConcurrentHashMap> + productTypesReferencingMissing2 = new ConcurrentHashMap<>(); + productTypesReferencingMissing2.put( + "referencing-product-type-1", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing2.put( + "referencing-product-type-2", ConcurrentHashMap.newKeySet()); + + missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); + missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test and assertion + assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()) + .isEqualTo(2); + } + + @Test + void + getNumberOfProductTypesWithMissingNestedProductTypes_WithDifferentReferencingProdTypes_CountDistinct() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + productTypesReferencingMissing1.put( + "referencing-product-type-1", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing1.put( + "referencing-product-type-2", ConcurrentHashMap.newKeySet()); + + final ConcurrentHashMap> + productTypesReferencingMissing2 = new ConcurrentHashMap<>(); + productTypesReferencingMissing2.put( + "referencing-product-type-3", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing2.put( + "referencing-product-type-4", ConcurrentHashMap.newKeySet()); + + missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); + missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test and assertion + assertThat(productTypeSyncStatistics.getNumberOfProductTypesWithMissingNestedProductTypes()) + .isEqualTo(4); + } + + @Test + void putMissingNestedProductType_WithNullExistingReferencingProductTypes_ShouldCreateANewMap() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + + final AttributeDefinitionDraft referencingAttributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missingPT")), + "attr-name", + ofEnglish("label"), + true) .build(); - final ConcurrentHashMap.KeySetView definitionDrafts = - ConcurrentHashMap.newKeySet(); - definitionDrafts.add(referencingAttributeDefinitionDraft); - productTypesReferencingMissing1.put("referencingPT", definitionDrafts); - - missingProductTypeReferences.put("missingPT", productTypesReferencingMissing1); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - - // test - productTypeSyncStatistics.putMissingNestedProductType("newMissing", - "referencingPT", - referencingAttributeDefinitionDraft); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(2); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("newMissing")).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents() - .get("newMissing") - .get("referencingPT")) - .contains(referencingAttributeDefinitionDraft); - } - - @Test - void putMissingNestedProductType_WithNullExistingAttributeDefs_ShouldCreateANewMap() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - - final AttributeDefinitionDraft referencingAttributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missingPT")), "attr-name", ofEnglish("label"), true) + final ConcurrentHashMap.KeySetView definitionDrafts = + ConcurrentHashMap.newKeySet(); + definitionDrafts.add(referencingAttributeDefinitionDraft); + productTypesReferencingMissing1.put("referencingPT", definitionDrafts); + + missingProductTypeReferences.put("missingPT", productTypesReferencingMissing1); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test + productTypeSyncStatistics.putMissingNestedProductType( + "newMissing", "referencingPT", referencingAttributeDefinitionDraft); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(2); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("newMissing")) + .hasSize(1); + assertThat( + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get("newMissing") + .get("referencingPT")) + .contains(referencingAttributeDefinitionDraft); + } + + @Test + void putMissingNestedProductType_WithNullExistingAttributeDefs_ShouldCreateANewMap() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + + final AttributeDefinitionDraft referencingAttributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missingPT")), + "attr-name", + ofEnglish("label"), + true) .build(); - final ConcurrentHashMap.KeySetView definitionDrafts = - ConcurrentHashMap.newKeySet(); - definitionDrafts.add(referencingAttributeDefinitionDraft); - - missingProductTypeReferences.put("missingPT", productTypesReferencingMissing1); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - - // test - productTypeSyncStatistics.putMissingNestedProductType("missingPT", - "referencingPT", - referencingAttributeDefinitionDraft); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missingPT")).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents() - .get("missingPT") - .get("referencingPT")) - .contains(referencingAttributeDefinitionDraft); - } - - @Test - void putMissingNestedProductType_WithIdenticalOneReferencingAttrToAnEmptyMap_ShouldOverwriteExisting() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - - final AttributeDefinitionDraft referencingAttributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missingPT")), "attr-name", ofEnglish("label"), true) + final ConcurrentHashMap.KeySetView definitionDrafts = + ConcurrentHashMap.newKeySet(); + definitionDrafts.add(referencingAttributeDefinitionDraft); + + missingProductTypeReferences.put("missingPT", productTypesReferencingMissing1); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test + productTypeSyncStatistics.putMissingNestedProductType( + "missingPT", "referencingPT", referencingAttributeDefinitionDraft); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missingPT")) + .hasSize(1); + assertThat( + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get("missingPT") + .get("referencingPT")) + .contains(referencingAttributeDefinitionDraft); + } + + @Test + void + putMissingNestedProductType_WithIdenticalOneReferencingAttrToAnEmptyMap_ShouldOverwriteExisting() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + + final AttributeDefinitionDraft referencingAttributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missingPT")), + "attr-name", + ofEnglish("label"), + true) .build(); - final ConcurrentHashMap.KeySetView definitionDrafts = - ConcurrentHashMap.newKeySet(); - definitionDrafts.add(referencingAttributeDefinitionDraft); - productTypesReferencingMissing1.put("referencingPT", definitionDrafts); - - missingProductTypeReferences.put("missingPT", productTypesReferencingMissing1); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - - // test - productTypeSyncStatistics.putMissingNestedProductType("missingPT", - "referencingPT", - referencingAttributeDefinitionDraft); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missingPT")).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents() - .get("missingPT") - .get("referencingPT")) - .contains(referencingAttributeDefinitionDraft); - } - - @Test - void putMissingNestedProductType_WithOnlyOneReferencingAttrToAnEmptyMap_ShouldHaveOnly1() { - // preparation - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(new ConcurrentHashMap<>()); - - final AttributeDefinitionDraft referencingAttributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missingPT")), "attr-name", ofEnglish("label"), true) + final ConcurrentHashMap.KeySetView definitionDrafts = + ConcurrentHashMap.newKeySet(); + definitionDrafts.add(referencingAttributeDefinitionDraft); + productTypesReferencingMissing1.put("referencingPT", definitionDrafts); + + missingProductTypeReferences.put("missingPT", productTypesReferencingMissing1); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test + productTypeSyncStatistics.putMissingNestedProductType( + "missingPT", "referencingPT", referencingAttributeDefinitionDraft); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missingPT")) + .hasSize(1); + assertThat( + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get("missingPT") + .get("referencingPT")) + .contains(referencingAttributeDefinitionDraft); + } + + @Test + void putMissingNestedProductType_WithOnlyOneReferencingAttrToAnEmptyMap_ShouldHaveOnly1() { + // preparation + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(new ConcurrentHashMap<>()); + + final AttributeDefinitionDraft referencingAttributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missingPT")), + "attr-name", + ofEnglish("label"), + true) .build(); - - // test - productTypeSyncStatistics.putMissingNestedProductType("missingPT", - "referencingPT", - referencingAttributeDefinitionDraft); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missingPT")).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents() - .get("missingPT") - .get("referencingPT")) - .contains(referencingAttributeDefinitionDraft); - } - - @Test - void putMissingNestedProductType_WithOnlyOneReferencingAttrToAnEmptyMap_ShouldHaveMultiple() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - productTypesReferencingMissing1.put("referencing-product-type-1", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing1.put("referencing-product-type-2", ConcurrentHashMap.newKeySet()); - - final ConcurrentHashMap> - productTypesReferencingMissing2 = new ConcurrentHashMap<>(); - productTypesReferencingMissing2.put("referencing-product-type-3", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing2.put("referencing-product-type-4", ConcurrentHashMap.newKeySet()); - - missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); - missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - final AttributeDefinitionDraft referencingAttributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missingPT")), "attr-name", ofEnglish("label"), true) + // test + productTypeSyncStatistics.putMissingNestedProductType( + "missingPT", "referencingPT", referencingAttributeDefinitionDraft); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missingPT")) + .hasSize(1); + assertThat( + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get("missingPT") + .get("referencingPT")) + .contains(referencingAttributeDefinitionDraft); + } + + @Test + void putMissingNestedProductType_WithOnlyOneReferencingAttrToAnEmptyMap_ShouldHaveMultiple() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + productTypesReferencingMissing1.put( + "referencing-product-type-1", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing1.put( + "referencing-product-type-2", ConcurrentHashMap.newKeySet()); + + final ConcurrentHashMap> + productTypesReferencingMissing2 = new ConcurrentHashMap<>(); + productTypesReferencingMissing2.put( + "referencing-product-type-3", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing2.put( + "referencing-product-type-4", ConcurrentHashMap.newKeySet()); + + missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); + missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + final AttributeDefinitionDraft referencingAttributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missingPT")), + "attr-name", + ofEnglish("label"), + true) .build(); - - // test - productTypeSyncStatistics.putMissingNestedProductType("missingPT", - "referencingPT", - referencingAttributeDefinitionDraft); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(3); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missingPT")).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents() - .get("missingPT") - .get("referencingPT")) - .contains(referencingAttributeDefinitionDraft); - } - - @Test - void putMissingNestedProductType_WithAdditionalAttribute_ShouldAppendAttribute() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - final ConcurrentHashMap.KeySetView definitionDrafts = - ConcurrentHashMap.newKeySet(); - final AttributeDefinitionDraft existingReferencingAttr = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missing1")), "attr-name-1", ofEnglish("label"), true) + // test + productTypeSyncStatistics.putMissingNestedProductType( + "missingPT", "referencingPT", referencingAttributeDefinitionDraft); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(3); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missingPT")) + .hasSize(1); + assertThat( + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get("missingPT") + .get("referencingPT")) + .contains(referencingAttributeDefinitionDraft); + } + + @Test + void putMissingNestedProductType_WithAdditionalAttribute_ShouldAppendAttribute() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + final ConcurrentHashMap.KeySetView definitionDrafts = + ConcurrentHashMap.newKeySet(); + final AttributeDefinitionDraft existingReferencingAttr = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missing1")), + "attr-name-1", + ofEnglish("label"), + true) .build(); - definitionDrafts.add(existingReferencingAttr); - - - productTypesReferencingMissing1.put("referencing-product-type-1", definitionDrafts); - productTypesReferencingMissing1.put("referencing-product-type-2", ConcurrentHashMap.newKeySet()); - - final ConcurrentHashMap> - productTypesReferencingMissing2 = new ConcurrentHashMap<>(); - productTypesReferencingMissing2.put("referencing-product-type-3", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing2.put("referencing-product-type-4", ConcurrentHashMap.newKeySet()); - - missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); - missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - final AttributeDefinitionDraft referencingAttributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missing1")), "attr-name", ofEnglish("label"), true) + definitionDrafts.add(existingReferencingAttr); + + productTypesReferencingMissing1.put("referencing-product-type-1", definitionDrafts); + productTypesReferencingMissing1.put( + "referencing-product-type-2", ConcurrentHashMap.newKeySet()); + + final ConcurrentHashMap> + productTypesReferencingMissing2 = new ConcurrentHashMap<>(); + productTypesReferencingMissing2.put( + "referencing-product-type-3", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing2.put( + "referencing-product-type-4", ConcurrentHashMap.newKeySet()); + + missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); + missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + final AttributeDefinitionDraft referencingAttributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missing1")), + "attr-name", + ofEnglish("label"), + true) .build(); - - // test - productTypeSyncStatistics.putMissingNestedProductType("missing1", - "referencing-product-type-1", - referencingAttributeDefinitionDraft); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(2); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing1")).hasSize(2); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents() - .get("missing1") - .get("referencing-product-type-1")) - .containsExactlyInAnyOrder(existingReferencingAttr, referencingAttributeDefinitionDraft); - } - - @Test - void putMissingNestedProductType_WithExistingReferencingPTypeToAnotherMissingRef_ShouldAppendAttribute() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - final ConcurrentHashMap.KeySetView definitionDrafts = - ConcurrentHashMap.newKeySet(); - final AttributeDefinitionDraft existingReferencingAttr = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missing1")), "attr-name-1", ofEnglish("label"), true) + // test + productTypeSyncStatistics.putMissingNestedProductType( + "missing1", "referencing-product-type-1", referencingAttributeDefinitionDraft); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(2); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing1")) + .hasSize(2); + assertThat( + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get("missing1") + .get("referencing-product-type-1")) + .containsExactlyInAnyOrder(existingReferencingAttr, referencingAttributeDefinitionDraft); + } + + @Test + void + putMissingNestedProductType_WithExistingReferencingPTypeToAnotherMissingRef_ShouldAppendAttribute() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + final ConcurrentHashMap.KeySetView definitionDrafts = + ConcurrentHashMap.newKeySet(); + final AttributeDefinitionDraft existingReferencingAttr = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missing1")), + "attr-name-1", + ofEnglish("label"), + true) .build(); - definitionDrafts.add(existingReferencingAttr); - - - productTypesReferencingMissing1.put("referencing-product-type-1", definitionDrafts); - productTypesReferencingMissing1.put("referencing-product-type-2", ConcurrentHashMap.newKeySet()); - - final ConcurrentHashMap> - productTypesReferencingMissing2 = new ConcurrentHashMap<>(); - productTypesReferencingMissing2.put("referencing-product-type-3", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing2.put("referencing-product-type-4", ConcurrentHashMap.newKeySet()); - - missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); - missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - final AttributeDefinitionDraft referencingAttributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missing1")), "attr-name", ofEnglish("label"), true) + definitionDrafts.add(existingReferencingAttr); + + productTypesReferencingMissing1.put("referencing-product-type-1", definitionDrafts); + productTypesReferencingMissing1.put( + "referencing-product-type-2", ConcurrentHashMap.newKeySet()); + + final ConcurrentHashMap> + productTypesReferencingMissing2 = new ConcurrentHashMap<>(); + productTypesReferencingMissing2.put( + "referencing-product-type-3", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing2.put( + "referencing-product-type-4", ConcurrentHashMap.newKeySet()); + + missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); + missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + final AttributeDefinitionDraft referencingAttributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missing1")), + "attr-name", + ofEnglish("label"), + true) .build(); - - // test - productTypeSyncStatistics.putMissingNestedProductType("missing3", - "referencing-product-type-1", - referencingAttributeDefinitionDraft); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(3); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing3")).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents() - .get("missing3") - .get("referencing-product-type-1")) - .containsExactly(referencingAttributeDefinitionDraft); - } - - @Test - void removeReferencingProductTypeKey_WithEmptyMap_ShouldRemoveNothing() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - // test - productTypeSyncStatistics.removeReferencingProductTypeKey("foo"); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).isEmpty(); - } - - @Test - void removeReferencingProductTypeKey_WithOneOccurrence_ShouldRemoveOccurrence() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - final ConcurrentHashMap.KeySetView definitionDrafts = - ConcurrentHashMap.newKeySet(); - final AttributeDefinitionDraft existingReferencingAttr = AttributeDefinitionDraftBuilder - .of(NestedAttributeType.of( - ProductType.referenceOfId("missing1")), "attr-name-1", ofEnglish("label"), true) + // test + productTypeSyncStatistics.putMissingNestedProductType( + "missing3", "referencing-product-type-1", referencingAttributeDefinitionDraft); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(3); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing3")) + .hasSize(1); + assertThat( + productTypeSyncStatistics + .getProductTypeKeysWithMissingParents() + .get("missing3") + .get("referencing-product-type-1")) + .containsExactly(referencingAttributeDefinitionDraft); + } + + @Test + void removeReferencingProductTypeKey_WithEmptyMap_ShouldRemoveNothing() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test + productTypeSyncStatistics.removeReferencingProductTypeKey("foo"); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).isEmpty(); + } + + @Test + void removeReferencingProductTypeKey_WithOneOccurrence_ShouldRemoveOccurrence() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + final ConcurrentHashMap.KeySetView definitionDrafts = + ConcurrentHashMap.newKeySet(); + final AttributeDefinitionDraft existingReferencingAttr = + AttributeDefinitionDraftBuilder.of( + NestedAttributeType.of(ProductType.referenceOfId("missing1")), + "attr-name-1", + ofEnglish("label"), + true) .build(); - definitionDrafts.add(existingReferencingAttr); - - - productTypesReferencingMissing1.put("referencing-product-type-1", definitionDrafts); - productTypesReferencingMissing1.put("referencing-product-type-2", ConcurrentHashMap.newKeySet()); - - final ConcurrentHashMap> - productTypesReferencingMissing2 = new ConcurrentHashMap<>(); - productTypesReferencingMissing2.put("referencing-product-type-3", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing2.put("referencing-product-type-4", ConcurrentHashMap.newKeySet()); - - missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); - missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - // test - productTypeSyncStatistics.removeReferencingProductTypeKey("referencing-product-type-1"); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(2); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing1")) - .containsOnlyKeys("referencing-product-type-2"); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing2")) - .containsOnlyKeys("referencing-product-type-3", "referencing-product-type-4"); - } - - @Test - void removeReferencingProductTypeKey_WithMultipleOccurrence_ShouldRemoveAllOccurrences() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - - productTypesReferencingMissing1.put("referencing-product-type-1", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing1.put("referencing-product-type-2", ConcurrentHashMap.newKeySet()); - - final ConcurrentHashMap> - productTypesReferencingMissing2 = new ConcurrentHashMap<>(); - productTypesReferencingMissing2.put("referencing-product-type-1", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing2.put("referencing-product-type-4", ConcurrentHashMap.newKeySet()); - - missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); - missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - // test - productTypeSyncStatistics.removeReferencingProductTypeKey("referencing-product-type-1"); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(2); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing1")) - .containsOnlyKeys("referencing-product-type-2"); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing2")) - .containsOnlyKeys("referencing-product-type-4"); - } - - @Test - void removeReferencingProductTypeKey_WithOnlyOccurrenceForMissingRef_ShouldRemoveMissingRef() { - // preparation - final ConcurrentHashMap>> - missingProductTypeReferences = new ConcurrentHashMap<>(); - - final ConcurrentHashMap> - productTypesReferencingMissing1 = new ConcurrentHashMap<>(); - - productTypesReferencingMissing1.put("referencing-product-type-1", ConcurrentHashMap.newKeySet()); - - final ConcurrentHashMap> - productTypesReferencingMissing2 = new ConcurrentHashMap<>(); - productTypesReferencingMissing2.put("referencing-product-type-1", ConcurrentHashMap.newKeySet()); - productTypesReferencingMissing2.put("referencing-product-type-4", ConcurrentHashMap.newKeySet()); - - missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); - missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); - - final ProductTypeSyncStatistics productTypeSyncStatistics = - new ProductTypeSyncStatistics(missingProductTypeReferences); - - // test - productTypeSyncStatistics.removeReferencingProductTypeKey("referencing-product-type-1"); - - // assertion - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); - assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing2")) - .containsOnlyKeys("referencing-product-type-4"); - } + definitionDrafts.add(existingReferencingAttr); + + productTypesReferencingMissing1.put("referencing-product-type-1", definitionDrafts); + productTypesReferencingMissing1.put( + "referencing-product-type-2", ConcurrentHashMap.newKeySet()); + + final ConcurrentHashMap> + productTypesReferencingMissing2 = new ConcurrentHashMap<>(); + productTypesReferencingMissing2.put( + "referencing-product-type-3", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing2.put( + "referencing-product-type-4", ConcurrentHashMap.newKeySet()); + + missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); + missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test + productTypeSyncStatistics.removeReferencingProductTypeKey("referencing-product-type-1"); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(2); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing1")) + .containsOnlyKeys("referencing-product-type-2"); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing2")) + .containsOnlyKeys("referencing-product-type-3", "referencing-product-type-4"); + } + + @Test + void removeReferencingProductTypeKey_WithMultipleOccurrence_ShouldRemoveAllOccurrences() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + + productTypesReferencingMissing1.put( + "referencing-product-type-1", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing1.put( + "referencing-product-type-2", ConcurrentHashMap.newKeySet()); + + final ConcurrentHashMap> + productTypesReferencingMissing2 = new ConcurrentHashMap<>(); + productTypesReferencingMissing2.put( + "referencing-product-type-1", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing2.put( + "referencing-product-type-4", ConcurrentHashMap.newKeySet()); + + missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); + missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test + productTypeSyncStatistics.removeReferencingProductTypeKey("referencing-product-type-1"); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(2); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing1")) + .containsOnlyKeys("referencing-product-type-2"); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing2")) + .containsOnlyKeys("referencing-product-type-4"); + } + + @Test + void removeReferencingProductTypeKey_WithOnlyOccurrenceForMissingRef_ShouldRemoveMissingRef() { + // preparation + final ConcurrentHashMap< + String, + ConcurrentHashMap< + String, ConcurrentHashMap.KeySetView>> + missingProductTypeReferences = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> + productTypesReferencingMissing1 = new ConcurrentHashMap<>(); + + productTypesReferencingMissing1.put( + "referencing-product-type-1", ConcurrentHashMap.newKeySet()); + + final ConcurrentHashMap> + productTypesReferencingMissing2 = new ConcurrentHashMap<>(); + productTypesReferencingMissing2.put( + "referencing-product-type-1", ConcurrentHashMap.newKeySet()); + productTypesReferencingMissing2.put( + "referencing-product-type-4", ConcurrentHashMap.newKeySet()); + + missingProductTypeReferences.put("missing1", productTypesReferencingMissing1); + missingProductTypeReferences.put("missing2", productTypesReferencingMissing2); + + final ProductTypeSyncStatistics productTypeSyncStatistics = + new ProductTypeSyncStatistics(missingProductTypeReferences); + + // test + productTypeSyncStatistics.removeReferencingProductTypeKey("referencing-product-type-1"); + + // assertion + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents()).hasSize(1); + assertThat(productTypeSyncStatistics.getProductTypeKeysWithMissingParents().get("missing2")) + .containsOnlyKeys("referencing-product-type-4"); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionUpdateActionUtilsTest.java index 8aee02818b..5908cdd49e 100644 --- a/src/test/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionUpdateActionUtilsTest.java @@ -1,5 +1,17 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildActions; +import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildChangeAttributeConstraintUpdateAction; +import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildChangeInputHintUpdateAction; +import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildChangeIsSearchableUpdateAction; +import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildChangeLabelUpdateAction; +import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildEnumUpdateActions; +import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildSetInputTipUpdateAction; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.EnumValue; import io.sphere.sdk.models.LocalizedEnumValue; @@ -26,42 +38,30 @@ import io.sphere.sdk.producttypes.commands.updateactions.ChangePlainEnumValueLabel; import io.sphere.sdk.producttypes.commands.updateactions.RemoveEnumValues; import io.sphere.sdk.producttypes.commands.updateactions.SetInputTip; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildActions; -import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildChangeAttributeConstraintUpdateAction; -import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildChangeInputHintUpdateAction; -import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildChangeIsSearchableUpdateAction; -import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildChangeLabelUpdateAction; -import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildEnumUpdateActions; -import static com.commercetools.sync.producttypes.utils.AttributeDefinitionUpdateActionUtils.buildSetInputTipUpdateAction; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; class AttributeDefinitionUpdateActionUtilsTest { - private static AttributeDefinition old; - private static AttributeDefinitionDraft newSame; - private static AttributeDefinitionDraft newDifferent; - - private static final EnumValue ENUM_VALUE_A = EnumValue.of("a", "label_a"); - private static final EnumValue ENUM_VALUE_B = EnumValue.of("b", "label_b"); - - private static final LocalizedEnumValue LOCALIZED_ENUM_VALUE_A = LocalizedEnumValue.of("a", ofEnglish("label_a")); - private static final LocalizedEnumValue LOCALIZED_ENUM_VALUE_B = LocalizedEnumValue.of("b", ofEnglish("label_b")); - - /** - * Initialises test data. - */ - @BeforeAll - static void setup() { - old = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), StringAttributeType.of()) + private static AttributeDefinition old; + private static AttributeDefinitionDraft newSame; + private static AttributeDefinitionDraft newDifferent; + + private static final EnumValue ENUM_VALUE_A = EnumValue.of("a", "label_a"); + private static final EnumValue ENUM_VALUE_B = EnumValue.of("b", "label_b"); + + private static final LocalizedEnumValue LOCALIZED_ENUM_VALUE_A = + LocalizedEnumValue.of("a", ofEnglish("label_a")); + private static final LocalizedEnumValue LOCALIZED_ENUM_VALUE_B = + LocalizedEnumValue.of("b", ofEnglish("label_b")); + + /** Initialises test data. */ + @BeforeAll + static void setup() { + old = + AttributeDefinitionBuilder.of( + "attributeName1", ofEnglish("label1"), StringAttributeType.of()) .isRequired(false) .attributeConstraint(AttributeConstraint.NONE) .inputTip(ofEnglish("inputTip1")) @@ -69,755 +69,799 @@ static void setup() { .isSearchable(false) .build(); - newSame = AttributeDefinitionDraftBuilder - .of(old) - .build(); + newSame = AttributeDefinitionDraftBuilder.of(old).build(); - newDifferent = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "attributeName1", ofEnglish("label2"), true) + newDifferent = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), "attributeName1", ofEnglish("label2"), true) .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) .inputTip(ofEnglish("inputTip2")) .inputHint(TextInputHint.MULTI_LINE) .isSearchable(true) .build(); - } + } - @Test - void buildChangeLabelAction_WithDifferentValues_ShouldReturnAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) - .build(); + @Test + void buildChangeLabelAction_WithDifferentValues_ShouldReturnAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null).build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("y"), null) - .build(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("y"), null).build(); - // test - final Optional> result = buildChangeLabelUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeLabelUpdateAction(attributeDefinition, draft); - //assertion - assertThat(result).contains(ChangeAttributeDefinitionLabel.of(attributeDefinition.getName(), ofEnglish("x"))); - } + // assertion + assertThat(result) + .contains(ChangeAttributeDefinitionLabel.of(attributeDefinition.getName(), ofEnglish("x"))); + } - @Test - void buildChangeLabelAction_WithSameValues_ShouldReturnEmptyOptional() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) - .build(); + @Test + void buildChangeLabelAction_WithSameValues_ShouldReturnEmptyOptional() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null).build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) - .build(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null).build(); - // test - final Optional> result = buildChangeLabelUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeLabelUpdateAction(attributeDefinition, draft); - //assertion - assertThat(result).isEmpty(); - } + // assertion + assertThat(result).isEmpty(); + } - @Test - void buildSetInputTipAction_WithDifferentValues_ShouldReturnAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildSetInputTipAction_WithDifferentValues_ShouldReturnAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .inputTip(ofEnglish("foo")) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .inputTip(ofEnglish("bar")) .build(); - // test - final Optional> result = buildSetInputTipUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildSetInputTipUpdateAction(attributeDefinition, draft); - //assertion - assertThat(result).contains(SetInputTip.of(attributeDefinition.getName(), ofEnglish("foo"))); - } + // assertion + assertThat(result).contains(SetInputTip.of(attributeDefinition.getName(), ofEnglish("foo"))); + } - @Test - void buildSetInputTipAction_WithSameValues_ShouldReturnEmptyOptional() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildSetInputTipAction_WithSameValues_ShouldReturnEmptyOptional() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .inputTip(ofEnglish("foo")) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .inputTip(ofEnglish("foo")) .build(); - // test - final Optional> result = buildSetInputTipUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildSetInputTipUpdateAction(attributeDefinition, draft); - //assertion - assertThat(result).isEmpty(); - } + // assertion + assertThat(result).isEmpty(); + } - @Test - void buildSetInputTipAction_WithSourceNullValues_ShouldReturnAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildSetInputTipAction_WithSourceNullValues_ShouldReturnAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .inputTip(null) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .inputTip(ofEnglish("foo")) .build(); - // test - final Optional> result = buildSetInputTipUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildSetInputTipUpdateAction(attributeDefinition, draft); - //assertion - assertThat(result).contains(SetInputTip.of(attributeDefinition.getName(), null)); - } + // assertion + assertThat(result).contains(SetInputTip.of(attributeDefinition.getName(), null)); + } - @Test - void buildSetInputTipAction_WithTargetNullValues_ShouldReturnAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildSetInputTipAction_WithTargetNullValues_ShouldReturnAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .inputTip(ofEnglish("foo")) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) - .inputTip(null) - .build(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null).inputTip(null).build(); - // test - final Optional> result = buildSetInputTipUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildSetInputTipUpdateAction(attributeDefinition, draft); - //assertion - assertThat(result).contains(SetInputTip.of(attributeDefinition.getName(), ofEnglish("foo"))); - } + // assertion + assertThat(result).contains(SetInputTip.of(attributeDefinition.getName(), ofEnglish("foo"))); + } - @Test - void buildChangeIsSearchableAction_WithDifferentValues_ShouldReturnAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeIsSearchableAction_WithDifferentValues_ShouldReturnAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .isSearchable(true) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) - .isSearchable(false) - .build(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null).isSearchable(false).build(); - // test - final Optional> result = - buildChangeIsSearchableUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeIsSearchableUpdateAction(attributeDefinition, draft); - assertThat(result).contains(ChangeIsSearchable.of(attributeDefinition.getName(), true)); - } + assertThat(result).contains(ChangeIsSearchable.of(attributeDefinition.getName(), true)); + } - @Test - void buildChangeIsSearchableAction_WithSameValues_ShouldReturnEmptyOptional() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeIsSearchableAction_WithSameValues_ShouldReturnEmptyOptional() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .isSearchable(true) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) - .isSearchable(true) - .build(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null).isSearchable(true).build(); - // test - final Optional> result = - buildChangeIsSearchableUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeIsSearchableUpdateAction(attributeDefinition, draft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildChangeIsSearchableAction_WithNullSourceAndNonDefaultTarget_ShouldBuildAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeIsSearchableAction_WithNullSourceAndNonDefaultTarget_ShouldBuildAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .isSearchable(null) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) - .isSearchable(false) - .build(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null).isSearchable(false).build(); - // test - final Optional> result = - buildChangeIsSearchableUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeIsSearchableUpdateAction(attributeDefinition, draft); - assertThat(result).contains(ChangeIsSearchable.of("foo", true)); - } + assertThat(result).contains(ChangeIsSearchable.of("foo", true)); + } - @Test - void buildChangeIsSearchableAction_WithNullSourceAndDefaultTarget_ShouldNotBuildAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeIsSearchableAction_WithNullSourceAndDefaultTarget_ShouldNotBuildAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .isSearchable(null) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) - .isSearchable(true) - .build(); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null).isSearchable(true).build(); - // test - final Optional> result = - buildChangeIsSearchableUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeIsSearchableUpdateAction(attributeDefinition, draft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildChangeInputHintAction_WithDifferentValues_ShouldReturnAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeInputHintAction_WithDifferentValues_ShouldReturnAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .inputHint(TextInputHint.MULTI_LINE) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .inputHint(TextInputHint.SINGLE_LINE) .build(); - // test - final Optional> result = buildChangeInputHintUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeInputHintUpdateAction(attributeDefinition, draft); - assertThat(result).contains(ChangeInputHint.of(attributeDefinition.getName(), TextInputHint.MULTI_LINE)); - } + assertThat(result) + .contains(ChangeInputHint.of(attributeDefinition.getName(), TextInputHint.MULTI_LINE)); + } - @Test - void buildChangeInputHintAction_WithSameValues_ShouldReturnEmptyOptional() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeInputHintAction_WithSameValues_ShouldReturnEmptyOptional() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .inputHint(TextInputHint.MULTI_LINE) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .inputHint(TextInputHint.MULTI_LINE) .build(); - // test - final Optional> result = buildChangeInputHintUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeInputHintUpdateAction(attributeDefinition, draft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildChangeInputHintAction_WithSourceNullValuesAndNonDefaultTargetValue_ShouldBuildAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeInputHintAction_WithSourceNullValuesAndNonDefaultTargetValue_ShouldBuildAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .inputHint(null) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .inputHint(TextInputHint.MULTI_LINE) .build(); - // test - final Optional> result = buildChangeInputHintUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeInputHintUpdateAction(attributeDefinition, draft); - assertThat(result).contains(ChangeInputHint.of(attributeDefinition.getName(), TextInputHint.SINGLE_LINE)); - } + assertThat(result) + .contains(ChangeInputHint.of(attributeDefinition.getName(), TextInputHint.SINGLE_LINE)); + } - @Test - void buildChangeInputHintAction_WithSourceNullValuesAndDefaultTargetValue_ShouldNotBuildAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeInputHintAction_WithSourceNullValuesAndDefaultTargetValue_ShouldNotBuildAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .inputHint(null) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .inputHint(TextInputHint.SINGLE_LINE) .build(); - // test - final Optional> result = buildChangeInputHintUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeInputHintUpdateAction(attributeDefinition, draft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildChangeAttributeConstraintAction_WithDifferentValues_ShouldBuildAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeAttributeConstraintAction_WithDifferentValues_ShouldBuildAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .attributeConstraint(AttributeConstraint.COMBINATION_UNIQUE) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) .build(); - // test - final Optional> result = - buildChangeAttributeConstraintUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeAttributeConstraintUpdateAction(attributeDefinition, draft); - assertThat(result).contains( - ChangeAttributeConstraint.of(attributeDefinition.getName(), AttributeConstraint.COMBINATION_UNIQUE)); - } + assertThat(result) + .contains( + ChangeAttributeConstraint.of( + attributeDefinition.getName(), AttributeConstraint.COMBINATION_UNIQUE)); + } - @Test - void buildChangeAttributeConstraintAction_WithSameValues_ShouldReturnEmptyOptional() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void buildChangeAttributeConstraintAction_WithSameValues_ShouldReturnEmptyOptional() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .attributeConstraint(AttributeConstraint.COMBINATION_UNIQUE) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .attributeConstraint(AttributeConstraint.COMBINATION_UNIQUE) .build(); - // test - final Optional> result = - buildChangeAttributeConstraintUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeAttributeConstraintUpdateAction(attributeDefinition, draft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildChangeAttributeConstraintAction_WithSourceNullValuesAndDefaultTarget_ShouldNotBuildAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void + buildChangeAttributeConstraintAction_WithSourceNullValuesAndDefaultTarget_ShouldNotBuildAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .attributeConstraint(null) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .attributeConstraint(AttributeConstraint.NONE) .build(); - // test - final Optional> result = - buildChangeAttributeConstraintUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeAttributeConstraintUpdateAction(attributeDefinition, draft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildChangeAttributeConstraintAction_WithSourceNullValuesAndNonDefaultTarget_ShouldBuildAction() { - // Preparation - final AttributeDefinitionDraft draft = AttributeDefinitionDraftBuilder - .of(null, "foo", ofEnglish("x"), null) + @Test + void + buildChangeAttributeConstraintAction_WithSourceNullValuesAndNonDefaultTarget_ShouldBuildAction() { + // Preparation + final AttributeDefinitionDraft draft = + AttributeDefinitionDraftBuilder.of(null, "foo", ofEnglish("x"), null) .attributeConstraint(null) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("foo", ofEnglish("x"), null) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("foo", ofEnglish("x"), null) .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) .build(); - // test - final Optional> result = - buildChangeAttributeConstraintUpdateAction(attributeDefinition, draft); + // test + final Optional> result = + buildChangeAttributeConstraintUpdateAction(attributeDefinition, draft); - assertThat(result).contains(ChangeAttributeConstraint.of(draft.getName(), AttributeConstraint.NONE)); - } + assertThat(result) + .contains(ChangeAttributeConstraint.of(draft.getName(), AttributeConstraint.NONE)); + } - @Test - void buildActions_WithNullOptionalsAndDefaultValues_ShouldBuildNoActions() { - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "attributeName1", ofEnglish("label2"), true) + @Test + void buildActions_WithNullOptionalsAndDefaultValues_ShouldBuildNoActions() { + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), "attributeName1", ofEnglish("label2"), true) .attributeConstraint(null) .inputHint(null) .isSearchable(null) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label2"), StringAttributeType.of()) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", ofEnglish("label2"), StringAttributeType.of()) .isRequired(true) .build(); + final List> result = + buildActions(attributeDefinition, attributeDefinitionDraft); - final List> result = buildActions(attributeDefinition, attributeDefinitionDraft); + assertThat(result).isEmpty(); + } - assertThat(result).isEmpty(); - } - - @Test - void buildActions_WithNonDefaultValuesForOptionalFields_ShouldBuildActions() { - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "attributeName1", ofEnglish("label2"), true) + @Test + void buildActions_WithNonDefaultValuesForOptionalFields_ShouldBuildActions() { + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), "attributeName1", ofEnglish("label2"), true) .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) .inputHint(TextInputHint.MULTI_LINE) .isSearchable(false) .build(); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label2"), StringAttributeType.of()) + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", ofEnglish("label2"), StringAttributeType.of()) .isRequired(true) .build(); + final List> result = + buildActions(attributeDefinition, attributeDefinitionDraft); - final List> result = buildActions(attributeDefinition, attributeDefinitionDraft); - - assertThat(result).containsExactlyInAnyOrder( + assertThat(result) + .containsExactlyInAnyOrder( ChangeAttributeConstraint.of("attributeName1", AttributeConstraint.SAME_FOR_ALL), ChangeInputHint.of("attributeName1", TextInputHint.MULTI_LINE), - ChangeIsSearchable.of("attributeName1", false) - ); - } + ChangeIsSearchable.of("attributeName1", false)); + } - @Test - void buildActions_WithNewDifferentValues_ShouldReturnActions() { - final List> result = buildActions(old, newDifferent); + @Test + void buildActions_WithNewDifferentValues_ShouldReturnActions() { + final List> result = buildActions(old, newDifferent); - assertThat(result).containsExactlyInAnyOrder( + assertThat(result) + .containsExactlyInAnyOrder( ChangeAttributeDefinitionLabel.of(old.getName(), newDifferent.getLabel()), SetInputTip.of(old.getName(), newDifferent.getInputTip()), ChangeAttributeConstraint.of(old.getName(), newDifferent.getAttributeConstraint()), ChangeInputHint.of(old.getName(), newDifferent.getInputHint()), - ChangeIsSearchable.of(old.getName(), newDifferent.isSearchable()) - ); - } + ChangeIsSearchable.of(old.getName(), newDifferent.isSearchable())); + } - @Test - void buildActions_WithSameValues_ShouldReturnEmpty() { - final List> result = buildActions(old, newSame); + @Test + void buildActions_WithSameValues_ShouldReturnEmpty() { + final List> result = buildActions(old, newSame); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildActions_WithStringAttributeTypesWithLabelChanges_ShouldBuildChangeLabelAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), StringAttributeType.of()) + @Test + void buildActions_WithStringAttributeTypesWithLabelChanges_ShouldBuildChangeLabelAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", ofEnglish("label1"), StringAttributeType.of()) .build(); - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "attributeName1", ofEnglish("label2"), false) + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), "attributeName1", ofEnglish("label2"), false) .build(); - final List> result = - buildActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result).containsExactly( - ChangeAttributeDefinitionLabel.of("attributeName1", attributeDefinitionDraft.getLabel())); - } + assertThat(result) + .containsExactly( + ChangeAttributeDefinitionLabel.of( + "attributeName1", attributeDefinitionDraft.getLabel())); + } - @Test - void buildActions_WithSetOfStringAttributeTypesWithDefinitionLabelChanges_ShouldBuildChangeLabelAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), SetAttributeType.of(StringAttributeType.of())) + @Test + void + buildActions_WithSetOfStringAttributeTypesWithDefinitionLabelChanges_ShouldBuildChangeLabelAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", + ofEnglish("label1"), + SetAttributeType.of(StringAttributeType.of())) .build(); - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(StringAttributeType.of()), "attributeName1", ofEnglish("label2"), false) + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(StringAttributeType.of()), + "attributeName1", + ofEnglish("label2"), + false) .build(); - final List> result = - buildActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result).containsExactly( - ChangeAttributeDefinitionLabel.of("attributeName1", attributeDefinitionDraft.getLabel())); - } + assertThat(result) + .containsExactly( + ChangeAttributeDefinitionLabel.of( + "attributeName1", attributeDefinitionDraft.getLabel())); + } - @Test - void buildActions_WithSetOfSetOfStringAttributeTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), + @Test + void + buildActions_WithSetOfSetOfStringAttributeTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", + ofEnglish("label1"), SetAttributeType.of(SetAttributeType.of(StringAttributeType.of()))) .build(); - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(SetAttributeType.of(StringAttributeType.of())), - "attributeName1", ofEnglish("label2"), false) + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(SetAttributeType.of(StringAttributeType.of())), + "attributeName1", + ofEnglish("label2"), + false) .build(); - final List> result = - buildActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result).containsExactly( - ChangeAttributeDefinitionLabel.of("attributeName1", attributeDefinitionDraft.getLabel())); - } + assertThat(result) + .containsExactly( + ChangeAttributeDefinitionLabel.of( + "attributeName1", attributeDefinitionDraft.getLabel())); + } - @Test - void buildActions_WithSameSetOfEnumsAttributeTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), SetAttributeType.of( - EnumAttributeType.of(emptyList()))) + @Test + void + buildActions_WithSameSetOfEnumsAttributeTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", + ofEnglish("label1"), + SetAttributeType.of(EnumAttributeType.of(emptyList()))) .build(); - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of( - EnumAttributeType.of(emptyList())), "attributeName1", ofEnglish("label2"), false) + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(EnumAttributeType.of(emptyList())), + "attributeName1", + ofEnglish("label2"), + false) .build(); - final List> result = - buildActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result).containsExactly( - ChangeAttributeDefinitionLabel.of("attributeName1", attributeDefinitionDraft.getLabel())); - } + assertThat(result) + .containsExactly( + ChangeAttributeDefinitionLabel.of( + "attributeName1", attributeDefinitionDraft.getLabel())); + } - @Test - void buildActions_WithChangedSetOfEnumAttributeTypes_ShouldBuildEnumActions() { - // preparation - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), SetAttributeType.of( - EnumAttributeType.of( - asList( - EnumValue.of("c", "c"), - ENUM_VALUE_B, - EnumValue.of("d", "d") - ) - ) - )) - .build(); - - final AttributeDefinitionDraft newDraft = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of( - EnumAttributeType.of( - asList( - ENUM_VALUE_A, - EnumValue.of(ENUM_VALUE_B.getKey(), "newLabel"), - EnumValue.of("c", "c") - ) - ) - ), "attributeName1", - ofEnglish("label1"), false) + @Test + void buildActions_WithChangedSetOfEnumAttributeTypes_ShouldBuildEnumActions() { + // preparation + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", + ofEnglish("label1"), + SetAttributeType.of( + EnumAttributeType.of( + asList(EnumValue.of("c", "c"), ENUM_VALUE_B, EnumValue.of("d", "d"))))) + .build(); + + final AttributeDefinitionDraft newDraft = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of( + EnumAttributeType.of( + asList( + ENUM_VALUE_A, + EnumValue.of(ENUM_VALUE_B.getKey(), "newLabel"), + EnumValue.of("c", "c")))), + "attributeName1", + ofEnglish("label1"), + false) .build(); - // test - final List> result = - buildActions(oldDefinition, newDraft); + // test + final List> result = buildActions(oldDefinition, newDraft); - // assertion - assertThat(result).containsExactly( + // assertion + assertThat(result) + .containsExactly( RemoveEnumValues.of(oldDefinition.getName(), "d"), ChangePlainEnumValueLabel.of( oldDefinition.getName(), EnumValue.of(ENUM_VALUE_B.getKey(), "newLabel")), AddEnumValue.of(oldDefinition.getName(), ENUM_VALUE_A), - ChangeEnumValueOrder.of(oldDefinition.getName(), asList( - ENUM_VALUE_A, - EnumValue.of(ENUM_VALUE_B.getKey(), "newLabel"), - EnumValue.of("c", "c") - )) - ); - } - - @Test - void buildActions_WithSameSetOfLEnumAttributeTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), SetAttributeType.of( - LocalizedEnumAttributeType.of(emptyList()))) - .build(); - - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of( - LocalizedEnumAttributeType.of(emptyList())), "attributeName1", ofEnglish("label2"), false) - .build(); - - final List> result = - buildActions(attributeDefinition, attributeDefinitionDraft); - - assertThat(result).containsExactly( - ChangeAttributeDefinitionLabel.of("attributeName1", attributeDefinitionDraft.getLabel())); - } - - @Test - void buildActions_WithChangedSetOfLocalizedEnumAttributeTypes_ShouldBuildEnumActions() { - // preparation - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), SetAttributeType.of( - LocalizedEnumAttributeType.of( - asList( - LocalizedEnumValue.of("c", ofEnglish("c")), - LOCALIZED_ENUM_VALUE_B, - LocalizedEnumValue.of("d", ofEnglish("d")) - )))) - .build(); - - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of( - LocalizedEnumAttributeType.of( - asList( - LOCALIZED_ENUM_VALUE_A, - LocalizedEnumValue.of(LOCALIZED_ENUM_VALUE_B.getKey(), ofEnglish("newLabel")), - LocalizedEnumValue.of("c", ofEnglish("c")) - ))), - "attributeName1", ofEnglish("label1"), false) - .build(); - - // test - final List> result = - buildActions(oldDefinition, newDefinition); - - // assertion - assertThat(result).containsExactly( - RemoveEnumValues.of(oldDefinition.getName(), "d"), - ChangeLocalizedEnumValueLabel.of(oldDefinition.getName(), - LocalizedEnumValue.of(LOCALIZED_ENUM_VALUE_B.getKey(), ofEnglish("newLabel"))), - AddLocalizedEnumValue.of("attributeName1", LOCALIZED_ENUM_VALUE_A), - ChangeLocalizedEnumValueOrder.of(oldDefinition.getName(), asList( - LOCALIZED_ENUM_VALUE_A, - LocalizedEnumValue.of(LOCALIZED_ENUM_VALUE_B.getKey(), ofEnglish("newLabel")), - LocalizedEnumValue.of("c", ofEnglish("c")) - ))); - } + ChangeEnumValueOrder.of( + oldDefinition.getName(), + asList( + ENUM_VALUE_A, + EnumValue.of(ENUM_VALUE_B.getKey(), "newLabel"), + EnumValue.of("c", "c")))); + } + + @Test + void + buildActions_WithSameSetOfLEnumAttributeTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", + ofEnglish("label1"), + SetAttributeType.of(LocalizedEnumAttributeType.of(emptyList()))) + .build(); - @Test - void buildActions_WithNewPlainEnum_ShouldReturnAddEnumValueAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), EnumAttributeType.of(ENUM_VALUE_A)) + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(LocalizedEnumAttributeType.of(emptyList())), + "attributeName1", + ofEnglish("label2"), + false) .build(); + final List> result = + buildActions(attributeDefinition, attributeDefinitionDraft); - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of( - EnumAttributeType.of(ENUM_VALUE_A, ENUM_VALUE_B), + assertThat(result) + .containsExactly( + ChangeAttributeDefinitionLabel.of( + "attributeName1", attributeDefinitionDraft.getLabel())); + } + + @Test + void buildActions_WithChangedSetOfLocalizedEnumAttributeTypes_ShouldBuildEnumActions() { + // preparation + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", + ofEnglish("label1"), + SetAttributeType.of( + LocalizedEnumAttributeType.of( + asList( + LocalizedEnumValue.of("c", ofEnglish("c")), + LOCALIZED_ENUM_VALUE_B, + LocalizedEnumValue.of("d", ofEnglish("d")))))) + .build(); + + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of( + LocalizedEnumAttributeType.of( + asList( + LOCALIZED_ENUM_VALUE_A, + LocalizedEnumValue.of( + LOCALIZED_ENUM_VALUE_B.getKey(), ofEnglish("newLabel")), + LocalizedEnumValue.of("c", ofEnglish("c"))))), "attributeName1", ofEnglish("label1"), - false - ) + false) .build(); + // test + final List> result = buildActions(oldDefinition, newDefinition); + + // assertion + assertThat(result) + .containsExactly( + RemoveEnumValues.of(oldDefinition.getName(), "d"), + ChangeLocalizedEnumValueLabel.of( + oldDefinition.getName(), + LocalizedEnumValue.of(LOCALIZED_ENUM_VALUE_B.getKey(), ofEnglish("newLabel"))), + AddLocalizedEnumValue.of("attributeName1", LOCALIZED_ENUM_VALUE_A), + ChangeLocalizedEnumValueOrder.of( + oldDefinition.getName(), + asList( + LOCALIZED_ENUM_VALUE_A, + LocalizedEnumValue.of(LOCALIZED_ENUM_VALUE_B.getKey(), ofEnglish("newLabel")), + LocalizedEnumValue.of("c", ofEnglish("c"))))); + } + + @Test + void buildActions_WithNewPlainEnum_ShouldReturnAddEnumValueAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", ofEnglish("label1"), EnumAttributeType.of(ENUM_VALUE_A)) + .build(); + + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + EnumAttributeType.of(ENUM_VALUE_A, ENUM_VALUE_B), + "attributeName1", + ofEnglish("label1"), + false) + .build(); - final List> result = - buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result).containsExactly(AddEnumValue.of("attributeName1", ENUM_VALUE_B)); - } + assertThat(result).containsExactly(AddEnumValue.of("attributeName1", ENUM_VALUE_B)); + } - @Test - void buildActions_WithoutOldPlainEnum_ShouldReturnRemoveEnumValueAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), EnumAttributeType.of(ENUM_VALUE_A)) + @Test + void buildActions_WithoutOldPlainEnum_ShouldReturnRemoveEnumValueAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", ofEnglish("label1"), EnumAttributeType.of(ENUM_VALUE_A)) .build(); - - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of( - EnumAttributeType.of(emptyList()), - "attributeName1", - ofEnglish("label1"), - false - ) + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( + EnumAttributeType.of(emptyList()), "attributeName1", ofEnglish("label1"), false) .build(); - final List> result = - buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result).containsExactly(RemoveEnumValues.of("attributeName1", "a")); - } + assertThat(result).containsExactly(RemoveEnumValues.of("attributeName1", "a")); + } - @Test - void buildActions_WitDifferentPlainEnumValueLabel_ShouldReturnChangeEnumValueLabelAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), EnumAttributeType.of(ENUM_VALUE_A)) + @Test + void buildActions_WitDifferentPlainEnumValueLabel_ShouldReturnChangeEnumValueLabelAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", ofEnglish("label1"), EnumAttributeType.of(ENUM_VALUE_A)) .build(); - final EnumValue enumValueDiffLabel = EnumValue.of("a", "label_a_different"); + final EnumValue enumValueDiffLabel = EnumValue.of("a", "label_a_different"); - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of( + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( EnumAttributeType.of(enumValueDiffLabel), "attributeName1", ofEnglish("label1"), - false - ) + false) .build(); - final List> result = - buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result).containsExactly(ChangePlainEnumValueLabel.of("attributeName1", enumValueDiffLabel)); - } + assertThat(result) + .containsExactly(ChangePlainEnumValueLabel.of("attributeName1", enumValueDiffLabel)); + } - @Test - void buildActions_WithNewLocalizedEnum_ShouldReturnAddLocalizedEnumValueAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of( + @Test + void buildActions_WithNewLocalizedEnum_ShouldReturnAddLocalizedEnumValueAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( "attributeName1", ofEnglish("label1"), - LocalizedEnumAttributeType.of(LOCALIZED_ENUM_VALUE_A) - ) + LocalizedEnumAttributeType.of(LOCALIZED_ENUM_VALUE_A)) .build(); - - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of( + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( LocalizedEnumAttributeType.of(LOCALIZED_ENUM_VALUE_A, LOCALIZED_ENUM_VALUE_B), "attributeName1", ofEnglish("label1"), - false - ) + false) .build(); - final List> result = - buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result).containsExactly(AddLocalizedEnumValue.of("attributeName1", LOCALIZED_ENUM_VALUE_B)); - } + assertThat(result) + .containsExactly(AddLocalizedEnumValue.of("attributeName1", LOCALIZED_ENUM_VALUE_B)); + } - @Test - void buildActions_WithoutOldLocalizedEnum_ShouldReturnRemoveLocalizedEnumValueAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of( + @Test + void buildActions_WithoutOldLocalizedEnum_ShouldReturnRemoveLocalizedEnumValueAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( "attributeName1", ofEnglish("label1"), LocalizedEnumAttributeType.of(LOCALIZED_ENUM_VALUE_A)) .build(); - - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of( + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( LocalizedEnumAttributeType.of(emptyList()), "attributeName1", ofEnglish("label1"), - false - ) + false) .build(); - final List> result = - buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result).containsExactly(RemoveEnumValues.of("attributeName1", "a")); - } + assertThat(result).containsExactly(RemoveEnumValues.of("attributeName1", "a")); + } - @Test - void buildActions_WithDifferentLocalizedEnumValueLabel_ShouldReturnChangeLocalizedEnumValueLabelAction() { - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", ofEnglish("label1"), + @Test + void + buildActions_WithDifferentLocalizedEnumValueLabel_ShouldReturnChangeLocalizedEnumValueLabelAction() { + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( + "attributeName1", + ofEnglish("label1"), LocalizedEnumAttributeType.of(LOCALIZED_ENUM_VALUE_A)) .build(); - final LocalizedEnumValue localizedEnumValueDiffLabel = LocalizedEnumValue.of("a", ofEnglish("label_a_diff")); + final LocalizedEnumValue localizedEnumValueDiffLabel = + LocalizedEnumValue.of("a", ofEnglish("label_a_diff")); - final AttributeDefinitionDraft attributeDefinitionDraft = AttributeDefinitionDraftBuilder - .of( + final AttributeDefinitionDraft attributeDefinitionDraft = + AttributeDefinitionDraftBuilder.of( LocalizedEnumAttributeType.of(localizedEnumValueDiffLabel), "attributeName1", ofEnglish("label1"), - false - ) + false) .build(); - final List> result = - buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); + final List> result = + buildEnumUpdateActions(attributeDefinition, attributeDefinitionDraft); - assertThat(result) - .containsExactly(ChangeLocalizedEnumValueLabel.of("attributeName1", localizedEnumValueDiffLabel)); - } + assertThat(result) + .containsExactly( + ChangeLocalizedEnumValueLabel.of("attributeName1", localizedEnumValueDiffLabel)); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/utils/LocalizedEnumValueUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/producttypes/utils/LocalizedEnumValueUpdateActionUtilsTest.java index 47a5468702..aae4d45501 100644 --- a/src/test/java/com/commercetools/sync/producttypes/utils/LocalizedEnumValueUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/utils/LocalizedEnumValueUpdateActionUtilsTest.java @@ -1,41 +1,38 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.producttypes.utils.LocalizedEnumValueUpdateActionUtils.buildChangeLabelAction; +import static org.assertj.core.api.Assertions.assertThat; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedEnumValue; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.commands.updateactions.ChangeLocalizedEnumValueLabel; -import org.junit.jupiter.api.Test; - import java.util.Optional; - -import static com.commercetools.sync.producttypes.utils.LocalizedEnumValueUpdateActionUtils.buildChangeLabelAction; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class LocalizedEnumValueUpdateActionUtilsTest { - private static LocalizedEnumValue old = LocalizedEnumValue.of("key1", LocalizedString.ofEnglish("label1")); - private static LocalizedEnumValue newSame = LocalizedEnumValue.of("key1", LocalizedString.ofEnglish("label1")); - private static LocalizedEnumValue newDifferent = LocalizedEnumValue.of("key1", LocalizedString.ofEnglish("label2")); - - @Test - void buildChangeLabelAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeLabelAction( - "attribute_definition_name_1", - old, - newDifferent - ); - - assertThat(result).contains(ChangeLocalizedEnumValueLabel.of("attribute_definition_name_1", newDifferent)); - } - - @Test - void buildChangeLabelAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeLabelAction( - "attribute_definition_name_1", - old, - newSame - ); - - assertThat(result).isEmpty(); - } + private static LocalizedEnumValue old = + LocalizedEnumValue.of("key1", LocalizedString.ofEnglish("label1")); + private static LocalizedEnumValue newSame = + LocalizedEnumValue.of("key1", LocalizedString.ofEnglish("label1")); + private static LocalizedEnumValue newDifferent = + LocalizedEnumValue.of("key1", LocalizedString.ofEnglish("label2")); + + @Test + void buildChangeLabelAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildChangeLabelAction("attribute_definition_name_1", old, newDifferent); + + assertThat(result) + .contains(ChangeLocalizedEnumValueLabel.of("attribute_definition_name_1", newDifferent)); + } + + @Test + void buildChangeLabelAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = + buildChangeLabelAction("attribute_definition_name_1", old, newSame); + + assertThat(result).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/utils/PlainEnumValueUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/producttypes/utils/PlainEnumValueUpdateActionUtilsTest.java index f6e1b00c4b..b9e8740edb 100644 --- a/src/test/java/com/commercetools/sync/producttypes/utils/PlainEnumValueUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/utils/PlainEnumValueUpdateActionUtilsTest.java @@ -1,52 +1,44 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.producttypes.utils.PlainEnumValueUpdateActionUtils.buildChangeLabelAction; +import static org.assertj.core.api.Assertions.assertThat; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.EnumValue; import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.commands.updateactions.ChangePlainEnumValueLabel; +import java.util.Optional; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.util.Optional; - -import static com.commercetools.sync.producttypes.utils.PlainEnumValueUpdateActionUtils.buildChangeLabelAction; -import static org.assertj.core.api.Assertions.assertThat; - class PlainEnumValueUpdateActionUtilsTest { - private static EnumValue old; - private static EnumValue newSame; - private static EnumValue newDifferent; - - /** - * Initialises test data. - */ - @BeforeAll - static void setup() { - old = EnumValue.of("key1", "label1"); - newSame = EnumValue.of("key1", "label1"); - newDifferent = EnumValue.of("key1", "label2"); - } - - @Test - void buildChangeLabelAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeLabelAction( - "attribute_definition_name_1", - old, - newDifferent - ); - - assertThat(result).containsInstanceOf(ChangePlainEnumValueLabel.class); - assertThat(result).contains(ChangePlainEnumValueLabel.of("attribute_definition_name_1", newDifferent)); - } - - @Test - void buildChangeLabelAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeLabelAction( - "attribute_definition_name_1", - old, - newSame - ); - - assertThat(result).isEmpty(); - } + private static EnumValue old; + private static EnumValue newSame; + private static EnumValue newDifferent; + + /** Initialises test data. */ + @BeforeAll + static void setup() { + old = EnumValue.of("key1", "label1"); + newSame = EnumValue.of("key1", "label1"); + newDifferent = EnumValue.of("key1", "label2"); + } + + @Test + void buildChangeLabelAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildChangeLabelAction("attribute_definition_name_1", old, newDifferent); + + assertThat(result).containsInstanceOf(ChangePlainEnumValueLabel.class); + assertThat(result) + .contains(ChangePlainEnumValueLabel.of("attribute_definition_name_1", newDifferent)); + } + + @Test + void buildChangeLabelAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = + buildChangeLabelAction("attribute_definition_name_1", old, newSame); + + assertThat(result).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtilsTest.java index b7b5af9f7a..f1dc6af743 100644 --- a/src/test/java/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtilsTest.java @@ -1,5 +1,17 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.producttypes.utils.ProductTypeReferenceResolutionUtils.buildProductTypeQuery; +import static com.commercetools.sync.producttypes.utils.ProductTypeReferenceResolutionUtils.mapToProductTypeDrafts; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.ReferenceReplacementException; import io.sphere.sdk.expansion.ExpansionPath; import io.sphere.sdk.models.Reference; @@ -13,344 +25,369 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.ProductTypeDraftBuilder; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; -import org.junit.jupiter.api.Test; - import java.util.List; - -import static com.commercetools.sync.producttypes.utils.ProductTypeReferenceResolutionUtils.buildProductTypeQuery; -import static com.commercetools.sync.producttypes.utils.ProductTypeReferenceResolutionUtils.mapToProductTypeDrafts; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductTypeReferenceResolutionUtilsTest { - @Test - void mapToProductDrafts_WithEmptyList_ShouldReturnEmptyList() { - // preparation - final List productTypes = emptyList(); - - // test - final List productTypeDrafts = mapToProductTypeDrafts(productTypes); + @Test + void mapToProductDrafts_WithEmptyList_ShouldReturnEmptyList() { + // preparation + final List productTypes = emptyList(); - // assertion - assertThat(productTypeDrafts).isEmpty(); - } + // test + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - @Test - void mapToProductDrafts_WithNullProductType_ShouldReturnEmptyList() { - // preparation - final List productTypes = singletonList(null); + // assertion + assertThat(productTypeDrafts).isEmpty(); + } - // test - final List productTypeDrafts = mapToProductTypeDrafts(productTypes); + @Test + void mapToProductDrafts_WithNullProductType_ShouldReturnEmptyList() { + // preparation + final List productTypes = singletonList(null); - // assertion - assertThat(productTypeDrafts).isEmpty(); - } + // test + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - @Test - void mapToProductDrafts_WithProductTypeWithNoAttributeDefs_ShouldReturnProductType() { - // preparation - final ProductType productTypeFoo = mock(ProductType.class); - when(productTypeFoo.getKey()).thenReturn("foo"); - when(productTypeFoo.getAttributes()).thenReturn(emptyList()); + // assertion + assertThat(productTypeDrafts).isEmpty(); + } - final List productTypes = singletonList(productTypeFoo); + @Test + void mapToProductDrafts_WithProductTypeWithNoAttributeDefs_ShouldReturnProductType() { + // preparation + final ProductType productTypeFoo = mock(ProductType.class); + when(productTypeFoo.getKey()).thenReturn("foo"); + when(productTypeFoo.getAttributes()).thenReturn(emptyList()); - // test - final List productTypeDrafts = mapToProductTypeDrafts(productTypes); + final List productTypes = singletonList(productTypeFoo); - // assertion - assertThat(productTypeDrafts).containsExactly(ProductTypeDraftBuilder.of(productTypeFoo).build()); - } + // test + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - @Test - void mapToProductDrafts_WithNoReferences_ShouldReturnCorrectProductTypeDrafts() { - final AttributeDefinition stringAttr = AttributeDefinitionBuilder - .of("a", ofEnglish("a"), StringAttributeType.of()) - .build(); + // assertion + assertThat(productTypeDrafts) + .containsExactly(ProductTypeDraftBuilder.of(productTypeFoo).build()); + } - final AttributeDefinition numberAttr = AttributeDefinitionBuilder - .of("b", ofEnglish("b"), NumberAttributeType.of()) - .build(); + @Test + void mapToProductDrafts_WithNoReferences_ShouldReturnCorrectProductTypeDrafts() { + final AttributeDefinition stringAttr = + AttributeDefinitionBuilder.of("a", ofEnglish("a"), StringAttributeType.of()).build(); + final AttributeDefinition numberAttr = + AttributeDefinitionBuilder.of("b", ofEnglish("b"), NumberAttributeType.of()).build(); - final ProductType productTypeFoo = mock(ProductType.class); - when(productTypeFoo.getKey()).thenReturn("ProductTypeFoo"); - when(productTypeFoo.getAttributes()).thenReturn(singletonList(stringAttr)); + final ProductType productTypeFoo = mock(ProductType.class); + when(productTypeFoo.getKey()).thenReturn("ProductTypeFoo"); + when(productTypeFoo.getAttributes()).thenReturn(singletonList(stringAttr)); - final ProductType productTypeBar = mock(ProductType.class); - when(productTypeBar.getKey()).thenReturn("ProductTypeBar"); - when(productTypeBar.getAttributes()).thenReturn(singletonList(numberAttr)); + final ProductType productTypeBar = mock(ProductType.class); + when(productTypeBar.getKey()).thenReturn("ProductTypeBar"); + when(productTypeBar.getAttributes()).thenReturn(singletonList(numberAttr)); - final List productTypes = asList(productTypeFoo, productTypeBar); + final List productTypes = asList(productTypeFoo, productTypeBar); - // test - final List productTypeDrafts = mapToProductTypeDrafts(productTypes); + // test + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - // assertion - assertThat(productTypeDrafts).containsExactly( + // assertion + assertThat(productTypeDrafts) + .containsExactly( ProductTypeDraftBuilder.of(productTypeFoo).build(), ProductTypeDraftBuilder.of(productTypeBar).build()); - } + } - @Test - void mapToProductDrafts_WithProductTypeWithAnExpandedRefNestedType_ShouldReplaceRef() { - // preparation - final ProductType referencedProductType = mock(ProductType.class); - when(referencedProductType.getKey()).thenReturn("referencedProductType"); + @Test + void mapToProductDrafts_WithProductTypeWithAnExpandedRefNestedType_ShouldReplaceRef() { + // preparation + final ProductType referencedProductType = mock(ProductType.class); + when(referencedProductType.getKey()).thenReturn("referencedProductType"); - final Reference productTypeReference = spy(ProductType.reference(referencedProductType)); + final Reference productTypeReference = + spy(ProductType.reference(referencedProductType)); - final AttributeDefinition nestedTypeAttr = AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productTypeReference)) + final AttributeDefinition nestedTypeAttr = + AttributeDefinitionBuilder.of( + "nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productTypeReference)) .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getKey()).thenReturn("withNestedTypeAttr"); - when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); + final ProductType productType = mock(ProductType.class); + when(productType.getKey()).thenReturn("withNestedTypeAttr"); + when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); - final List productTypes = singletonList(productType); + final List productTypes = singletonList(productType); - // test - final List productTypeDrafts = mapToProductTypeDrafts(productTypes); + // test + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - // assertion - assertThat(productTypeDrafts) - .hasOnlyOneElementSatisfying(productTypeDraft -> { - final NestedAttributeType nestedAttributeType = (NestedAttributeType) - productTypeDraft.getAttributes().get(0).getAttributeType(); - assertThat(nestedAttributeType.getTypeReference().getId()).isEqualTo(referencedProductType.getKey()); + // assertion + assertThat(productTypeDrafts) + .hasOnlyOneElementSatisfying( + productTypeDraft -> { + final NestedAttributeType nestedAttributeType = + (NestedAttributeType) productTypeDraft.getAttributes().get(0).getAttributeType(); + assertThat(nestedAttributeType.getTypeReference().getId()) + .isEqualTo(referencedProductType.getKey()); }); - } + } - @Test - void mapToProductDrafts_WithProductTypeWithNonExpandedRefNestedType_ShouldFail() { - // preparation - final Reference productTypeReference = ProductType.referenceOfId("referencedProductType"); + @Test + void mapToProductDrafts_WithProductTypeWithNonExpandedRefNestedType_ShouldFail() { + // preparation + final Reference productTypeReference = + ProductType.referenceOfId("referencedProductType"); - final AttributeDefinition nestedTypeAttr = AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productTypeReference)) + final AttributeDefinition nestedTypeAttr = + AttributeDefinitionBuilder.of( + "nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productTypeReference)) .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getKey()).thenReturn("withNestedTypeAttr"); - when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); + final ProductType productType = mock(ProductType.class); + when(productType.getKey()).thenReturn("withNestedTypeAttr"); + when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); - final List productTypes = singletonList(productType); + final List productTypes = singletonList(productType); - // test - assertThatThrownBy(() -> mapToProductTypeDrafts(productTypes)) - .isExactlyInstanceOf(ReferenceReplacementException.class) - .hasMessageContaining("Some errors occurred during reference replacement.") - .hasMessageContaining("Failed to replace some references on the productType with key 'withNestedTypeAttr'") - .hasMessageContaining("Failed to replace some references on the attributeDefinition with name 'nestedattr'." + // test + assertThatThrownBy(() -> mapToProductTypeDrafts(productTypes)) + .isExactlyInstanceOf(ReferenceReplacementException.class) + .hasMessageContaining("Some errors occurred during reference replacement.") + .hasMessageContaining( + "Failed to replace some references on the productType with key 'withNestedTypeAttr'") + .hasMessageContaining( + "Failed to replace some references on the attributeDefinition with name 'nestedattr'." + " Cause: ProductType reference is not expanded."); - } - - @Test - void mapToProductDrafts_WithSetOfNestedType_ShouldReplaceRef() { - // preparation - final ProductType referencedProductType = mock(ProductType.class); - when(referencedProductType.getKey()).thenReturn("referencedProductType"); - - final Reference productTypeReference = spy(ProductType.reference(referencedProductType)); - when(productTypeReference.getObj()).thenReturn(referencedProductType); - - final AttributeDefinition nestedTypeAttr = AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), + } + + @Test + void mapToProductDrafts_WithSetOfNestedType_ShouldReplaceRef() { + // preparation + final ProductType referencedProductType = mock(ProductType.class); + when(referencedProductType.getKey()).thenReturn("referencedProductType"); + + final Reference productTypeReference = + spy(ProductType.reference(referencedProductType)); + when(productTypeReference.getObj()).thenReturn(referencedProductType); + + final AttributeDefinition nestedTypeAttr = + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), SetAttributeType.of(NestedAttributeType.of(productTypeReference))) .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getKey()).thenReturn("withNestedTypeAttr"); - when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); - - final List productTypes = singletonList(productType); - - // test - final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - - // assertion - assertThat(productTypeDrafts) - .hasOnlyOneElementSatisfying(productTypeDraft -> { - final SetAttributeType setAttributeType = (SetAttributeType) - productTypeDraft.getAttributes().get(0).getAttributeType(); - final NestedAttributeType nestedAttributeType = (NestedAttributeType) setAttributeType.getElementType(); - assertThat(nestedAttributeType.getTypeReference().getId()).isEqualTo(referencedProductType.getKey()); + final ProductType productType = mock(ProductType.class); + when(productType.getKey()).thenReturn("withNestedTypeAttr"); + when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); + + final List productTypes = singletonList(productType); + + // test + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); + + // assertion + assertThat(productTypeDrafts) + .hasOnlyOneElementSatisfying( + productTypeDraft -> { + final SetAttributeType setAttributeType = + (SetAttributeType) productTypeDraft.getAttributes().get(0).getAttributeType(); + final NestedAttributeType nestedAttributeType = + (NestedAttributeType) setAttributeType.getElementType(); + assertThat(nestedAttributeType.getTypeReference().getId()) + .isEqualTo(referencedProductType.getKey()); }); - } - - @Test - void mapToProductDrafts_WithSetOfNestedTypeNonExpanded_ShouldFail() { - // preparation - final Reference productTypeReference = ProductType.referenceOfId("referencedProductType"); - - final AttributeDefinition nestedTypeAttr = AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), + } + + @Test + void mapToProductDrafts_WithSetOfNestedTypeNonExpanded_ShouldFail() { + // preparation + final Reference productTypeReference = + ProductType.referenceOfId("referencedProductType"); + + final AttributeDefinition nestedTypeAttr = + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), SetAttributeType.of(NestedAttributeType.of(productTypeReference))) .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getKey()).thenReturn("withNestedTypeAttr"); - when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); + final ProductType productType = mock(ProductType.class); + when(productType.getKey()).thenReturn("withNestedTypeAttr"); + when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); - final List productTypes = singletonList(productType); + final List productTypes = singletonList(productType); - // test - assertThatThrownBy(() -> mapToProductTypeDrafts(productTypes)) - .isExactlyInstanceOf(ReferenceReplacementException.class) - .hasMessageContaining("Some errors occurred during reference replacement. Causes:\n") - .hasMessageContaining("\tFailed to replace some references on the productType with key 'withNestedTypeAttr'" + // test + assertThatThrownBy(() -> mapToProductTypeDrafts(productTypes)) + .isExactlyInstanceOf(ReferenceReplacementException.class) + .hasMessageContaining("Some errors occurred during reference replacement. Causes:\n") + .hasMessageContaining( + "\tFailed to replace some references on the productType with key 'withNestedTypeAttr'" + ". Causes:\n") - .hasMessageContaining("\t\tFailed to replace some references on the attributeDefinition with name " + .hasMessageContaining( + "\t\tFailed to replace some references on the attributeDefinition with name " + "'nestedattr'. Cause: ProductType reference is not expanded."); - } - - @Test - void mapToProductDrafts_WithNestedTypeWithSetOfSet_ShouldReplaceRef() { - // preparation - final ProductType referencedProductType = mock(ProductType.class); - when(referencedProductType.getKey()).thenReturn("referencedProductType"); - - final Reference productTypeReference = spy(ProductType.reference(referencedProductType)); - when(productTypeReference.getObj()).thenReturn(referencedProductType); - - final AttributeDefinition nestedTypeAttr = AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), + } + + @Test + void mapToProductDrafts_WithNestedTypeWithSetOfSet_ShouldReplaceRef() { + // preparation + final ProductType referencedProductType = mock(ProductType.class); + when(referencedProductType.getKey()).thenReturn("referencedProductType"); + + final Reference productTypeReference = + spy(ProductType.reference(referencedProductType)); + when(productTypeReference.getObj()).thenReturn(referencedProductType); + + final AttributeDefinition nestedTypeAttr = + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), SetAttributeType.of( - SetAttributeType.of( - NestedAttributeType.of(productTypeReference)))) + SetAttributeType.of(NestedAttributeType.of(productTypeReference)))) .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getKey()).thenReturn("withNestedTypeAttr"); - when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); - - final List productTypes = singletonList(productType); - - // test - final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - - // assertion - assertThat(productTypeDrafts) - .hasOnlyOneElementSatisfying(productTypeDraft -> { - final SetAttributeType setAttributeType = (SetAttributeType) - productTypeDraft.getAttributes().get(0).getAttributeType(); - final SetAttributeType setOfSet = (SetAttributeType) setAttributeType.getElementType(); - final NestedAttributeType nestedAttributeType = (NestedAttributeType) setOfSet.getElementType(); - assertThat(nestedAttributeType.getTypeReference().getId()).isEqualTo(referencedProductType.getKey()); + final ProductType productType = mock(ProductType.class); + when(productType.getKey()).thenReturn("withNestedTypeAttr"); + when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); + + final List productTypes = singletonList(productType); + + // test + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); + + // assertion + assertThat(productTypeDrafts) + .hasOnlyOneElementSatisfying( + productTypeDraft -> { + final SetAttributeType setAttributeType = + (SetAttributeType) productTypeDraft.getAttributes().get(0).getAttributeType(); + final SetAttributeType setOfSet = + (SetAttributeType) setAttributeType.getElementType(); + final NestedAttributeType nestedAttributeType = + (NestedAttributeType) setOfSet.getElementType(); + assertThat(nestedAttributeType.getTypeReference().getId()) + .isEqualTo(referencedProductType.getKey()); }); - } + } - @Test - void mapToProductDrafts_WithProductTypeWithNonExpandedSetOfRefNestedType_ShouldFail() { - // preparation - final Reference productTypeReference = ProductType.referenceOfId("referencedProductType"); + @Test + void mapToProductDrafts_WithProductTypeWithNonExpandedSetOfRefNestedType_ShouldFail() { + // preparation + final Reference productTypeReference = + ProductType.referenceOfId("referencedProductType"); - final AttributeDefinition nestedTypeAttr = AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productTypeReference)) + final AttributeDefinition nestedTypeAttr = + AttributeDefinitionBuilder.of( + "nestedattr", ofEnglish("nestedattr"), NestedAttributeType.of(productTypeReference)) .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getKey()).thenReturn("withNestedTypeAttr"); - when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); + final ProductType productType = mock(ProductType.class); + when(productType.getKey()).thenReturn("withNestedTypeAttr"); + when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); - final List productTypes = singletonList(productType); + final List productTypes = singletonList(productType); - // test - assertThatThrownBy(() -> mapToProductTypeDrafts(productTypes)) - .isExactlyInstanceOf(ReferenceReplacementException.class) - .hasMessageContaining("Some errors occurred during reference replacement. Causes:\n") - .hasMessageContaining("\tFailed to replace some references on the productType with key 'withNestedTypeAttr'" + // test + assertThatThrownBy(() -> mapToProductTypeDrafts(productTypes)) + .isExactlyInstanceOf(ReferenceReplacementException.class) + .hasMessageContaining("Some errors occurred during reference replacement. Causes:\n") + .hasMessageContaining( + "\tFailed to replace some references on the productType with key 'withNestedTypeAttr'" + ". Causes:\n") - .hasMessageContaining("\t\tFailed to replace some references on the attributeDefinition with name " + .hasMessageContaining( + "\t\tFailed to replace some references on the attributeDefinition with name " + "'nestedattr'. Cause: ProductType reference is not expanded."); - } - - @Test - void mapToProductDrafts_WithNestedTypeWithSetOfSetOfSet_ShouldReplaceRef() { - // preparation - final ProductType referencedProductType = mock(ProductType.class); - when(referencedProductType.getKey()).thenReturn("referencedProductType"); - - final Reference productTypeReference = spy(ProductType.reference(referencedProductType)); - when(productTypeReference.getObj()).thenReturn(referencedProductType); - - final AttributeDefinition nestedTypeAttr = AttributeDefinitionBuilder - .of("nestedattr", ofEnglish("nestedattr"), + } + + @Test + void mapToProductDrafts_WithNestedTypeWithSetOfSetOfSet_ShouldReplaceRef() { + // preparation + final ProductType referencedProductType = mock(ProductType.class); + when(referencedProductType.getKey()).thenReturn("referencedProductType"); + + final Reference productTypeReference = + spy(ProductType.reference(referencedProductType)); + when(productTypeReference.getObj()).thenReturn(referencedProductType); + + final AttributeDefinition nestedTypeAttr = + AttributeDefinitionBuilder.of( + "nestedattr", + ofEnglish("nestedattr"), SetAttributeType.of( SetAttributeType.of( - SetAttributeType.of( - NestedAttributeType.of(productTypeReference))))) + SetAttributeType.of(NestedAttributeType.of(productTypeReference))))) .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getKey()).thenReturn("withNestedTypeAttr"); - when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); - - final List productTypes = singletonList(productType); - - // test - final List productTypeDrafts = mapToProductTypeDrafts(productTypes); - - // assertion - assertThat(productTypeDrafts) - .hasOnlyOneElementSatisfying(productTypeDraft -> { - final SetAttributeType setAttributeType = (SetAttributeType) - productTypeDraft.getAttributes().get(0).getAttributeType(); - final SetAttributeType setOfSet = (SetAttributeType) setAttributeType.getElementType(); - final SetAttributeType setOfSetOfSet = (SetAttributeType) setOfSet.getElementType(); - final NestedAttributeType nestedAttributeType = (NestedAttributeType) setOfSetOfSet.getElementType(); - assertThat(nestedAttributeType.getTypeReference().getId()).isEqualTo(referencedProductType.getKey()); + final ProductType productType = mock(ProductType.class); + when(productType.getKey()).thenReturn("withNestedTypeAttr"); + when(productType.getAttributes()).thenReturn(singletonList(nestedTypeAttr)); + + final List productTypes = singletonList(productType); + + // test + final List productTypeDrafts = mapToProductTypeDrafts(productTypes); + + // assertion + assertThat(productTypeDrafts) + .hasOnlyOneElementSatisfying( + productTypeDraft -> { + final SetAttributeType setAttributeType = + (SetAttributeType) productTypeDraft.getAttributes().get(0).getAttributeType(); + final SetAttributeType setOfSet = + (SetAttributeType) setAttributeType.getElementType(); + final SetAttributeType setOfSetOfSet = (SetAttributeType) setOfSet.getElementType(); + final NestedAttributeType nestedAttributeType = + (NestedAttributeType) setOfSetOfSet.getElementType(); + assertThat(nestedAttributeType.getTypeReference().getId()) + .isEqualTo(referencedProductType.getKey()); }); - } - - @Test - void buildProductTypeQuery_WithNoParam_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final ProductTypeQuery productTypeQuery = buildProductTypeQuery(); - assertThat(productTypeQuery.expansionPaths()).containsExactly( - ExpansionPath.of("attributes[*].type.typeReference")); - } - - @Test - void buildProductTypeQuery_With0MaxSetDepth_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final ProductTypeQuery productTypeQuery = buildProductTypeQuery(0); - assertThat(productTypeQuery.expansionPaths()).containsExactly( - ExpansionPath.of("attributes[*].type.typeReference")); - } - - @Test - void buildProductTypeQuery_With1MaxSetDepth_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final ProductTypeQuery productTypeQuery = buildProductTypeQuery(1); - assertThat(productTypeQuery.expansionPaths()).containsExactly( + } + + @Test + void buildProductTypeQuery_WithNoParam_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final ProductTypeQuery productTypeQuery = buildProductTypeQuery(); + assertThat(productTypeQuery.expansionPaths()) + .containsExactly(ExpansionPath.of("attributes[*].type.typeReference")); + } + + @Test + void buildProductTypeQuery_With0MaxSetDepth_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final ProductTypeQuery productTypeQuery = buildProductTypeQuery(0); + assertThat(productTypeQuery.expansionPaths()) + .containsExactly(ExpansionPath.of("attributes[*].type.typeReference")); + } + + @Test + void buildProductTypeQuery_With1MaxSetDepth_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final ProductTypeQuery productTypeQuery = buildProductTypeQuery(1); + assertThat(productTypeQuery.expansionPaths()) + .containsExactly( ExpansionPath.of("attributes[*].type.typeReference"), ExpansionPath.of("attributes[*].type.elementType.typeReference")); - } + } - @Test - void buildProductTypeQuery_With2MaxSetDepth_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final ProductTypeQuery productTypeQuery = buildProductTypeQuery(2); - assertThat(productTypeQuery.expansionPaths()).containsExactly( + @Test + void buildProductTypeQuery_With2MaxSetDepth_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final ProductTypeQuery productTypeQuery = buildProductTypeQuery(2); + assertThat(productTypeQuery.expansionPaths()) + .containsExactly( ExpansionPath.of("attributes[*].type.typeReference"), ExpansionPath.of("attributes[*].type.elementType.typeReference"), ExpansionPath.of("attributes[*].type.elementType.elementType.typeReference")); - } + } - @Test - void buildProductTypeQuery_With3MaxSetDepth_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final ProductTypeQuery productTypeQuery = buildProductTypeQuery(3); - assertThat(productTypeQuery.expansionPaths()).containsExactly( + @Test + void buildProductTypeQuery_With3MaxSetDepth_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final ProductTypeQuery productTypeQuery = buildProductTypeQuery(3); + assertThat(productTypeQuery.expansionPaths()) + .containsExactly( ExpansionPath.of("attributes[*].type.typeReference"), ExpansionPath.of("attributes[*].type.elementType.typeReference"), ExpansionPath.of("attributes[*].type.elementType.elementType.typeReference"), - ExpansionPath.of("attributes[*].type.elementType.elementType.elementType.typeReference")); - } + ExpansionPath.of( + "attributes[*].type.elementType.elementType.elementType.typeReference")); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/utils/ProductTypeUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/producttypes/utils/ProductTypeUpdateActionUtilsTest.java index 263a2ce00a..1aa8e75c6a 100644 --- a/src/test/java/com/commercetools/sync/producttypes/utils/ProductTypeUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/utils/ProductTypeUpdateActionUtilsTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.producttypes.utils; +import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildChangeDescriptionAction; +import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildChangeNameAction; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.products.attributes.AttributeDefinition; @@ -11,88 +18,72 @@ import io.sphere.sdk.producttypes.ProductTypeDraft; import io.sphere.sdk.producttypes.commands.updateactions.ChangeDescription; import io.sphere.sdk.producttypes.commands.updateactions.ChangeName; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Locale; import java.util.Optional; - -import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildChangeDescriptionAction; -import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildChangeNameAction; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; class ProductTypeUpdateActionUtilsTest { - private static ProductType old; - private static ProductTypeDraft newSame; - private static ProductTypeDraft newDifferent; - - /** - * Initialises test data. - */ - @BeforeAll - static void setup() { - final LocalizedString label = LocalizedString.of(Locale.ENGLISH, "label1"); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of("attributeName1", label, StringAttributeType.of()) - .build(); - - final List oldAttributeDefinitions = singletonList(attributeDefinition); - - final List sameAttributeDefinitionDrafts = singletonList( - AttributeDefinitionDraftBuilder.of(attributeDefinition).build()); - - old = mock(ProductType.class); - when(old.getKey()).thenReturn("key1"); - when(old.getName()).thenReturn("name1"); - when(old.getDescription()).thenReturn("description 1"); - when(old.getAttributes()).thenReturn(oldAttributeDefinitions); - - newSame = ProductTypeDraft.ofAttributeDefinitionDrafts( - "key1", - "name1", - "description 1", - sameAttributeDefinitionDrafts - ); - - newDifferent = ProductTypeDraft.ofAttributeDefinitionDrafts( - "key2", - "name2", - "description 2", - sameAttributeDefinitionDrafts - ); - } - - @Test - void buildChangeNameAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeNameAction(old, newDifferent); - - assertThat(result).containsInstanceOf(ChangeName.class); - assertThat(result).contains(ChangeName.of(newDifferent.getName())); - } - - @Test - void buildChangeNameAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeNameAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildChangeDescriptionAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeDescriptionAction(old, newDifferent); - - assertThat(result).containsInstanceOf(ChangeDescription.class); - assertThat(result).contains(ChangeDescription.of(newDifferent.getDescription())); - } - - @Test - void buildChangeDescriptionAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeDescriptionAction(old, newSame); - - assertThat(result).isEmpty(); - } + private static ProductType old; + private static ProductTypeDraft newSame; + private static ProductTypeDraft newDifferent; + + /** Initialises test data. */ + @BeforeAll + static void setup() { + final LocalizedString label = LocalizedString.of(Locale.ENGLISH, "label1"); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of("attributeName1", label, StringAttributeType.of()).build(); + + final List oldAttributeDefinitions = singletonList(attributeDefinition); + + final List sameAttributeDefinitionDrafts = + singletonList(AttributeDefinitionDraftBuilder.of(attributeDefinition).build()); + + old = mock(ProductType.class); + when(old.getKey()).thenReturn("key1"); + when(old.getName()).thenReturn("name1"); + when(old.getDescription()).thenReturn("description 1"); + when(old.getAttributes()).thenReturn(oldAttributeDefinitions); + + newSame = + ProductTypeDraft.ofAttributeDefinitionDrafts( + "key1", "name1", "description 1", sameAttributeDefinitionDrafts); + + newDifferent = + ProductTypeDraft.ofAttributeDefinitionDrafts( + "key2", "name2", "description 2", sameAttributeDefinitionDrafts); + } + + @Test + void buildChangeNameAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = buildChangeNameAction(old, newDifferent); + + assertThat(result).containsInstanceOf(ChangeName.class); + assertThat(result).contains(ChangeName.of(newDifferent.getName())); + } + + @Test + void buildChangeNameAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildChangeNameAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildChangeDescriptionAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildChangeDescriptionAction(old, newDifferent); + + assertThat(result).containsInstanceOf(ChangeDescription.class); + assertThat(result).contains(ChangeDescription.of(newDifferent.getDescription())); + } + + @Test + void buildChangeDescriptionAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildChangeDescriptionAction(old, newSame); + + assertThat(result).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildAttributeDefinitionUpdateActionsTest.java b/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildAttributeDefinitionUpdateActionsTest.java index 1414bb8726..c0a3049e3e 100644 --- a/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildAttributeDefinitionUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildAttributeDefinitionUpdateActionsTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.producttypes.utils.producttypeactionutils; +import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildAttributesUpdateActions; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.DuplicateNameException; import com.commercetools.sync.producttypes.ProductTypeSyncOptions; @@ -38,666 +48,634 @@ import io.sphere.sdk.producttypes.commands.updateactions.ChangePlainEnumValueLabel; import io.sphere.sdk.producttypes.commands.updateactions.RemoveAttributeDefinition; import io.sphere.sdk.producttypes.commands.updateactions.RemoveEnumValues; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Collections; import java.util.List; - -import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateActionUtils.buildAttributesUpdateActions; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class BuildAttributeDefinitionUpdateActionsTest { - private static final String RES_ROOT = - "com/commercetools/sync/producttypes/utils/updateattributedefinitions/attributes/"; - - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_AB = - RES_ROOT + "product-type-with-attribute-definitions-ab.json"; - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ABB = - RES_ROOT + "product-type-with-attribute-definitions-abb.json"; - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ABC = - RES_ROOT + "product-type-with-attribute-definitions-abc.json"; - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ABCD = - RES_ROOT + "product-type-with-attribute-definitions-abcd.json"; - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ABD = - RES_ROOT + "product-type-with-attribute-definitions-abd.json"; - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_CAB = - RES_ROOT + "product-type-with-attribute-definitions-cab.json"; - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_CB = - RES_ROOT + "product-type-with-attribute-definitions-cb.json"; - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ACBD = - RES_ROOT + "product-type-with-attribute-definitions-acbd.json"; - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ADBC = - RES_ROOT + "product-type-with-attribute-definitions-adbc.json"; - private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_CBD = - RES_ROOT + "product-type-with-attribute-definitions-cbd.json"; - - private static final ProductTypeSyncOptions SYNC_OPTIONS = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); - - private static final AttributeDefinition ATTRIBUTE_DEFINITION_A = AttributeDefinitionBuilder - .of("a", ofEnglish("label_en"), StringAttributeType.of()) - .build(); - - private static final AttributeDefinition ATTRIBUTE_DEFINITION_B = AttributeDefinitionBuilder - .of("b", ofEnglish("label_en"), StringAttributeType.of()) - .build(); - - private static final AttributeDefinition ATTRIBUTE_DEFINITION_C = AttributeDefinitionBuilder - .of("c", ofEnglish("label_en"), StringAttributeType.of()) - .build(); - - private static final AttributeDefinition ATTRIBUTE_DEFINITION_D = AttributeDefinitionBuilder - .of("d", ofEnglish("label_en"), StringAttributeType.of()) - .build(); - - @Test - void buildAttributesUpdateActions_WithNullNewAttributesAndExistingAttributes_ShouldBuild3RemoveActions() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - - final List> updateActions = buildAttributesUpdateActions( + private static final String RES_ROOT = + "com/commercetools/sync/producttypes/utils/updateattributedefinitions/attributes/"; + + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_AB = + RES_ROOT + "product-type-with-attribute-definitions-ab.json"; + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ABB = + RES_ROOT + "product-type-with-attribute-definitions-abb.json"; + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ABC = + RES_ROOT + "product-type-with-attribute-definitions-abc.json"; + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ABCD = + RES_ROOT + "product-type-with-attribute-definitions-abcd.json"; + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ABD = + RES_ROOT + "product-type-with-attribute-definitions-abd.json"; + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_CAB = + RES_ROOT + "product-type-with-attribute-definitions-cab.json"; + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_CB = + RES_ROOT + "product-type-with-attribute-definitions-cb.json"; + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ACBD = + RES_ROOT + "product-type-with-attribute-definitions-acbd.json"; + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_ADBC = + RES_ROOT + "product-type-with-attribute-definitions-adbc.json"; + private static final String PRODUCT_TYPE_WITH_ATTRIBUTES_CBD = + RES_ROOT + "product-type-with-attribute-definitions-cbd.json"; + + private static final ProductTypeSyncOptions SYNC_OPTIONS = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + private static final AttributeDefinition ATTRIBUTE_DEFINITION_A = + AttributeDefinitionBuilder.of("a", ofEnglish("label_en"), StringAttributeType.of()).build(); + + private static final AttributeDefinition ATTRIBUTE_DEFINITION_B = + AttributeDefinitionBuilder.of("b", ofEnglish("label_en"), StringAttributeType.of()).build(); + + private static final AttributeDefinition ATTRIBUTE_DEFINITION_C = + AttributeDefinitionBuilder.of("c", ofEnglish("label_en"), StringAttributeType.of()).build(); + + private static final AttributeDefinition ATTRIBUTE_DEFINITION_D = + AttributeDefinitionBuilder.of("d", ofEnglish("label_en"), StringAttributeType.of()).build(); + + @Test + void + buildAttributesUpdateActions_WithNullNewAttributesAndExistingAttributes_ShouldBuild3RemoveActions() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); + + final List> updateActions = + buildAttributesUpdateActions( oldProductType, ProductTypeDraftBuilder.of("key", "name", "key", null).build(), - SYNC_OPTIONS - ); + SYNC_OPTIONS); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("a"), RemoveAttributeDefinition.of("b"), - RemoveAttributeDefinition.of("c") - ); - } - - @Test - void buildAttributesUpdateActions_WithNullNewAttributesAndNoOldAttributes_ShouldNotBuildActions() { - final ProductType oldProductType = mock(ProductType.class); - when(oldProductType.getAttributes()).thenReturn(emptyList()); - when(oldProductType.getKey()).thenReturn("product_type_key_1"); - - final List> updateActions = buildAttributesUpdateActions( + RemoveAttributeDefinition.of("c")); + } + + @Test + void + buildAttributesUpdateActions_WithNullNewAttributesAndNoOldAttributes_ShouldNotBuildActions() { + final ProductType oldProductType = mock(ProductType.class); + when(oldProductType.getAttributes()).thenReturn(emptyList()); + when(oldProductType.getKey()).thenReturn("product_type_key_1"); + + final List> updateActions = + buildAttributesUpdateActions( oldProductType, ProductTypeDraftBuilder.of("key", "name", "key", null).build(), - SYNC_OPTIONS - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAttributesUpdateActions_WithNewAttributesAndNoOldAttributes_ShouldBuild3AddActions() { - final ProductType oldProductType = mock(ProductType.class); - when(oldProductType.getAttributes()).thenReturn(emptyList()); - when(oldProductType.getKey()).thenReturn("product_type_key_1"); - - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, - ProductTypeDraft.class - ); - - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).hasSize(3); - assertThat(updateActions).allSatisfy(action -> { - assertThat(action).isExactlyInstanceOf(AddAttributeDefinition.class); - final AddAttributeDefinition addAttributeDefinition = (AddAttributeDefinition) action; - final AttributeDefinitionDraft attribute = addAttributeDefinition.getAttribute(); - assertThat(newProductTypeDraft.getAttributes()).contains(attribute); - }); - } - - @Test - void buildAttributesUpdateActions_WithIdenticalAttributes_ShouldNotBuildUpdateActions() { - final ProductType oldProductType = mock(ProductType.class); - when(oldProductType.getAttributes()) - .thenReturn(asList(ATTRIBUTE_DEFINITION_A, ATTRIBUTE_DEFINITION_B, ATTRIBUTE_DEFINITION_C)); - - final AttributeDefinitionDraft attributeA = AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_A.getAttributeType(), ATTRIBUTE_DEFINITION_A.getName(), + SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAttributesUpdateActions_WithNewAttributesAndNoOldAttributes_ShouldBuild3AddActions() { + final ProductType oldProductType = mock(ProductType.class); + when(oldProductType.getAttributes()).thenReturn(emptyList()); + when(oldProductType.getKey()).thenReturn("product_type_key_1"); + + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductTypeDraft.class); + + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions).hasSize(3); + assertThat(updateActions) + .allSatisfy( + action -> { + assertThat(action).isExactlyInstanceOf(AddAttributeDefinition.class); + final AddAttributeDefinition addAttributeDefinition = (AddAttributeDefinition) action; + final AttributeDefinitionDraft attribute = addAttributeDefinition.getAttribute(); + assertThat(newProductTypeDraft.getAttributes()).contains(attribute); + }); + } + + @Test + void buildAttributesUpdateActions_WithIdenticalAttributes_ShouldNotBuildUpdateActions() { + final ProductType oldProductType = mock(ProductType.class); + when(oldProductType.getAttributes()) + .thenReturn(asList(ATTRIBUTE_DEFINITION_A, ATTRIBUTE_DEFINITION_B, ATTRIBUTE_DEFINITION_C)); + + final AttributeDefinitionDraft attributeA = + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_A.getAttributeType(), ATTRIBUTE_DEFINITION_A.getName(), ATTRIBUTE_DEFINITION_A.getLabel(), ATTRIBUTE_DEFINITION_A.isRequired()) .build(); - final AttributeDefinitionDraft attributeB = AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_B.getAttributeType(), ATTRIBUTE_DEFINITION_B.getName(), + final AttributeDefinitionDraft attributeB = + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_B.getAttributeType(), ATTRIBUTE_DEFINITION_B.getName(), ATTRIBUTE_DEFINITION_B.getLabel(), ATTRIBUTE_DEFINITION_B.isRequired()) .build(); - final AttributeDefinitionDraft attributeC = AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_C.getAttributeType(), ATTRIBUTE_DEFINITION_C.getName(), + final AttributeDefinitionDraft attributeC = + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_C.getAttributeType(), ATTRIBUTE_DEFINITION_C.getName(), ATTRIBUTE_DEFINITION_C.getLabel(), ATTRIBUTE_DEFINITION_C.isRequired()) .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of(oldProductType.getKey(), oldProductType.getName(), oldProductType.getDescription(), + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of( + oldProductType.getKey(), + oldProductType.getName(), + oldProductType.getDescription(), asList(attributeA, attributeB, attributeC)) .build(); - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - productTypeDraft, - SYNC_OPTIONS - ); + final List> updateActions = + buildAttributesUpdateActions(oldProductType, productTypeDraft, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildAttributesUpdateActions_WithChangedAttributes_ShouldBuildUpdateActions() { - final ProductType oldProductType = mock(ProductType.class); - when(oldProductType.getAttributes()) - .thenReturn(asList(ATTRIBUTE_DEFINITION_A, ATTRIBUTE_DEFINITION_B, ATTRIBUTE_DEFINITION_C)); + @Test + void buildAttributesUpdateActions_WithChangedAttributes_ShouldBuildUpdateActions() { + final ProductType oldProductType = mock(ProductType.class); + when(oldProductType.getAttributes()) + .thenReturn(asList(ATTRIBUTE_DEFINITION_A, ATTRIBUTE_DEFINITION_B, ATTRIBUTE_DEFINITION_C)); - final AttributeDefinitionDraft attributeA = AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_A.getAttributeType(), ATTRIBUTE_DEFINITION_A.getName(), + final AttributeDefinitionDraft attributeA = + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_A.getAttributeType(), ATTRIBUTE_DEFINITION_A.getName(), ATTRIBUTE_DEFINITION_A.getLabel(), ATTRIBUTE_DEFINITION_A.isRequired()) .isSearchable(false) .build(); - final AttributeDefinitionDraft attributeB = AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_B.getAttributeType(), ATTRIBUTE_DEFINITION_B.getName(), + final AttributeDefinitionDraft attributeB = + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_B.getAttributeType(), ATTRIBUTE_DEFINITION_B.getName(), ofEnglish("newLabel"), ATTRIBUTE_DEFINITION_B.isRequired()) .inputHint(TextInputHint.MULTI_LINE) .build(); - final AttributeDefinitionDraft attributeC = AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_C.getAttributeType(), ATTRIBUTE_DEFINITION_C.getName(), + final AttributeDefinitionDraft attributeC = + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_C.getAttributeType(), ATTRIBUTE_DEFINITION_C.getName(), ATTRIBUTE_DEFINITION_C.getLabel(), ATTRIBUTE_DEFINITION_C.isRequired()) .attributeConstraint(AttributeConstraint.SAME_FOR_ALL) .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of(oldProductType.getKey(), oldProductType.getName(), oldProductType.getDescription(), + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of( + oldProductType.getKey(), + oldProductType.getName(), + oldProductType.getDescription(), asList(attributeA, attributeB, attributeC)) .build(); - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - productTypeDraft, - SYNC_OPTIONS - ); + final List> updateActions = + buildAttributesUpdateActions(oldProductType, productTypeDraft, SYNC_OPTIONS); - assertThat(updateActions).containsExactlyInAnyOrder( + assertThat(updateActions) + .containsExactlyInAnyOrder( ChangeIsSearchable.of(ATTRIBUTE_DEFINITION_A.getName(), false), ChangeInputHint.of(ATTRIBUTE_DEFINITION_B.getName(), TextInputHint.MULTI_LINE), - ChangeAttributeConstraint.of(ATTRIBUTE_DEFINITION_C.getName(), AttributeConstraint.SAME_FOR_ALL), - ChangeAttributeDefinitionLabel.of(ATTRIBUTE_DEFINITION_B.getName(), ofEnglish("newLabel")) - ); - } - - @Test - void buildAttributesUpdateActions_WithDuplicateAttributeNames_ShouldNotBuildActionsAndTriggerErrorCb() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_ABB, - ProductTypeDraft.class - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) - .build(); - - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - syncOptions - ); - - assertThat(updateActions).isEmpty(); - assertThat(errorMessages).hasSize(1); - assertThat(errorMessages.get(0)).matches("Failed to build update actions for the attributes definitions of the " - + "product type with the key 'key'. Reason: .*DuplicateNameException: Attribute definitions drafts " - + "have duplicated names. Duplicated attribute definition name: 'b'. Attribute definitions names are " - + "expected to be unique inside their product type."); - assertThat(exceptions).hasSize(1); - assertThat(exceptions.get(0)).isExactlyInstanceOf(BuildUpdateActionException.class); - assertThat(exceptions.get(0).getMessage()).contains("Attribute definitions drafts have duplicated names. " - + "Duplicated attribute definition name: 'b'. Attribute definitions names are expected to be unique " - + "inside their product type."); - assertThat(exceptions.get(0).getCause()).isExactlyInstanceOf(DuplicateNameException.class); - } - - @Test - void buildAttributesUpdateActions_WithOneMissingAttribute_ShouldBuildRemoveAttributeAction() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_AB, - ProductTypeDraft.class - ); - - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly(RemoveAttributeDefinition.of("c")); - } - - @Test - void buildAttributesUpdateActions_WithOneExtraAttribute_ShouldBuildAddAttributesAction() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_ABCD, - ProductTypeDraft.class - ); - - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_D.getAttributeType(), ATTRIBUTE_DEFINITION_D.getName(), - ATTRIBUTE_DEFINITION_D.getLabel(), ATTRIBUTE_DEFINITION_D.isRequired()) - .isSearchable(true) - .build()) - ); - } - - @Test - void buildAttributesUpdateActions_WithOneAttributeSwitch_ShouldBuildRemoveAndAddAttributesActions() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_ABD, - ProductTypeDraft.class - ); - - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - SYNC_OPTIONS - ); - - - assertThat(updateActions).containsExactly( + ChangeAttributeConstraint.of( + ATTRIBUTE_DEFINITION_C.getName(), AttributeConstraint.SAME_FOR_ALL), + ChangeAttributeDefinitionLabel.of( + ATTRIBUTE_DEFINITION_B.getName(), ofEnglish("newLabel"))); + } + + @Test + void + buildAttributesUpdateActions_WithDuplicateAttributeNames_ShouldNotBuildActionsAndTriggerErrorCb() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); + + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABB, ProductTypeDraft.class); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) + .build(); + + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(errorMessages).hasSize(1); + assertThat(errorMessages.get(0)) + .matches( + "Failed to build update actions for the attributes definitions of the " + + "product type with the key 'key'. Reason: .*DuplicateNameException: Attribute definitions drafts " + + "have duplicated names. Duplicated attribute definition name: 'b'. Attribute definitions names are " + + "expected to be unique inside their product type."); + assertThat(exceptions).hasSize(1); + assertThat(exceptions.get(0)).isExactlyInstanceOf(BuildUpdateActionException.class); + assertThat(exceptions.get(0).getMessage()) + .contains( + "Attribute definitions drafts have duplicated names. " + + "Duplicated attribute definition name: 'b'. Attribute definitions names are expected to be unique " + + "inside their product type."); + assertThat(exceptions.get(0).getCause()).isExactlyInstanceOf(DuplicateNameException.class); + } + + @Test + void buildAttributesUpdateActions_WithOneMissingAttribute_ShouldBuildRemoveAttributeAction() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); + + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_AB, ProductTypeDraft.class); + + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions).containsExactly(RemoveAttributeDefinition.of("c")); + } + + @Test + void buildAttributesUpdateActions_WithOneExtraAttribute_ShouldBuildAddAttributesAction() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); + + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABCD, ProductTypeDraft.class); + + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_D.getAttributeType(), ATTRIBUTE_DEFINITION_D.getName(), + ATTRIBUTE_DEFINITION_D.getLabel(), ATTRIBUTE_DEFINITION_D.isRequired()) + .isSearchable(true) + .build())); + } + + @Test + void + buildAttributesUpdateActions_WithOneAttributeSwitch_ShouldBuildRemoveAndAddAttributesActions() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); + + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABD, ProductTypeDraft.class); + + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("c"), - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_D.getAttributeType(), ATTRIBUTE_DEFINITION_D.getName(), - ATTRIBUTE_DEFINITION_D.getLabel(), ATTRIBUTE_DEFINITION_D.isRequired()) - .isSearchable(true) - .build()) - ); - } - - @Test - void buildAttributesUpdateActions_WithDifferentOrder_ShouldBuildChangeAttributeOrderAction() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_CAB, - ProductTypeDraft.class - ); - - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( - ChangeAttributeOrderByName - .of(asList( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_D.getAttributeType(), ATTRIBUTE_DEFINITION_D.getName(), + ATTRIBUTE_DEFINITION_D.getLabel(), ATTRIBUTE_DEFINITION_D.isRequired()) + .isSearchable(true) + .build())); + } + + @Test + void buildAttributesUpdateActions_WithDifferentOrder_ShouldBuildChangeAttributeOrderAction() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); + + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_CAB, ProductTypeDraft.class); + + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + ChangeAttributeOrderByName.of( + asList( ATTRIBUTE_DEFINITION_C.getName(), ATTRIBUTE_DEFINITION_A.getName(), - ATTRIBUTE_DEFINITION_B.getName() - )) - ); - } + ATTRIBUTE_DEFINITION_B.getName()))); + } - @Test - void buildAttributesUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); + @Test + void + buildAttributesUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_CB, - ProductTypeDraft.class - ); + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_CB, ProductTypeDraft.class); + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, SYNC_OPTIONS); - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("a"), - ChangeAttributeOrderByName - .of(asList( - ATTRIBUTE_DEFINITION_C.getName(), - ATTRIBUTE_DEFINITION_B.getName() - ) - ) - ); - } - - @Test - void buildAttributesUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_ACBD, - ProductTypeDraft.class - ); - - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_D.getAttributeType(), - ATTRIBUTE_DEFINITION_D.getName(), ATTRIBUTE_DEFINITION_D.getLabel(), - ATTRIBUTE_DEFINITION_D.isRequired()) - .isSearchable(true) - .inputHint(TextInputHint.SINGLE_LINE) - .attributeConstraint(AttributeConstraint.NONE) - .build()), - ChangeAttributeOrderByName - .of(asList( + ChangeAttributeOrderByName.of( + asList(ATTRIBUTE_DEFINITION_C.getName(), ATTRIBUTE_DEFINITION_B.getName()))); + } + + @Test + void + buildAttributesUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); + + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ACBD, ProductTypeDraft.class); + + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_D.getAttributeType(), + ATTRIBUTE_DEFINITION_D.getName(), + ATTRIBUTE_DEFINITION_D.getLabel(), + ATTRIBUTE_DEFINITION_D.isRequired()) + .isSearchable(true) + .inputHint(TextInputHint.SINGLE_LINE) + .attributeConstraint(AttributeConstraint.NONE) + .build()), + ChangeAttributeOrderByName.of( + asList( ATTRIBUTE_DEFINITION_A.getName(), ATTRIBUTE_DEFINITION_C.getName(), ATTRIBUTE_DEFINITION_B.getName(), - ATTRIBUTE_DEFINITION_D.getName() - ) - ) - ); - } - - @Test - void buildAttributesUpdateActions_WithAddedAttributeInBetween_ShouldBuildChangeOrderAndAddActions() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_ADBC, - ProductTypeDraft.class - ); - - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_D.getAttributeType(), - ATTRIBUTE_DEFINITION_D.getName(), ATTRIBUTE_DEFINITION_D.getLabel(), - ATTRIBUTE_DEFINITION_D.isRequired()) - .isSearchable(true) - .build()), - ChangeAttributeOrderByName - .of(asList( + ATTRIBUTE_DEFINITION_D.getName()))); + } + + @Test + void + buildAttributesUpdateActions_WithAddedAttributeInBetween_ShouldBuildChangeOrderAndAddActions() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); + + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ADBC, ProductTypeDraft.class); + + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_D.getAttributeType(), + ATTRIBUTE_DEFINITION_D.getName(), + ATTRIBUTE_DEFINITION_D.getLabel(), + ATTRIBUTE_DEFINITION_D.isRequired()) + .isSearchable(true) + .build()), + ChangeAttributeOrderByName.of( + asList( ATTRIBUTE_DEFINITION_A.getName(), ATTRIBUTE_DEFINITION_D.getName(), ATTRIBUTE_DEFINITION_B.getName(), - ATTRIBUTE_DEFINITION_C.getName() - ) - ) - ); - } - - @Test - void buildAttributesUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveAttributeActions() { - final ProductType oldProductType = readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - - final ProductTypeDraft newProductTypeDraft = readObjectFromResource( - PRODUCT_TYPE_WITH_ATTRIBUTES_CBD, - ProductTypeDraft.class - ); - - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - SYNC_OPTIONS - ); + ATTRIBUTE_DEFINITION_C.getName()))); + } + + @Test + void + buildAttributesUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveAttributeActions() { + final ProductType oldProductType = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_ABC, ProductType.class); - assertThat(updateActions).containsExactly( + final ProductTypeDraft newProductTypeDraft = + readObjectFromResource(PRODUCT_TYPE_WITH_ATTRIBUTES_CBD, ProductTypeDraft.class); + + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("a"), - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(ATTRIBUTE_DEFINITION_D.getAttributeType(), - ATTRIBUTE_DEFINITION_D.getName(), ATTRIBUTE_DEFINITION_D.getLabel(), - ATTRIBUTE_DEFINITION_D.isRequired()) - .isSearchable(true).build()), - ChangeAttributeOrderByName - .of(asList( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + ATTRIBUTE_DEFINITION_D.getAttributeType(), + ATTRIBUTE_DEFINITION_D.getName(), + ATTRIBUTE_DEFINITION_D.getLabel(), + ATTRIBUTE_DEFINITION_D.isRequired()) + .isSearchable(true) + .build()), + ChangeAttributeOrderByName.of( + asList( ATTRIBUTE_DEFINITION_C.getName(), ATTRIBUTE_DEFINITION_B.getName(), - ATTRIBUTE_DEFINITION_D.getName() - ) - ) - ); - } - - @Test - void buildAttributesUpdateActions_WithDifferentAttributeType_ShouldRemoveOldAttributeAndAddNewAttribute() { - // preparation - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "a", ofEnglish("new_label"), true) + ATTRIBUTE_DEFINITION_D.getName()))); + } + + @Test + void + buildAttributesUpdateActions_WithDifferentAttributeType_ShouldRemoveOldAttributeAndAddNewAttribute() { + // preparation + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), "a", ofEnglish("new_label"), true) .build(); - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("old_label"), LocalizedStringAttributeType.of()) + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of( + "a", ofEnglish("old_label"), LocalizedStringAttributeType.of()) .isRequired(true) .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - // test - final List> updateActions = - buildAttributesUpdateActions( - productType, - productTypeDraft, - syncOptions - ); + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, syncOptions); - // assertions - assertThat(updateActions).containsExactly( + // assertions + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("a"), - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(newDefinition.getAttributeType(), - newDefinition.getName(), newDefinition.getLabel(), - newDefinition.isRequired()) - .build()) - ); - } - - @Test - void buildAttributesUpdateActions_WithDifferentSetAttributeType_ShouldRemoveOldAttributeAndAddNewAttribute() { - // preparation - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(StringAttributeType.of()), "a", ofEnglish("new_label"), true) - .build(); - - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("old_label"), SetAttributeType.of(LocalizedStringAttributeType.of())) + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + newDefinition.getAttributeType(), + newDefinition.getName(), + newDefinition.getLabel(), + newDefinition.isRequired()) + .build())); + } + + @Test + void + buildAttributesUpdateActions_WithDifferentSetAttributeType_ShouldRemoveOldAttributeAndAddNewAttribute() { + // preparation + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(StringAttributeType.of()), "a", ofEnglish("new_label"), true) + .build(); + + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of( + "a", ofEnglish("old_label"), SetAttributeType.of(LocalizedStringAttributeType.of())) .isRequired(true) .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - // test - final List> updateActions = - buildAttributesUpdateActions( - productType, - productTypeDraft, - syncOptions - ); + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, syncOptions); - // assertions - assertThat(updateActions).containsExactly( + // assertions + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("a"), - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(newDefinition.getAttributeType(), - newDefinition.getName(), newDefinition.getLabel(), - newDefinition.isRequired()) - .build()) - ); - } - - @Test - void buildAttributesUpdateActions_WithDifferentNestedAttributeRefs_ShouldRemoveOldAttributeAndAddNewAttributes() { - // preparation - final AttributeDefinition ofNestedType = AttributeDefinitionBuilder - .of("nested", ofEnglish("label"), NestedAttributeType.of(ProductType.referenceOfId("foo"))) - .build(); - - final AttributeDefinition ofSetOfNestedType = AttributeDefinitionBuilder - .of("set-of-nested", ofEnglish("label"), + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + newDefinition.getAttributeType(), + newDefinition.getName(), + newDefinition.getLabel(), + newDefinition.isRequired()) + .build())); + } + + @Test + void + buildAttributesUpdateActions_WithDifferentNestedAttributeRefs_ShouldRemoveOldAttributeAndAddNewAttributes() { + // preparation + final AttributeDefinition ofNestedType = + AttributeDefinitionBuilder.of( + "nested", + ofEnglish("label"), + NestedAttributeType.of(ProductType.referenceOfId("foo"))) + .build(); + + final AttributeDefinition ofSetOfNestedType = + AttributeDefinitionBuilder.of( + "set-of-nested", + ofEnglish("label"), SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("foo")))) .build(); - final AttributeDefinition ofSetOfSetOfNestedType = AttributeDefinitionBuilder - .of("set-of-set-of-nested", ofEnglish("label"), - SetAttributeType.of(SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("foo"))))) + final AttributeDefinition ofSetOfSetOfNestedType = + AttributeDefinitionBuilder.of( + "set-of-set-of-nested", + ofEnglish("label"), + SetAttributeType.of( + SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("foo"))))) .build(); - - final AttributeDefinitionDraft ofNestedTypeDraft = AttributeDefinitionDraftBuilder - .of(ofNestedType) + final AttributeDefinitionDraft ofNestedTypeDraft = + AttributeDefinitionDraftBuilder.of(ofNestedType) .attributeType(NestedAttributeType.of(ProductType.referenceOfId("bar"))) .build(); - final AttributeDefinitionDraft ofSetOfNestedTypeDraft = AttributeDefinitionDraftBuilder - .of(ofSetOfNestedType) - .attributeType(SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("bar")))) + final AttributeDefinitionDraft ofSetOfNestedTypeDraft = + AttributeDefinitionDraftBuilder.of(ofSetOfNestedType) + .attributeType( + SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("bar")))) .build(); - final AttributeDefinitionDraft ofSetOfSetOfNestedTypeDraft = AttributeDefinitionDraftBuilder - .of(ofSetOfSetOfNestedType) + final AttributeDefinitionDraft ofSetOfSetOfNestedTypeDraft = + AttributeDefinitionDraftBuilder.of(ofSetOfSetOfNestedType) .attributeType( - SetAttributeType.of(SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("bar"))))) + SetAttributeType.of( + SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("bar"))))) .build(); - final ProductType oldProductType = mock(ProductType.class); - when(oldProductType.getAttributes()) - .thenReturn(asList(ofNestedType, ofSetOfNestedType, ofSetOfSetOfNestedType)); + final ProductType oldProductType = mock(ProductType.class); + when(oldProductType.getAttributes()) + .thenReturn(asList(ofNestedType, ofSetOfNestedType, ofSetOfSetOfNestedType)); - final ProductTypeDraft newProductTypeDraft = mock(ProductTypeDraft.class); - when(newProductTypeDraft.getAttributes()) - .thenReturn(asList(ofNestedTypeDraft, ofSetOfNestedTypeDraft, ofSetOfSetOfNestedTypeDraft)); + final ProductTypeDraft newProductTypeDraft = mock(ProductTypeDraft.class); + when(newProductTypeDraft.getAttributes()) + .thenReturn(asList(ofNestedTypeDraft, ofSetOfNestedTypeDraft, ofSetOfSetOfNestedTypeDraft)); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - // test - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - syncOptions - ); + // test + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, syncOptions); - // assertions - assertThat(updateActions).containsExactly( + // assertions + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of(ofNestedType.getName()), AddAttributeDefinition.of(ofNestedTypeDraft), RemoveAttributeDefinition.of(ofSetOfNestedType.getName()), AddAttributeDefinition.of(ofSetOfNestedTypeDraft), RemoveAttributeDefinition.of(ofSetOfSetOfNestedType.getName()), - AddAttributeDefinition.of(ofSetOfSetOfNestedTypeDraft) - ); - } - - @Test - void buildAttributesUpdateActions_WithIdenticalNestedAttributeRefs_ShouldNotBuildActions() { - // preparation - final AttributeDefinition ofNestedType = AttributeDefinitionBuilder - .of("nested", ofEnglish("label"), NestedAttributeType.of(ProductType.referenceOfId("foo"))) - .build(); - - final AttributeDefinition ofSetOfNestedType = AttributeDefinitionBuilder - .of("set-of-nested", ofEnglish("label"), + AddAttributeDefinition.of(ofSetOfSetOfNestedTypeDraft)); + } + + @Test + void buildAttributesUpdateActions_WithIdenticalNestedAttributeRefs_ShouldNotBuildActions() { + // preparation + final AttributeDefinition ofNestedType = + AttributeDefinitionBuilder.of( + "nested", + ofEnglish("label"), + NestedAttributeType.of(ProductType.referenceOfId("foo"))) + .build(); + + final AttributeDefinition ofSetOfNestedType = + AttributeDefinitionBuilder.of( + "set-of-nested", + ofEnglish("label"), SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("foo")))) .build(); - final AttributeDefinition ofSetOfSetOfNestedType = AttributeDefinitionBuilder - .of("set-of-set-of-nested", ofEnglish("label"), - SetAttributeType.of(SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("foo"))))) + final AttributeDefinition ofSetOfSetOfNestedType = + AttributeDefinitionBuilder.of( + "set-of-set-of-nested", + ofEnglish("label"), + SetAttributeType.of( + SetAttributeType.of(NestedAttributeType.of(ProductType.referenceOfId("foo"))))) .build(); + final AttributeDefinitionDraft ofNestedTypeDraft = + AttributeDefinitionDraftBuilder.of(ofNestedType).build(); - final AttributeDefinitionDraft ofNestedTypeDraft = AttributeDefinitionDraftBuilder - .of(ofNestedType) - .build(); + final AttributeDefinitionDraft ofSetOfNestedTypeDraft = + AttributeDefinitionDraftBuilder.of(ofSetOfNestedType).build(); - final AttributeDefinitionDraft ofSetOfNestedTypeDraft = AttributeDefinitionDraftBuilder - .of(ofSetOfNestedType) - .build(); + final AttributeDefinitionDraft ofSetOfSetOfNestedTypeDraft = + AttributeDefinitionDraftBuilder.of(ofSetOfSetOfNestedType).build(); - final AttributeDefinitionDraft ofSetOfSetOfNestedTypeDraft = AttributeDefinitionDraftBuilder - .of(ofSetOfSetOfNestedType) - .build(); + final ProductType oldProductType = mock(ProductType.class); + when(oldProductType.getAttributes()) + .thenReturn(asList(ofNestedType, ofSetOfNestedType, ofSetOfSetOfNestedType)); - final ProductType oldProductType = mock(ProductType.class); - when(oldProductType.getAttributes()) - .thenReturn(asList(ofNestedType, ofSetOfNestedType, ofSetOfSetOfNestedType)); + final ProductTypeDraft newProductTypeDraft = mock(ProductTypeDraft.class); + when(newProductTypeDraft.getAttributes()) + .thenReturn(asList(ofNestedTypeDraft, ofSetOfNestedTypeDraft, ofSetOfSetOfNestedTypeDraft)); - final ProductTypeDraft newProductTypeDraft = mock(ProductTypeDraft.class); - when(newProductTypeDraft.getAttributes()) - .thenReturn(asList(ofNestedTypeDraft, ofSetOfNestedTypeDraft, ofSetOfSetOfNestedTypeDraft)); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); + // test + final List> updateActions = + buildAttributesUpdateActions(oldProductType, newProductTypeDraft, syncOptions); - // test - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - newProductTypeDraft, - syncOptions - ); - - // assertions - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAttributesUpdateActions_WithANullAttributeDefinitionDraft_ShouldSkipNullAttributes() { - // preparation - final ProductType oldProductType = mock(ProductType.class); - final AttributeDefinition attributeDefinition = AttributeDefinitionBuilder - .of( + // assertions + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAttributesUpdateActions_WithANullAttributeDefinitionDraft_ShouldSkipNullAttributes() { + // preparation + final ProductType oldProductType = mock(ProductType.class); + final AttributeDefinition attributeDefinition = + AttributeDefinitionBuilder.of( "attributeName1", LocalizedString.ofEnglish("label1"), LocalizedEnumAttributeType.of(Collections.emptyList())) @@ -708,401 +686,409 @@ void buildAttributesUpdateActions_WithANullAttributeDefinitionDraft_ShouldSkipNu .isSearchable(false) .build(); - when(oldProductType.getAttributes()).thenReturn(singletonList(attributeDefinition)); + when(oldProductType.getAttributes()).thenReturn(singletonList(attributeDefinition)); - final AttributeDefinitionDraft attributeDefinitionDraftWithDifferentLabel = AttributeDefinitionDraftBuilder - .of( + final AttributeDefinitionDraft attributeDefinitionDraftWithDifferentLabel = + AttributeDefinitionDraftBuilder.of( LocalizedEnumAttributeType.of(Collections.emptyList()), "attributeName1", LocalizedString.ofEnglish("label2"), - false - ) + false) .attributeConstraint(AttributeConstraint.NONE) .inputTip(LocalizedString.ofEnglish("inputTip1")) .inputHint(TextInputHint.SINGLE_LINE) .isSearchable(false) .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("key", "name", "key", asList(null, attributeDefinitionDraftWithDifferentLabel)) + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of( + "key", "name", "key", asList(null, attributeDefinitionDraftWithDifferentLabel)) .build(); - // test - final List> updateActions = buildAttributesUpdateActions( - oldProductType, - productTypeDraft, - SYNC_OPTIONS - ); + // test + final List> updateActions = + buildAttributesUpdateActions(oldProductType, productTypeDraft, SYNC_OPTIONS); - // assertion - assertThat(updateActions).containsExactly( - ChangeAttributeDefinitionLabel.of(attributeDefinitionDraftWithDifferentLabel.getName(), + // assertion + assertThat(updateActions) + .containsExactly( + ChangeAttributeDefinitionLabel.of( + attributeDefinitionDraftWithDifferentLabel.getName(), attributeDefinitionDraftWithDifferentLabel.getLabel())); - } - - @Test - void buildAttributesUpdateActions_WithSetOfText_ShouldBuildActions() { - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(StringAttributeType.of()), "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); - - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("old_label"), SetAttributeType.of(StringAttributeType.of())) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - assertThat(updateActions) - .containsExactly(ChangeAttributeDefinitionLabel.of(newDefinition.getName(), newDefinition.getLabel())); - - } - - @Test - void buildAttributesUpdateActions_WithSetOfSetOfText_ShouldBuildActions() { - - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(SetAttributeType.of(SetAttributeType.of(StringAttributeType.of())), "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); - - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("old_label"), SetAttributeType.of(SetAttributeType.of(StringAttributeType.of()))) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - assertThat(updateActions) - .containsExactly(ChangeAttributeDefinitionLabel.of(newDefinition.getName(), newDefinition.getLabel())); - - } - - @Test - void buildAttributesUpdateActions_WithSetOfEnumsChanges_ShouldBuildCorrectActions() { - // preparation - final SetAttributeType newSetOfEnumType = SetAttributeType.of( + } + + @Test + void buildAttributesUpdateActions_WithSetOfText_ShouldBuildActions() { + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(StringAttributeType.of()), "a", ofEnglish("new_label"), true) + .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); + + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of( + "a", ofEnglish("old_label"), SetAttributeType.of(StringAttributeType.of())) + .build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + ChangeAttributeDefinitionLabel.of(newDefinition.getName(), newDefinition.getLabel())); + } + + @Test + void buildAttributesUpdateActions_WithSetOfSetOfText_ShouldBuildActions() { + + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of( + SetAttributeType.of(SetAttributeType.of(StringAttributeType.of())), + "a", + ofEnglish("new_label"), + true) + .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); + + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of( + "a", + ofEnglish("old_label"), + SetAttributeType.of(SetAttributeType.of(StringAttributeType.of()))) + .build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + ChangeAttributeDefinitionLabel.of(newDefinition.getName(), newDefinition.getLabel())); + } + + @Test + void buildAttributesUpdateActions_WithSetOfEnumsChanges_ShouldBuildCorrectActions() { + // preparation + final SetAttributeType newSetOfEnumType = + SetAttributeType.of( EnumAttributeType.of( - asList( - EnumValue.of("a", "a"), - EnumValue.of("b", "newB"), - EnumValue.of("c", "c") - ))); + asList(EnumValue.of("a", "a"), EnumValue.of("b", "newB"), EnumValue.of("c", "c")))); - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(newSetOfEnumType, "a", ofEnglish("new_label"), true) + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of(newSetOfEnumType, "a", ofEnglish("new_label"), true) .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); - + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); - final SetAttributeType oldSetOfEnumType = SetAttributeType.of( + final SetAttributeType oldSetOfEnumType = + SetAttributeType.of( EnumAttributeType.of( - asList( - EnumValue.of("d", "d"), - EnumValue.of("b", "b"), - EnumValue.of("a", "a") - ))); - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("new_label"), oldSetOfEnumType) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - // assertion - assertThat(updateActions).containsExactly( + asList(EnumValue.of("d", "d"), EnumValue.of("b", "b"), EnumValue.of("a", "a")))); + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of("a", ofEnglish("new_label"), oldSetOfEnumType).build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + // assertion + assertThat(updateActions) + .containsExactly( RemoveEnumValues.of("a", "d"), ChangePlainEnumValueLabel.of("a", EnumValue.of("b", "newB")), AddEnumValue.of("a", EnumValue.of("c", "c")), - ChangeEnumValueOrder.of("a", asList( - EnumValue.of("a", "a"), - EnumValue.of("b", "newB"), - EnumValue.of("c", "c") - ))); - } - - @Test - void buildAttributesUpdateActions_WithSetOfIdenticalEnums_ShouldNotBuildActions() { - // preparation - final SetAttributeType newSetOfEnumType = SetAttributeType.of( - EnumAttributeType.of(singletonList(EnumValue.of("foo", "bar")))); - - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(newSetOfEnumType, "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); - - - final SetAttributeType oldSetOfEnumType = SetAttributeType.of(EnumAttributeType.of(EnumValue.of("foo", "bar"))); - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("new_label"), oldSetOfEnumType) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - // assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAttributesUpdateActions_WithSetOfLEnumsChanges_ShouldBuildCorrectActions() { - // preparation - final SetAttributeType newSetOfLenumType = SetAttributeType.of( - LocalizedEnumAttributeType.of(singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar"))))); + ChangeEnumValueOrder.of( + "a", + asList(EnumValue.of("a", "a"), EnumValue.of("b", "newB"), EnumValue.of("c", "c")))); + } + + @Test + void buildAttributesUpdateActions_WithSetOfIdenticalEnums_ShouldNotBuildActions() { + // preparation + final SetAttributeType newSetOfEnumType = + SetAttributeType.of(EnumAttributeType.of(singletonList(EnumValue.of("foo", "bar")))); + + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of(newSetOfEnumType, "a", ofEnglish("new_label"), true) + .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); + + final SetAttributeType oldSetOfEnumType = + SetAttributeType.of(EnumAttributeType.of(EnumValue.of("foo", "bar"))); + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of("a", ofEnglish("new_label"), oldSetOfEnumType).build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + // assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAttributesUpdateActions_WithSetOfLEnumsChanges_ShouldBuildCorrectActions() { + // preparation + final SetAttributeType newSetOfLenumType = + SetAttributeType.of( + LocalizedEnumAttributeType.of( + singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar"))))); - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(newSetOfLenumType, "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of(newSetOfLenumType, "a", ofEnglish("new_label"), true) .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); - final SetAttributeType oldSetOfLenumType = SetAttributeType.of( - LocalizedEnumAttributeType.of(emptyList())); + final SetAttributeType oldSetOfLenumType = + SetAttributeType.of(LocalizedEnumAttributeType.of(emptyList())); - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("new_label"), oldSetOfLenumType) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - // assertion - assertThat(updateActions) - .containsExactly(AddLocalizedEnumValue.of("a", LocalizedEnumValue.of("foo", ofEnglish("bar")))); - } - - @Test - void buildAttributesUpdateActions_WithSetOfIdenticalLEnums_ShouldBuildNoActions() { - // preparation - final SetAttributeType newSetOfLenumType = SetAttributeType.of( - LocalizedEnumAttributeType.of(singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar"))))); - - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(newSetOfLenumType, "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of("a", ofEnglish("new_label"), oldSetOfLenumType).build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - final SetAttributeType oldSetOfLenumType = SetAttributeType.of( - LocalizedEnumAttributeType.of(singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar"))))); + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("new_label"), oldSetOfLenumType) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - // assertion - assertThat(updateActions).isEmpty(); - } - - @Test - void buildAttributesUpdateActions_WithOldEnumAndNewAsNonEnum_ShouldBuildRemoveAndAddActions() { - // preparation - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); + // assertion + assertThat(updateActions) + .containsExactly( + AddLocalizedEnumValue.of("a", LocalizedEnumValue.of("foo", ofEnglish("bar")))); + } - final LocalizedEnumAttributeType oldLenumType = - LocalizedEnumAttributeType.of(singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar")))); + @Test + void buildAttributesUpdateActions_WithSetOfIdenticalLEnums_ShouldBuildNoActions() { + // preparation + final SetAttributeType newSetOfLenumType = + SetAttributeType.of( + LocalizedEnumAttributeType.of( + singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar"))))); - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("new_label"), oldLenumType) + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of(newSetOfLenumType, "a", ofEnglish("new_label"), true) .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); - // test - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - // assertions - assertThat(updateActions).containsExactly( + final SetAttributeType oldSetOfLenumType = + SetAttributeType.of( + LocalizedEnumAttributeType.of( + singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar"))))); + + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of("a", ofEnglish("new_label"), oldSetOfLenumType).build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + // assertion + assertThat(updateActions).isEmpty(); + } + + @Test + void buildAttributesUpdateActions_WithOldEnumAndNewAsNonEnum_ShouldBuildRemoveAndAddActions() { + // preparation + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), "a", ofEnglish("new_label"), true) + .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); + + final LocalizedEnumAttributeType oldLenumType = + LocalizedEnumAttributeType.of( + singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar")))); + + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of("a", ofEnglish("new_label"), oldLenumType).build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + // assertions + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("a"), - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(newDefinition.getAttributeType(), - newDefinition.getName(), newDefinition.getLabel(), - newDefinition.isRequired()) - .build()) - ); - } - - @Test - void buildAttributesUpdateActions_WithNewEnumAndOldAsNonEnum_ShouldBuildRemoveAndAddActions() { - // preparation - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(EnumAttributeType.of(EnumValue.of("foo", "bar")), "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); - - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("new_label"), StringAttributeType.of()) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - // assertions - assertThat(updateActions).containsExactly( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + newDefinition.getAttributeType(), + newDefinition.getName(), + newDefinition.getLabel(), + newDefinition.isRequired()) + .build())); + } + + @Test + void buildAttributesUpdateActions_WithNewEnumAndOldAsNonEnum_ShouldBuildRemoveAndAddActions() { + // preparation + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of( + EnumAttributeType.of(EnumValue.of("foo", "bar")), "a", ofEnglish("new_label"), true) + .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); + + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of("a", ofEnglish("new_label"), StringAttributeType.of()) + .build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + // assertions + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("a"), - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(newDefinition.getAttributeType(), - newDefinition.getName(), newDefinition.getLabel(), - newDefinition.isRequired()) - .build()) - ); - } - - @Test - void buildAttributesUpdateActions_WithOldLenumAndNewAsNonLenum_ShouldBuildRemoveAndAddActions() { - // preparation - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(StringAttributeType.of(), "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); - - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("new_label"), LocalizedEnumAttributeType - .of(singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar"))))) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - // assertions - assertThat(updateActions).containsExactly( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + newDefinition.getAttributeType(), + newDefinition.getName(), + newDefinition.getLabel(), + newDefinition.isRequired()) + .build())); + } + + @Test + void buildAttributesUpdateActions_WithOldLenumAndNewAsNonLenum_ShouldBuildRemoveAndAddActions() { + // preparation + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of( + StringAttributeType.of(), "a", ofEnglish("new_label"), true) + .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); + + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of( + "a", + ofEnglish("new_label"), + LocalizedEnumAttributeType.of( + singletonList(LocalizedEnumValue.of("foo", ofEnglish("bar"))))) + .build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + // assertions + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("a"), - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(newDefinition.getAttributeType(), - newDefinition.getName(), newDefinition.getLabel(), - newDefinition.isRequired()) - .build()) - ); - } - - @Test - void buildAttributesUpdateActions_WithNewLenumAndOldAsNonLenum_ShouldBuildRemoveAndAddActions() { - // preparation - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(LocalizedEnumAttributeType.of(LocalizedEnumValue.of("foo", ofEnglish("bar"))), - "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) - .build(); - - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("new_label"), StringAttributeType.of()) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = buildAttributesUpdateActions(productType, - productTypeDraft, SYNC_OPTIONS); - - // assertions - assertThat(updateActions).containsExactly( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + newDefinition.getAttributeType(), + newDefinition.getName(), + newDefinition.getLabel(), + newDefinition.isRequired()) + .build())); + } + + @Test + void buildAttributesUpdateActions_WithNewLenumAndOldAsNonLenum_ShouldBuildRemoveAndAddActions() { + // preparation + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of( + LocalizedEnumAttributeType.of(LocalizedEnumValue.of("foo", ofEnglish("bar"))), + "a", + ofEnglish("new_label"), + true) + .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); + + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of("a", ofEnglish("new_label"), StringAttributeType.of()) + .build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + // assertions + assertThat(updateActions) + .containsExactly( RemoveAttributeDefinition.of("a"), - AddAttributeDefinition.of(AttributeDefinitionDraftBuilder - .of(newDefinition.getAttributeType(), - newDefinition.getName(), newDefinition.getLabel(), - newDefinition.isRequired()) - .build()) - ); - } - - @Test - void buildAttributesUpdateActions_WithSetOfLEnumsChangesAndDefLabelChange_ShouldBuildCorrectActions() { - // preparation - final SetAttributeType newSetOfLenumType = SetAttributeType.of( + AddAttributeDefinition.of( + AttributeDefinitionDraftBuilder.of( + newDefinition.getAttributeType(), + newDefinition.getName(), + newDefinition.getLabel(), + newDefinition.isRequired()) + .build())); + } + + @Test + void + buildAttributesUpdateActions_WithSetOfLEnumsChangesAndDefLabelChange_ShouldBuildCorrectActions() { + // preparation + final SetAttributeType newSetOfLenumType = + SetAttributeType.of( LocalizedEnumAttributeType.of( asList( LocalizedEnumValue.of("a", ofEnglish("a")), LocalizedEnumValue.of("b", ofEnglish("newB")), - LocalizedEnumValue.of("c", ofEnglish("c")) - ) - )); + LocalizedEnumValue.of("c", ofEnglish("c"))))); - final AttributeDefinitionDraft newDefinition = AttributeDefinitionDraftBuilder - .of(newSetOfLenumType, "a", ofEnglish("new_label"), true) - .build(); - final ProductTypeDraft productTypeDraft = ProductTypeDraftBuilder - .of("foo", "name", "desc", singletonList(newDefinition)) + final AttributeDefinitionDraft newDefinition = + AttributeDefinitionDraftBuilder.of(newSetOfLenumType, "a", ofEnglish("new_label"), true) .build(); + final ProductTypeDraft productTypeDraft = + ProductTypeDraftBuilder.of("foo", "name", "desc", singletonList(newDefinition)).build(); - final SetAttributeType oldSetOfLenumType = SetAttributeType.of( + final SetAttributeType oldSetOfLenumType = + SetAttributeType.of( LocalizedEnumAttributeType.of( asList( LocalizedEnumValue.of("d", ofEnglish("d")), LocalizedEnumValue.of("b", ofEnglish("b")), - LocalizedEnumValue.of("a", ofEnglish("a")) - ) - )); - - final AttributeDefinition oldDefinition = AttributeDefinitionBuilder - .of("a", ofEnglish("old_label"), oldSetOfLenumType) - .build(); - final ProductType productType = mock(ProductType.class); - when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = - buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); - - // assertion - assertThat(updateActions) - .containsExactlyInAnyOrder( - RemoveEnumValues.ofLocalizedEnumValue("a", LocalizedEnumValue.of("d", ofEnglish("d"))), - ChangeLocalizedEnumValueLabel.of("a", LocalizedEnumValue.of("b", ofEnglish("newB"))), - AddLocalizedEnumValue.of("a", LocalizedEnumValue.of("c", ofEnglish("c"))), - ChangeLocalizedEnumValueOrder.of("a", asList( + LocalizedEnumValue.of("a", ofEnglish("a"))))); + + final AttributeDefinition oldDefinition = + AttributeDefinitionBuilder.of("a", ofEnglish("old_label"), oldSetOfLenumType).build(); + final ProductType productType = mock(ProductType.class); + when(productType.getAttributes()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildAttributesUpdateActions(productType, productTypeDraft, SYNC_OPTIONS); + + // assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( + RemoveEnumValues.ofLocalizedEnumValue("a", LocalizedEnumValue.of("d", ofEnglish("d"))), + ChangeLocalizedEnumValueLabel.of("a", LocalizedEnumValue.of("b", ofEnglish("newB"))), + AddLocalizedEnumValue.of("a", LocalizedEnumValue.of("c", ofEnglish("c"))), + ChangeLocalizedEnumValueOrder.of( + "a", + asList( LocalizedEnumValue.of("a", ofEnglish("a")), LocalizedEnumValue.of("b", ofEnglish("newB")), - LocalizedEnumValue.of("c", ofEnglish("c")) - )), - ChangeAttributeDefinitionLabel.of("a", ofEnglish("new_label"))); - } + LocalizedEnumValue.of("c", ofEnglish("c")))), + ChangeAttributeDefinitionLabel.of("a", ofEnglish("new_label"))); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildLocalizedEnumUpdateActionsTest.java b/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildLocalizedEnumUpdateActionsTest.java index e002ecdda9..f901a26287 100644 --- a/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildLocalizedEnumUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildLocalizedEnumUpdateActionsTest.java @@ -1,5 +1,13 @@ package com.commercetools.sync.producttypes.utils.producttypeactionutils; +import static com.commercetools.sync.commons.utils.LocalizedEnumValueFixtures.*; +import static com.commercetools.sync.producttypes.utils.LocalizedEnumValueUpdateActionUtils.buildLocalizedEnumValueUpdateActions; +import static com.commercetools.sync.producttypes.utils.LocalizedEnumValueUpdateActionUtils.buildLocalizedEnumValuesUpdateActions; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.producttypes.ProductType; @@ -7,270 +15,220 @@ import io.sphere.sdk.producttypes.commands.updateactions.ChangeLocalizedEnumValueLabel; import io.sphere.sdk.producttypes.commands.updateactions.ChangeLocalizedEnumValueOrder; import io.sphere.sdk.producttypes.commands.updateactions.RemoveEnumValues; -import org.junit.jupiter.api.Test; - import java.util.Collections; import java.util.List; - -import static com.commercetools.sync.commons.utils.LocalizedEnumValueFixtures.*; -import static com.commercetools.sync.producttypes.utils.LocalizedEnumValueUpdateActionUtils.buildLocalizedEnumValueUpdateActions; -import static com.commercetools.sync.producttypes.utils.LocalizedEnumValueUpdateActionUtils.buildLocalizedEnumValuesUpdateActions; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.Test; class BuildLocalizedEnumUpdateActionsTest { - @Test - void buildLocalizedEnumUpdateActions_WithNullNewEnumValuesAndExistingEnumValues_ShouldBuildRemoveAction() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - null - ); - - assertThat(updateActions).containsExactly( - RemoveEnumValues.of("attribute_definition_name_1", asList("a", "b", "c")) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithEmptyNewEnumValuesAndExistingEnumValues_ShouldBuildRemoveAction() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - Collections.emptyList() - ); - - assertThat(updateActions).containsExactly( - RemoveEnumValues.of("attribute_definition_name_1", asList("a", "b", "c")) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithEmptyPlainEnumValuesAndNoOldEnumValues_ShouldNotBuildActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - Collections.emptyList(), - Collections.emptyList() - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildLocalizedEnumUpdateActions_WithNewPlainEnumValuesAndNoOldPlainEnumValues_ShouldBuild3AddActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - Collections.emptyList(), - ENUM_VALUES_ABC - ); - - assertThat(updateActions).containsExactly( + @Test + void + buildLocalizedEnumUpdateActions_WithNullNewEnumValuesAndExistingEnumValues_ShouldBuildRemoveAction() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions("attribute_definition_name_1", ENUM_VALUES_ABC, null); + + assertThat(updateActions) + .containsExactly(RemoveEnumValues.of("attribute_definition_name_1", asList("a", "b", "c"))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithEmptyNewEnumValuesAndExistingEnumValues_ShouldBuildRemoveAction() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, Collections.emptyList()); + + assertThat(updateActions) + .containsExactly(RemoveEnumValues.of("attribute_definition_name_1", asList("a", "b", "c"))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithEmptyPlainEnumValuesAndNoOldEnumValues_ShouldNotBuildActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", Collections.emptyList(), Collections.emptyList()); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithNewPlainEnumValuesAndNoOldPlainEnumValues_ShouldBuild3AddActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", Collections.emptyList(), ENUM_VALUES_ABC); + + assertThat(updateActions) + .containsExactly( AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_A), AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_B), - AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_C) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithIdenticalPlainEnum_ShouldNotBuildUpdateActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ABC - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildLocalizedEnumUpdateActions_WithDuplicatePlainEnumValues_ShouldTriggerDuplicateKeyError() { - assertThatThrownBy(() -> buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ABB - )) - .isInstanceOf(DuplicateKeyException.class) - .hasMessage("Enum Values have duplicated keys. Definition name: " + AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_C)); + } + + @Test + void buildLocalizedEnumUpdateActions_WithIdenticalPlainEnum_ShouldNotBuildUpdateActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ABC); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithDuplicatePlainEnumValues_ShouldTriggerDuplicateKeyError() { + assertThatThrownBy( + () -> + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ABB)) + .isInstanceOf(DuplicateKeyException.class) + .hasMessage( + "Enum Values have duplicated keys. Definition name: " + "'attribute_definition_name_1', Duplicated enum value: 'b'. " + "Enum Values are expected to be unique inside their definition."); - } - - @Test - void buildLocalizedEnumUpdateActions_WithOneMissingPlainEnumValue_ShouldBuildRemoveEnumValueAction() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_AB - ); - - assertThat(updateActions).containsExactly( - RemoveEnumValues.of("attribute_definition_name_1", singletonList("c")) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithOnePlainEnumValue_ShouldBuildAddEnumValueAction() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ABCD - ); - - assertThat(updateActions).containsExactly( - AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithOneEnumValueSwitch_ShouldBuildRemoveAndAddEnumValueActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ABD - ); - - assertThat(updateActions).containsExactly( + } + + @Test + void + buildLocalizedEnumUpdateActions_WithOneMissingPlainEnumValue_ShouldBuildRemoveEnumValueAction() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_AB); + + assertThat(updateActions) + .containsExactly(RemoveEnumValues.of("attribute_definition_name_1", singletonList("c"))); + } + + @Test + void buildLocalizedEnumUpdateActions_WithOnePlainEnumValue_ShouldBuildAddEnumValueAction() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ABCD); + + assertThat(updateActions) + .containsExactly(AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D)); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithOneEnumValueSwitch_ShouldBuildRemoveAndAddEnumValueActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ABD); + + assertThat(updateActions) + .containsExactly( RemoveEnumValues.of("attribute_definition_name_1", singletonList("c")), - AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithDifferent_ShouldBuildChangeEnumValueOrderAction() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_CAB - ); - - assertThat(updateActions).containsExactly( - ChangeLocalizedEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_C, - ENUM_VALUE_A, - ENUM_VALUE_B - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithMixedCase_ShouldBuildChangeEnumValueOrderAction() { - final String attributeDefinitionName = "attribute_definition_name_1"; - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - attributeDefinitionName, - ENUM_VALUES_BAC, - ENUM_VALUES_AB_WITH_DIFFERENT_LABEL - ); - - assertThat(updateActions).containsExactly( + AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D)); + } + + @Test + void buildLocalizedEnumUpdateActions_WithDifferent_ShouldBuildChangeEnumValueOrderAction() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_CAB); + + assertThat(updateActions) + .containsExactly( + ChangeLocalizedEnumValueOrder.of( + "attribute_definition_name_1", asList(ENUM_VALUE_C, ENUM_VALUE_A, ENUM_VALUE_B))); + } + + @Test + void buildPlainEnumUpdateActions_WithMixedCase_ShouldBuildChangeEnumValueOrderAction() { + final String attributeDefinitionName = "attribute_definition_name_1"; + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + attributeDefinitionName, ENUM_VALUES_BAC, ENUM_VALUES_AB_WITH_DIFFERENT_LABEL); + + assertThat(updateActions) + .containsExactly( RemoveEnumValues.of(attributeDefinitionName, ENUM_VALUE_C.getKey()), ChangeLocalizedEnumValueLabel.of(attributeDefinitionName, ENUM_VALUE_A_DIFFERENT_LABEL), - ChangeLocalizedEnumValueOrder.of(attributeDefinitionName, asList( - ENUM_VALUE_A_DIFFERENT_LABEL, - ENUM_VALUE_B - )) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_CB - ); - - assertThat(updateActions).containsExactly( + ChangeLocalizedEnumValueOrder.of( + attributeDefinitionName, asList(ENUM_VALUE_A_DIFFERENT_LABEL, ENUM_VALUE_B))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_CB); + + assertThat(updateActions) + .containsExactly( RemoveEnumValues.of("attribute_definition_name_1", singletonList("a")), - ChangeLocalizedEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_C, - ENUM_VALUE_B - )) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ACBD - ); - - assertThat(updateActions).containsExactly( + ChangeLocalizedEnumValueOrder.of( + "attribute_definition_name_1", asList(ENUM_VALUE_C, ENUM_VALUE_B))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ACBD); + + assertThat(updateActions) + .containsExactly( AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D), - ChangeLocalizedEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_A, - ENUM_VALUE_C, - ENUM_VALUE_B, - ENUM_VALUE_D - )) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithAddedEnumValueInBetween_ShouldBuildChangeOrderAndAddActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ADBC - ); - - assertThat(updateActions).containsExactly( + ChangeLocalizedEnumValueOrder.of( + "attribute_definition_name_1", + asList(ENUM_VALUE_A, ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithAddedEnumValueInBetween_ShouldBuildChangeOrderAndAddActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ADBC); + + assertThat(updateActions) + .containsExactly( AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D), - ChangeLocalizedEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_A, - ENUM_VALUE_D, - ENUM_VALUE_B, - ENUM_VALUE_C - )) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveEnumValueActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_CBD - ); - - assertThat(updateActions).containsExactly( + ChangeLocalizedEnumValueOrder.of( + "attribute_definition_name_1", + asList(ENUM_VALUE_A, ENUM_VALUE_D, ENUM_VALUE_B, ENUM_VALUE_C))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveEnumValueActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_CBD); + + assertThat(updateActions) + .containsExactly( RemoveEnumValues.of("attribute_definition_name_1", singletonList("a")), AddLocalizedEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D), - ChangeLocalizedEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_C, - ENUM_VALUE_B, - ENUM_VALUE_D - )) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithDifferentLabels_ShouldReturnChangeLabelAction() { - final List> updateActions = buildLocalizedEnumValueUpdateActions( - "attribute_definition_name_1", - ENUM_VALUE_A, - ENUM_VALUE_A_DIFFERENT_LABEL - ); - - assertThat(updateActions).containsAnyOf( - ChangeLocalizedEnumValueLabel.of("attribute_definition_name_1", ENUM_VALUE_A_DIFFERENT_LABEL) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithSameLabels_ShouldNotReturnChangeLabelAction() { - final List> updateActions = buildLocalizedEnumValueUpdateActions( - "attribute_definition_name_1", - ENUM_VALUE_A, - ENUM_VALUE_A - ); - - assertThat(updateActions).doesNotContain( - ChangeLocalizedEnumValueLabel.of("attribute_definition_name_1", ENUM_VALUE_A) - ); - } + ChangeLocalizedEnumValueOrder.of( + "attribute_definition_name_1", asList(ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D))); + } + + @Test + void buildLocalizedEnumUpdateActions_WithDifferentLabels_ShouldReturnChangeLabelAction() { + final List> updateActions = + buildLocalizedEnumValueUpdateActions( + "attribute_definition_name_1", ENUM_VALUE_A, ENUM_VALUE_A_DIFFERENT_LABEL); + + assertThat(updateActions) + .containsAnyOf( + ChangeLocalizedEnumValueLabel.of( + "attribute_definition_name_1", ENUM_VALUE_A_DIFFERENT_LABEL)); + } + + @Test + void buildLocalizedEnumUpdateActions_WithSameLabels_ShouldNotReturnChangeLabelAction() { + final List> updateActions = + buildLocalizedEnumValueUpdateActions( + "attribute_definition_name_1", ENUM_VALUE_A, ENUM_VALUE_A); + + assertThat(updateActions) + .doesNotContain( + ChangeLocalizedEnumValueLabel.of("attribute_definition_name_1", ENUM_VALUE_A)); + } } diff --git a/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildPlainEnumUpdateActionsTest.java b/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildPlainEnumUpdateActionsTest.java index 770bfac5dc..117acb4677 100644 --- a/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildPlainEnumUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/producttypes/utils/producttypeactionutils/BuildPlainEnumUpdateActionsTest.java @@ -1,5 +1,13 @@ package com.commercetools.sync.producttypes.utils.producttypeactionutils; +import static com.commercetools.sync.commons.utils.PlainEnumValueFixtures.*; +import static com.commercetools.sync.producttypes.utils.PlainEnumValueUpdateActionUtils.buildEnumValueUpdateActions; +import static com.commercetools.sync.producttypes.utils.PlainEnumValueUpdateActionUtils.buildEnumValuesUpdateActions; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.producttypes.ProductType; @@ -7,270 +15,216 @@ import io.sphere.sdk.producttypes.commands.updateactions.ChangeEnumValueOrder; import io.sphere.sdk.producttypes.commands.updateactions.ChangePlainEnumValueLabel; import io.sphere.sdk.producttypes.commands.updateactions.RemoveEnumValues; -import org.junit.jupiter.api.Test; - import java.util.Collections; import java.util.List; - -import static com.commercetools.sync.commons.utils.PlainEnumValueFixtures.*; -import static com.commercetools.sync.producttypes.utils.PlainEnumValueUpdateActionUtils.buildEnumValueUpdateActions; -import static com.commercetools.sync.producttypes.utils.PlainEnumValueUpdateActionUtils.buildEnumValuesUpdateActions; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.Test; class BuildPlainEnumUpdateActionsTest { - @Test - void buildPlainEnumUpdateActions_WithNullNewEnumValuesAndExistingEnumValues_ShouldBuildRemoveAction() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - null - ); - - assertThat(updateActions).containsExactly( - RemoveEnumValues.of("attribute_definition_name_1", asList("a", "b", "c")) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithEmptyNewEnumValuesAndExistingEnumValues_ShouldBuildRemoveAction() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - Collections.emptyList() - ); - - assertThat(updateActions).containsExactly( - RemoveEnumValues.of("attribute_definition_name_1", asList("a", "b", "c")) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithEmptyPlainEnumValuesAndNoOldEnumValues_ShouldNotBuildActions() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - Collections.emptyList(), - Collections.emptyList() - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildPlainEnumUpdateActions_WithNewPlainEnumValuesAndNoOldPlainEnumValues_ShouldBuild3AddActions() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - Collections.emptyList(), - ENUM_VALUES_ABC - ); - - assertThat(updateActions).containsExactly( + @Test + void + buildPlainEnumUpdateActions_WithNullNewEnumValuesAndExistingEnumValues_ShouldBuildRemoveAction() { + final List> updateActions = + buildEnumValuesUpdateActions("attribute_definition_name_1", ENUM_VALUES_ABC, null); + + assertThat(updateActions) + .containsExactly(RemoveEnumValues.of("attribute_definition_name_1", asList("a", "b", "c"))); + } + + @Test + void + buildPlainEnumUpdateActions_WithEmptyNewEnumValuesAndExistingEnumValues_ShouldBuildRemoveAction() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, Collections.emptyList()); + + assertThat(updateActions) + .containsExactly(RemoveEnumValues.of("attribute_definition_name_1", asList("a", "b", "c"))); + } + + @Test + void + buildPlainEnumUpdateActions_WithEmptyPlainEnumValuesAndNoOldEnumValues_ShouldNotBuildActions() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", Collections.emptyList(), Collections.emptyList()); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildPlainEnumUpdateActions_WithNewPlainEnumValuesAndNoOldPlainEnumValues_ShouldBuild3AddActions() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", Collections.emptyList(), ENUM_VALUES_ABC); + + assertThat(updateActions) + .containsExactly( AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_A), AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_B), - AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_C) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithIdenticalPlainEnum_ShouldNotBuildUpdateActions() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ABC - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildPlainEnumUpdateActions_WithDuplicatePlainEnumValues_ShouldTriggerDuplicateKeyError() { - assertThatThrownBy(() -> buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ABB - )) - .isInstanceOf(DuplicateKeyException.class) - .hasMessage("Enum Values have duplicated keys. Definition name: " + AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_C)); + } + + @Test + void buildPlainEnumUpdateActions_WithIdenticalPlainEnum_ShouldNotBuildUpdateActions() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ABC); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildPlainEnumUpdateActions_WithDuplicatePlainEnumValues_ShouldTriggerDuplicateKeyError() { + assertThatThrownBy( + () -> + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ABB)) + .isInstanceOf(DuplicateKeyException.class) + .hasMessage( + "Enum Values have duplicated keys. Definition name: " + "'attribute_definition_name_1', Duplicated enum value: 'b'. " + "Enum Values are expected to be unique inside their definition."); - } - - @Test - void buildPlainEnumUpdateActions_WithOneMissingPlainEnumValue_ShouldBuildRemoveEnumValueAction() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_AB - ); - - assertThat(updateActions).containsExactly( - RemoveEnumValues.of("attribute_definition_name_1", singletonList("c")) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithOnePlainEnumValue_ShouldBuildAddEnumValueAction() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ABCD - ); - - assertThat(updateActions).containsExactly( - AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithOneEnumValueSwitch_ShouldBuildRemoveAndAddEnumValueActions() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ABD - ); - - assertThat(updateActions).containsExactly( + } + + @Test + void buildPlainEnumUpdateActions_WithOneMissingPlainEnumValue_ShouldBuildRemoveEnumValueAction() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_AB); + + assertThat(updateActions) + .containsExactly(RemoveEnumValues.of("attribute_definition_name_1", singletonList("c"))); + } + + @Test + void buildPlainEnumUpdateActions_WithOnePlainEnumValue_ShouldBuildAddEnumValueAction() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ABCD); + + assertThat(updateActions) + .containsExactly(AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D)); + } + + @Test + void + buildPlainEnumUpdateActions_WithOneEnumValueSwitch_ShouldBuildRemoveAndAddEnumValueActions() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ABD); + + assertThat(updateActions) + .containsExactly( RemoveEnumValues.of("attribute_definition_name_1", Collections.singletonList("c")), - AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithDifferent_ShouldBuildChangeEnumValueOrderAction() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_CAB - ); - - assertThat(updateActions).containsExactly( - ChangeEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_C, - ENUM_VALUE_A, - ENUM_VALUE_B - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithMixedCase_ShouldBuildChangeEnumValueOrderAction() { - final String attributeDefinitionName = "attribute_definition_name_1"; - final List> updateActions = buildEnumValuesUpdateActions( - attributeDefinitionName, - ENUM_VALUES_BAC, - ENUM_VALUES_AB_WITH_DIFFERENT_LABEL - ); - - assertThat(updateActions).containsExactly( + AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D)); + } + + @Test + void buildPlainEnumUpdateActions_WithDifferent_ShouldBuildChangeEnumValueOrderAction() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_CAB); + + assertThat(updateActions) + .containsExactly( + ChangeEnumValueOrder.of( + "attribute_definition_name_1", asList(ENUM_VALUE_C, ENUM_VALUE_A, ENUM_VALUE_B))); + } + + @Test + void buildPlainEnumUpdateActions_WithMixedCase_ShouldBuildChangeEnumValueOrderAction() { + final String attributeDefinitionName = "attribute_definition_name_1"; + final List> updateActions = + buildEnumValuesUpdateActions( + attributeDefinitionName, ENUM_VALUES_BAC, ENUM_VALUES_AB_WITH_DIFFERENT_LABEL); + + assertThat(updateActions) + .containsExactly( RemoveEnumValues.of(attributeDefinitionName, ENUM_VALUE_C.getKey()), ChangePlainEnumValueLabel.of(attributeDefinitionName, ENUM_VALUE_A_DIFFERENT_LABEL), - ChangeEnumValueOrder.of(attributeDefinitionName, asList( - ENUM_VALUE_A_DIFFERENT_LABEL, - ENUM_VALUE_B - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_CB - ); - - assertThat(updateActions).containsExactly( + ChangeEnumValueOrder.of( + attributeDefinitionName, asList(ENUM_VALUE_A_DIFFERENT_LABEL, ENUM_VALUE_B))); + } + + @Test + void + buildPlainEnumUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAndRemoveActions() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_CB); + + assertThat(updateActions) + .containsExactly( RemoveEnumValues.of("attribute_definition_name_1", singletonList("a")), - ChangeEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_C, - ENUM_VALUE_B - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ACBD - ); - - assertThat(updateActions).containsExactly( + ChangeEnumValueOrder.of( + "attribute_definition_name_1", asList(ENUM_VALUE_C, ENUM_VALUE_B))); + } + + @Test + void + buildPlainEnumUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ACBD); + + assertThat(updateActions) + .containsExactly( AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D), - ChangeEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_A, - ENUM_VALUE_C, - ENUM_VALUE_B, - ENUM_VALUE_D - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithAddedEnumValueInBetween_ShouldBuildChangeOrderAndAddActions() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_ADBC - ); - - assertThat(updateActions).containsExactly( + ChangeEnumValueOrder.of( + "attribute_definition_name_1", + asList(ENUM_VALUE_A, ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D))); + } + + @Test + void + buildPlainEnumUpdateActions_WithAddedEnumValueInBetween_ShouldBuildChangeOrderAndAddActions() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_ADBC); + + assertThat(updateActions) + .containsExactly( AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D), - ChangeEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_A, - ENUM_VALUE_D, - ENUM_VALUE_B, - ENUM_VALUE_C - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveEnumValueActions() { - final List> updateActions = buildEnumValuesUpdateActions( - "attribute_definition_name_1", - ENUM_VALUES_ABC, - ENUM_VALUES_CBD - ); - - assertThat(updateActions).containsExactly( + ChangeEnumValueOrder.of( + "attribute_definition_name_1", + asList(ENUM_VALUE_A, ENUM_VALUE_D, ENUM_VALUE_B, ENUM_VALUE_C))); + } + + @Test + void + buildPlainEnumUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAllThreeMoveEnumValueActions() { + final List> updateActions = + buildEnumValuesUpdateActions( + "attribute_definition_name_1", ENUM_VALUES_ABC, ENUM_VALUES_CBD); + + assertThat(updateActions) + .containsExactly( RemoveEnumValues.of("attribute_definition_name_1", Collections.singletonList("a")), AddEnumValue.of("attribute_definition_name_1", ENUM_VALUE_D), - ChangeEnumValueOrder.of("attribute_definition_name_1", asList( - ENUM_VALUE_C, - ENUM_VALUE_B, - ENUM_VALUE_D - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithDifferentLabels_ShouldReturnChangeLabelAction() { - final List> updateActions = buildEnumValueUpdateActions( - "attribute_definition_name_1", - ENUM_VALUE_A, - ENUM_VALUE_A_DIFFERENT_LABEL - ); - - assertThat(updateActions).containsAnyOf( - ChangePlainEnumValueLabel.of("attribute_definition_name_1", ENUM_VALUE_A_DIFFERENT_LABEL) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithSameLabels_ShouldNotReturnChangeLabelAction() { - final List> updateActions = buildEnumValueUpdateActions( - "attribute_definition_name_1", - ENUM_VALUE_A, - ENUM_VALUE_A - ); - - assertThat(updateActions).doesNotContain( - ChangePlainEnumValueLabel.of("attribute_definition_name_1", ENUM_VALUE_A) - ); - } + ChangeEnumValueOrder.of( + "attribute_definition_name_1", asList(ENUM_VALUE_C, ENUM_VALUE_B, ENUM_VALUE_D))); + } + + @Test + void buildPlainEnumUpdateActions_WithDifferentLabels_ShouldReturnChangeLabelAction() { + final List> updateActions = + buildEnumValueUpdateActions( + "attribute_definition_name_1", ENUM_VALUE_A, ENUM_VALUE_A_DIFFERENT_LABEL); + + assertThat(updateActions) + .containsAnyOf( + ChangePlainEnumValueLabel.of( + "attribute_definition_name_1", ENUM_VALUE_A_DIFFERENT_LABEL)); + } + + @Test + void buildPlainEnumUpdateActions_WithSameLabels_ShouldNotReturnChangeLabelAction() { + final List> updateActions = + buildEnumValueUpdateActions("attribute_definition_name_1", ENUM_VALUE_A, ENUM_VALUE_A); + + assertThat(updateActions) + .doesNotContain(ChangePlainEnumValueLabel.of("attribute_definition_name_1", ENUM_VALUE_A)); + } } diff --git a/src/test/java/com/commercetools/sync/services/impl/BaseServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/BaseServiceImplTest.java index 918f5fd51c..eb2ce1f0c9 100644 --- a/src/test/java/com/commercetools/sync/services/impl/BaseServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/BaseServiceImplTest.java @@ -1,5 +1,19 @@ package com.commercetools.sync.services.impl; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.helpers.ResourceKeyIdGraphQlRequest; import com.commercetools.sync.commons.models.ResourceKeyId; @@ -20,15 +34,6 @@ import io.sphere.sdk.products.queries.ProductQuery; import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.apache.commons.lang3.RandomStringUtils; -import org.assertj.core.data.MapEntry; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.ValueSource; - import java.util.Arrays; import java.util.HashSet; import java.util.Map; @@ -38,380 +43,391 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; - -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.RandomStringUtils; +import org.assertj.core.data.MapEntry; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; class BaseServiceImplTest { - @SuppressWarnings("unchecked") - private TriConsumer, Optional> warningCallback - = mock(TriConsumer.class); - private SphereClient client = mock(SphereClient.class); - private ProductService service; - - @BeforeEach - void setup() { - final ProductSyncOptions syncOptions = - ProductSyncOptionsBuilder - .of(client) - .warningCallback(warningCallback) - .batchSize(20) - .cacheSize(2) - .build(); - service = new ProductServiceImpl(syncOptions); - } - - @AfterEach - void cleanup() { - reset(client, warningCallback); - } - - @ParameterizedTest - @NullAndEmptySource - @ValueSource(strings = {" "}) - void fetchCachedResourceId_WithBlankKey_ShouldMakeNoRequestAndReturnEmptyOptional(final String key) { - //test - final Optional result = service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); - - //assertions - assertThat(result).isEmpty(); - verify(client, never()).execute(any()); - } - - @Test - void fetchCachedResourceId_WithFetchResourceWithKey_ShouldReturnResourceId() { - //preparation - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - final Product mockProductResult = mock(Product.class); - final String key = "testKey"; - final String id = "testId"; - when(mockProductResult.getKey()).thenReturn(key); - when(mockProductResult.getId()).thenReturn(id); - when(pagedQueryResult.getResults()).thenReturn(singletonList(mockProductResult)); - - when(client.execute(any())).thenReturn(completedFuture(pagedQueryResult)); - - //test - final Optional result = service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); - - //assertions - assertThat(result).contains(id); - } - - @Test - void fetchCachedResourceId_WithCachedResource_ShouldReturnResourceIdWithoutMakingRequest() { - //preparation - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - final Product mockProductResult = mock(Product.class); - final String key = "testKey"; - final String id = "testId"; - when(mockProductResult.getKey()).thenReturn(key); - when(mockProductResult.getId()).thenReturn(id); - when(pagedQueryResult.getResults()).thenReturn(singletonList(mockProductResult)); - when(client.execute(any())).thenReturn(completedFuture(pagedQueryResult)); - service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); - - //test - final Optional result = service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); - - //assertions - assertThat(result).contains(id); - // only 1 request of the first fetch, but no more since second time it gets it from cache. - verify(client, times(1)).execute(any(ProductQuery.class)); - } - - @Test - void fetchMatchingResources_WithEmptyKeySet_ShouldFetchAndCacheNothing() { - //test - final Set resources = service - .fetchMatchingProductsByKeys(new HashSet<>()) + @SuppressWarnings("unchecked") + private TriConsumer, Optional> warningCallback = + mock(TriConsumer.class); + + private SphereClient client = mock(SphereClient.class); + private ProductService service; + + @BeforeEach + void setup() { + final ProductSyncOptions syncOptions = + ProductSyncOptionsBuilder.of(client) + .warningCallback(warningCallback) + .batchSize(20) + .cacheSize(2) + .build(); + service = new ProductServiceImpl(syncOptions); + } + + @AfterEach + void cleanup() { + reset(client, warningCallback); + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" "}) + void fetchCachedResourceId_WithBlankKey_ShouldMakeNoRequestAndReturnEmptyOptional( + final String key) { + // test + final Optional result = service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); + + // assertions + assertThat(result).isEmpty(); + verify(client, never()).execute(any()); + } + + @Test + void fetchCachedResourceId_WithFetchResourceWithKey_ShouldReturnResourceId() { + // preparation + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + final Product mockProductResult = mock(Product.class); + final String key = "testKey"; + final String id = "testId"; + when(mockProductResult.getKey()).thenReturn(key); + when(mockProductResult.getId()).thenReturn(id); + when(pagedQueryResult.getResults()).thenReturn(singletonList(mockProductResult)); + + when(client.execute(any())).thenReturn(completedFuture(pagedQueryResult)); + + // test + final Optional result = service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); + + // assertions + assertThat(result).contains(id); + } + + @Test + void fetchCachedResourceId_WithCachedResource_ShouldReturnResourceIdWithoutMakingRequest() { + // preparation + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + final Product mockProductResult = mock(Product.class); + final String key = "testKey"; + final String id = "testId"; + when(mockProductResult.getKey()).thenReturn(key); + when(mockProductResult.getId()).thenReturn(id); + when(pagedQueryResult.getResults()).thenReturn(singletonList(mockProductResult)); + when(client.execute(any())).thenReturn(completedFuture(pagedQueryResult)); + service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); + + // test + final Optional result = service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); + + // assertions + assertThat(result).contains(id); + // only 1 request of the first fetch, but no more since second time it gets it from cache. + verify(client, times(1)).execute(any(ProductQuery.class)); + } + + @Test + void fetchMatchingResources_WithEmptyKeySet_ShouldFetchAndCacheNothing() { + // test + final Set resources = + service.fetchMatchingProductsByKeys(new HashSet<>()).toCompletableFuture().join(); + + // assertions + assertThat(resources).isEmpty(); + verify(client, never()).execute(any(ProductQuery.class)); + } + + @SuppressWarnings("unchecked") + @Test + void fetchMatchingResources_WithKeySet_ShouldFetchResourcesAndCacheKeys() { + // preparation + final String key1 = RandomStringUtils.random(15); + final String key2 = RandomStringUtils.random(15); + + final HashSet resourceKeys = new HashSet<>(); + resourceKeys.add(key1); + resourceKeys.add(key2); + + final Product mock1 = mock(Product.class); + when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock1.getKey()).thenReturn(key1); + + final Product mock2 = mock(Product.class); + when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock2.getKey()).thenReturn(key2); + + final PagedQueryResult result = mock(PagedQueryResult.class); + when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); + + when(client.execute(any(ProductQuery.class))).thenReturn(completedFuture(result)); + + // test fetch + final Set resources = + service.fetchMatchingProductsByKeys(resourceKeys).toCompletableFuture().join(); + + // assertions + assertThat(resources).containsExactlyInAnyOrder(mock1, mock2); + verify(client, times(1)).execute(any(ProductQuery.class)); + + // test caching + final Optional cachedKey1 = + service.getIdFromCacheOrFetch(mock1.getKey()).toCompletableFuture().join(); + + final Optional cachedKey2 = + service.getIdFromCacheOrFetch(mock2.getKey()).toCompletableFuture().join(); + + // assertions + assertThat(cachedKey1).contains(mock1.getId()); + assertThat(cachedKey2).contains(mock2.getId()); + // still 1 request from the first #fetchMatchingProductsByKeys call + verify(client, times(1)).execute(any(ProductQuery.class)); + } + + @Test + void fetchMatchingResources_WithBadGateWayException_ShouldCompleteExceptionally() { + // preparation + final String key1 = RandomStringUtils.random(15); + final String key2 = RandomStringUtils.random(15); + + final HashSet resourceKeys = new HashSet<>(); + resourceKeys.add(key1); + resourceKeys.add(key2); + + when(client.execute(any(ProductQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); + + // test + final CompletionStage> result = service.fetchMatchingProductsByKeys(resourceKeys); + + // assertions + assertThat(result).hasFailedWithThrowableThat().isExactlyInstanceOf(BadGatewayException.class); + verify(client).execute(any(ProductQuery.class)); + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" "}) + void fetchResource_WithBlankKey_ShouldMakeNoRequestAndReturnEmptyOptional(final String key) { + // test + final Optional optional = service.fetchProduct(key).toCompletableFuture().join(); + + // assertions + assertThat(optional).isEmpty(); + verify(client, never()).execute(any()); + } + + @SuppressWarnings("unchecked") + @Test + void fetchResource_WithKey_ShouldFetchResource() { + // preparation + final String resourceId = RandomStringUtils.random(15); + final String resourceKey = RandomStringUtils.random(15); + + final Product mockProductResult = mock(Product.class); + when(mockProductResult.getKey()).thenReturn(resourceKey); + when(mockProductResult.getId()).thenReturn(resourceId); + + final PagedQueryResult result = mock(PagedQueryResult.class); + when(result.head()).thenReturn(Optional.of(mockProductResult)); + + when(client.execute(any())).thenReturn(completedFuture(result)); + + // test + final Optional resourceOptional = + service.fetchProduct(resourceKey).toCompletableFuture().join(); + + // assertions + assertThat(resourceOptional).containsSame(mockProductResult); + verify(client).execute(any(ProductQuery.class)); + } + + @Test + void fetchResource_WithBadGateWayException_ShouldCompleteExceptionally() { + // preparation + when(client.execute(any(ProductQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); + + // test + final CompletionStage> result = service.fetchProduct("foo"); + + // assertions + assertThat(result).hasFailedWithThrowableThat().isExactlyInstanceOf(BadGatewayException.class); + verify(client, times(1)).execute(any(ProductQuery.class)); + } + + @Test + void cacheKeysToIdsUsingGraphQl_WithEmptySetOfKeys_ShouldMakeNoRequestAndReturnEmptyOptional() { + // test + final Map optional = + service.cacheKeysToIds(emptySet()).toCompletableFuture().join(); + + // assertions + assertThat(optional).isEmpty(); + verify(client, never()).execute(any()); + } + + @Test + void cacheKeysToIdsUsingGraphQl_WithAllCachedKeys_ShouldMakeNoRequestAndReturnCachedEntry() { + // preparation + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + final Product mockProductResult = mock(Product.class); + final String key = "testKey"; + final String id = "testId"; + when(mockProductResult.getKey()).thenReturn(key); + when(mockProductResult.getId()).thenReturn(id); + when(pagedQueryResult.getResults()).thenReturn(singletonList(mockProductResult)); + when(client.execute(any())).thenReturn(completedFuture(pagedQueryResult)); + service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); + + // test + final Map optional = + service.cacheKeysToIds(singleton("testKey")).toCompletableFuture().join(); + + // assertions + assertThat(optional).containsExactly(MapEntry.entry(key, id)); + verify(client, times(1)).execute(any(ProductQuery.class)); + } + + @Test + void cacheKeysToIds_WithCachedKeysExceedingCacheSize_ShouldNotReturnLeastUsedKeys() { + // preparation + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + final Product product1 = mock(Product.class); + when(product1.getKey()).thenReturn("key-1"); + when(product1.getId()).thenReturn("id-1"); + final Product product2 = mock(Product.class); + when(product2.getKey()).thenReturn("key-2"); + when(product2.getId()).thenReturn("id-2"); + when(pagedQueryResult.getResults()).thenReturn(Arrays.asList(product1, product2)); + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = + mock(ResourceKeyIdGraphQlResult.class); + final ResourceKeyId resourceKeyId = mock(ResourceKeyId.class); + when(resourceKeyId.getKey()).thenReturn("testKey"); + when(resourceKeyId.getId()).thenReturn("testId"); + when(resourceKeyIdGraphQlResult.getResults()).thenReturn(singleton(resourceKeyId)); + when(client.execute(any())) + .thenReturn(completedFuture(pagedQueryResult)) + .thenReturn(completedFuture(resourceKeyIdGraphQlResult)); + service.fetchMatchingProductsByKeys( + Arrays.asList("key-1", "key-2").stream().collect(Collectors.toSet())); + service.getIdFromCacheOrFetch("key-1"); // access the first added cache entry + + // test + final Map optional = + service.cacheKeysToIds(singleton("testKey")).toCompletableFuture().join(); + + // assertions + assertThat(optional) + .containsExactly(MapEntry.entry("key-1", "id-1"), MapEntry.entry("testKey", "testId")); + verify(client, times(1)).execute(any(ProductQuery.class)); + verify(client, times(1)).execute(any(ResourceKeyIdGraphQlRequest.class)); + } + + @Test + void cacheKeysToIdsUsingGraphQl_WithNoCachedKeys_ShouldMakeRequestAndReturnCachedEntry() { + // preparation + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = + mock(ResourceKeyIdGraphQlResult.class); + final ResourceKeyId mockResourceKeyId = mock(ResourceKeyId.class); + final String key = "testKey"; + final String id = "testId"; + when(mockResourceKeyId.getKey()).thenReturn(key); + when(mockResourceKeyId.getId()).thenReturn(id); + when(resourceKeyIdGraphQlResult.getResults()).thenReturn(singleton(mockResourceKeyId)); + when(client.execute(any())).thenReturn(completedFuture(resourceKeyIdGraphQlResult)); + + // test + final Map optional = + service.cacheKeysToIds(singleton("testKey")).toCompletableFuture().join(); + + // assertions + assertThat(optional).containsExactly(MapEntry.entry(key, id)); + verify(client, times(1)).execute(any(ResourceKeyIdGraphQlRequest.class)); + } + + @Test + void cacheKeysToIdsUsingGraphQl_WithBadGateWayException_ShouldCompleteExceptionally() { + // preparation + final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = + mock(ResourceKeyIdGraphQlResult.class); + final ResourceKeyId mockResourceKeyId = mock(ResourceKeyId.class); + final String key = "testKey"; + final String id = "testId"; + when(mockResourceKeyId.getKey()).thenReturn(key); + when(mockResourceKeyId.getId()).thenReturn(id); + when(resourceKeyIdGraphQlResult.getResults()).thenReturn(singleton(mockResourceKeyId)); + when(client.execute(any(ResourceKeyIdGraphQlRequest.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); + + // test + final CompletionStage> result = + service.cacheKeysToIds(singleton("testKey")); + + // assertions + assertThat(result) + .failsWithin(1, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) + .withCauseExactlyInstanceOf(BadGatewayException.class); + verify(client, times(1)).execute(any(ResourceKeyIdGraphQlRequest.class)); + } + + @Test + void cacheKeysToIds_WithEmptySetOfKeys_ShouldNotMakeRequestAndReturnEmpty() { + // preparation + CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(client).build(); + CustomObjectServiceImpl serviceImpl = new CustomObjectServiceImpl(customObjectSyncOptions); + + // test + final Map optional = + serviceImpl.cacheKeysToIds(emptySet()).toCompletableFuture().join(); + + // assertions + assertThat(optional).isEmpty(); + verify(client, never()).execute(any()); + } + + @Test + void cacheKeysToIds_WithEmptyCache_ShouldMakeRequestAndReturnCacheEntries() { + // preparation + final CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(client).build(); + final CustomObjectServiceImpl serviceImpl = + new CustomObjectServiceImpl(customObjectSyncOptions); + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + final CustomObject customObject = mock(CustomObject.class); + final String customObjectId = "customObjectId"; + final String customObjectContainer = "customObjectContainer"; + final String customObjectKey = "customObjectKey"; + + when(customObject.getId()).thenReturn(customObjectId); + when(customObject.getKey()).thenReturn(customObjectKey); + when(customObject.getContainer()).thenReturn(customObjectContainer); + when(pagedQueryResult.getResults()).thenReturn(singletonList(customObject)); + when(client.execute(any())).thenReturn(completedFuture(pagedQueryResult)); + + final Map result = + serviceImpl + .cacheKeysToIds( + singleton( + CustomObjectCompositeIdentifier.of(customObjectKey, customObjectContainer))) .toCompletableFuture() .join(); - //assertions - assertThat(resources).isEmpty(); - verify(client, never()).execute(any(ProductQuery.class)); - } - - @SuppressWarnings("unchecked") - @Test - void fetchMatchingResources_WithKeySet_ShouldFetchResourcesAndCacheKeys() { - //preparation - final String key1 = RandomStringUtils.random(15); - final String key2 = RandomStringUtils.random(15); - - final HashSet resourceKeys = new HashSet<>(); - resourceKeys.add(key1); - resourceKeys.add(key2); - - final Product mock1 = mock(Product.class); - when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock1.getKey()).thenReturn(key1); - - final Product mock2 = mock(Product.class); - when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock2.getKey()).thenReturn(key2); - - final PagedQueryResult result = mock(PagedQueryResult.class); - when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); - - when(client.execute(any(ProductQuery.class))).thenReturn(completedFuture(result)); - - //test fetch - final Set resources = service - .fetchMatchingProductsByKeys(resourceKeys) - .toCompletableFuture().join(); - - //assertions - assertThat(resources).containsExactlyInAnyOrder(mock1, mock2); - verify(client, times(1)).execute(any(ProductQuery.class)); - - //test caching - final Optional cachedKey1 = service - .getIdFromCacheOrFetch(mock1.getKey()) - .toCompletableFuture().join(); - - final Optional cachedKey2 = service - .getIdFromCacheOrFetch(mock2.getKey()) - .toCompletableFuture().join(); - - //assertions - assertThat(cachedKey1).contains(mock1.getId()); - assertThat(cachedKey2).contains(mock2.getId()); - // still 1 request from the first #fetchMatchingProductsByKeys call - verify(client, times(1)).execute(any(ProductQuery.class)); - } - - @Test - void fetchMatchingResources_WithBadGateWayException_ShouldCompleteExceptionally() { - //preparation - final String key1 = RandomStringUtils.random(15); - final String key2 = RandomStringUtils.random(15); - - final HashSet resourceKeys = new HashSet<>(); - resourceKeys.add(key1); - resourceKeys.add(key2); - - when(client.execute(any(ProductQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); - - //test - final CompletionStage> result = service.fetchMatchingProductsByKeys(resourceKeys); - - //assertions - assertThat(result).hasFailedWithThrowableThat().isExactlyInstanceOf(BadGatewayException.class); - verify(client).execute(any(ProductQuery.class)); - } - - @ParameterizedTest - @NullAndEmptySource - @ValueSource(strings = {" "}) - void fetchResource_WithBlankKey_ShouldMakeNoRequestAndReturnEmptyOptional(final String key) { - //test - final Optional optional = service.fetchProduct(key).toCompletableFuture().join(); - - //assertions - assertThat(optional).isEmpty(); - verify(client, never()).execute(any()); - } - - @SuppressWarnings("unchecked") - @Test - void fetchResource_WithKey_ShouldFetchResource() { - //preparation - final String resourceId = RandomStringUtils.random(15); - final String resourceKey = RandomStringUtils.random(15); - - final Product mockProductResult = mock(Product.class); - when(mockProductResult.getKey()).thenReturn(resourceKey); - when(mockProductResult.getId()).thenReturn(resourceId); - - final PagedQueryResult result = mock(PagedQueryResult.class); - when(result.head()).thenReturn(Optional.of(mockProductResult)); - - when(client.execute(any())).thenReturn(completedFuture(result)); - - //test - final Optional resourceOptional = service.fetchProduct(resourceKey).toCompletableFuture().join(); - - //assertions - assertThat(resourceOptional).containsSame(mockProductResult); - verify(client).execute(any(ProductQuery.class)); - } - - @Test - void fetchResource_WithBadGateWayException_ShouldCompleteExceptionally() { - //preparation - when(client.execute(any(ProductQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); - - //test - final CompletionStage> result = service.fetchProduct("foo"); - - //assertions - assertThat(result).hasFailedWithThrowableThat().isExactlyInstanceOf(BadGatewayException.class); - verify(client, times(1)).execute(any(ProductQuery.class)); - } - - @Test - void cacheKeysToIdsUsingGraphQl_WithEmptySetOfKeys_ShouldMakeNoRequestAndReturnEmptyOptional() { - //test - final Map optional = service.cacheKeysToIds(emptySet()).toCompletableFuture().join(); - - //assertions - assertThat(optional).isEmpty(); - verify(client, never()).execute(any()); - } - - @Test - void cacheKeysToIdsUsingGraphQl_WithAllCachedKeys_ShouldMakeNoRequestAndReturnCachedEntry() { - //preparation - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - final Product mockProductResult = mock(Product.class); - final String key = "testKey"; - final String id = "testId"; - when(mockProductResult.getKey()).thenReturn(key); - when(mockProductResult.getId()).thenReturn(id); - when(pagedQueryResult.getResults()).thenReturn(singletonList(mockProductResult)); - when(client.execute(any())).thenReturn(completedFuture(pagedQueryResult)); - service.getIdFromCacheOrFetch(key).toCompletableFuture().join(); - - //test - final Map optional = service.cacheKeysToIds(singleton("testKey")).toCompletableFuture().join(); - - //assertions - assertThat(optional).containsExactly(MapEntry.entry(key, id)); - verify(client, times(1)).execute(any(ProductQuery.class)); - } - - @Test - void cacheKeysToIds_WithCachedKeysExceedingCacheSize_ShouldNotReturnLeastUsedKeys() { - //preparation - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - final Product product1 = mock(Product.class); - when(product1.getKey()).thenReturn("key-1"); - when(product1.getId()).thenReturn("id-1"); - final Product product2 = mock(Product.class); - when(product2.getKey()).thenReturn("key-2"); - when(product2.getId()).thenReturn("id-2"); - when(pagedQueryResult.getResults()).thenReturn(Arrays.asList(product1, product2)); - final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); - final ResourceKeyId resourceKeyId = mock(ResourceKeyId.class); - when(resourceKeyId.getKey()).thenReturn("testKey"); - when(resourceKeyId.getId()).thenReturn("testId"); - when(resourceKeyIdGraphQlResult.getResults()).thenReturn(singleton(resourceKeyId)); - when(client.execute(any())) - .thenReturn(completedFuture(pagedQueryResult)) - .thenReturn(completedFuture(resourceKeyIdGraphQlResult)); - service.fetchMatchingProductsByKeys(Arrays.asList("key-1", "key-2").stream().collect(Collectors.toSet())); - service.getIdFromCacheOrFetch("key-1"); //access the first added cache entry - - //test - final Map optional = service.cacheKeysToIds(singleton("testKey")).toCompletableFuture().join(); - - //assertions - assertThat(optional).containsExactly(MapEntry.entry("key-1", "id-1"), MapEntry.entry("testKey", "testId")); - verify(client, times(1)).execute(any(ProductQuery.class)); - verify(client, times(1)).execute(any(ResourceKeyIdGraphQlRequest.class)); - } - - @Test - void cacheKeysToIdsUsingGraphQl_WithNoCachedKeys_ShouldMakeRequestAndReturnCachedEntry() { - //preparation - final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); - final ResourceKeyId mockResourceKeyId = mock(ResourceKeyId.class); - final String key = "testKey"; - final String id = "testId"; - when(mockResourceKeyId.getKey()).thenReturn(key); - when(mockResourceKeyId.getId()).thenReturn(id); - when(resourceKeyIdGraphQlResult.getResults()).thenReturn(singleton(mockResourceKeyId)); - when(client.execute(any())).thenReturn(completedFuture(resourceKeyIdGraphQlResult)); - - //test - final Map optional = service.cacheKeysToIds(singleton("testKey")).toCompletableFuture().join(); - - //assertions - assertThat(optional).containsExactly(MapEntry.entry(key, id)); - verify(client, times(1)).execute(any(ResourceKeyIdGraphQlRequest.class)); - } - - @Test - void cacheKeysToIdsUsingGraphQl_WithBadGateWayException_ShouldCompleteExceptionally() { - //preparation - final ResourceKeyIdGraphQlResult resourceKeyIdGraphQlResult = mock(ResourceKeyIdGraphQlResult.class); - final ResourceKeyId mockResourceKeyId = mock(ResourceKeyId.class); - final String key = "testKey"; - final String id = "testId"; - when(mockResourceKeyId.getKey()).thenReturn(key); - when(mockResourceKeyId.getId()).thenReturn(id); - when(resourceKeyIdGraphQlResult.getResults()).thenReturn(singleton(mockResourceKeyId)); - when(client.execute(any(ResourceKeyIdGraphQlRequest.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); - - //test - final CompletionStage> result = service.cacheKeysToIds(singleton("testKey")); - - //assertions - assertThat(result).failsWithin(1, TimeUnit.SECONDS) - .withThrowableOfType(ExecutionException.class) - .withCauseExactlyInstanceOf(BadGatewayException.class); - verify(client, times(1)).execute(any(ResourceKeyIdGraphQlRequest.class)); - } - - @Test - void cacheKeysToIds_WithEmptySetOfKeys_ShouldNotMakeRequestAndReturnEmpty() { - //preparation - CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(client).build(); - CustomObjectServiceImpl serviceImpl = new CustomObjectServiceImpl(customObjectSyncOptions); - - //test - final Map optional = serviceImpl.cacheKeysToIds(emptySet()).toCompletableFuture().join(); - - //assertions - assertThat(optional).isEmpty(); - verify(client, never()).execute(any()); - } - - @Test - void cacheKeysToIds_WithEmptyCache_ShouldMakeRequestAndReturnCacheEntries() { - //preparation - final CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(client).build(); - final CustomObjectServiceImpl serviceImpl = new CustomObjectServiceImpl(customObjectSyncOptions); - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - final CustomObject customObject = mock(CustomObject.class); - final String customObjectId = "customObjectId"; - final String customObjectContainer = "customObjectContainer"; - final String customObjectKey = "customObjectKey"; - - when(customObject.getId()).thenReturn(customObjectId); - when(customObject.getKey()).thenReturn(customObjectKey); - when(customObject.getContainer()).thenReturn(customObjectContainer); - when(pagedQueryResult.getResults()).thenReturn(singletonList(customObject)); - when(client.execute(any())).thenReturn(completedFuture(pagedQueryResult)); - - - final Map result = serviceImpl - .cacheKeysToIds(singleton(CustomObjectCompositeIdentifier.of(customObjectKey, customObjectContainer))) - .toCompletableFuture().join(); - - assertAll( - () -> assertThat(result).hasSize(1), - () -> assertThat(result.get( - CustomObjectCompositeIdentifier.of(customObjectKey, customObjectContainer).toString()) - ).isEqualTo(customObjectId) - ); - verify(client).execute(any(CustomObjectQuery.class)); - } + assertAll( + () -> assertThat(result).hasSize(1), + () -> + assertThat( + result.get( + CustomObjectCompositeIdentifier.of(customObjectKey, customObjectContainer) + .toString())) + .isEqualTo(customObjectId)); + verify(client).execute(any(CustomObjectQuery.class)); + } } diff --git a/src/test/java/com/commercetools/sync/services/impl/CartDiscountServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/CartDiscountServiceImplTest.java index b4e24aa749..81a80ea175 100644 --- a/src/test/java/com/commercetools/sync/services/impl/CartDiscountServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/CartDiscountServiceImplTest.java @@ -1,5 +1,17 @@ package com.commercetools.sync.services.impl; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.internal.verification.VerificationModeFactory.only; + import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptions; import com.commercetools.sync.cartdiscounts.CartDiscountSyncOptionsBuilder; import com.commercetools.sync.commons.exceptions.SyncException; @@ -15,225 +27,224 @@ import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletionStage; - -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.internal.verification.VerificationModeFactory.only; +import org.junit.jupiter.api.Test; class CartDiscountServiceImplTest { - @Test - void fetchCartDiscount_WithEmptyKey_ShouldNotFetchAnyCartDiscount() { - // preparation - final SphereClient sphereClient = mock(SphereClient.class); - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(sphereClient) - .build(); - final CartDiscountService cartDiscountService = new CartDiscountServiceImpl(cartDiscountSyncOptions); - - // test - final CompletionStage> result = cartDiscountService.fetchCartDiscount(""); - - - // assertions - assertThat(result).isCompletedWithValue(Optional.empty()); - verify(sphereClient, never()).execute(any()); - } - - @Test - void fetchCartDiscount_WithNullKey_ShouldNotFetchAnyCartDiscount() { - // preparation - final SphereClient sphereClient = mock(SphereClient.class); - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(sphereClient) - .build(); - final CartDiscountService cartDiscountService = new CartDiscountServiceImpl(cartDiscountSyncOptions); - - // test - final CompletionStage> result = cartDiscountService.fetchCartDiscount(null); - - - // assertions - assertThat(result).isCompletedWithValue(Optional.empty()); - verify(sphereClient, never()).execute(any()); - } - - @Test - void fetchCartDiscount_WithValidKey_ShouldReturnMockCartDiscount() { - // preparation - final SphereClient sphereClient = mock(SphereClient.class); - final CartDiscount mockCartDiscount = mock(CartDiscount.class); - when(mockCartDiscount.getId()).thenReturn("testId"); - when(mockCartDiscount.getKey()).thenReturn("any_key"); - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(sphereClient) - .build(); - final CartDiscountService cartDiscountService = new CartDiscountServiceImpl(cartDiscountSyncOptions); - - @SuppressWarnings("unchecked") - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - when(pagedQueryResult.head()).thenReturn(Optional.of(mockCartDiscount)); - when(cartDiscountSyncOptions.getCtpClient().execute(any(CartDiscountQuery.class))) - .thenReturn(completedFuture(pagedQueryResult)); - - // test - final CompletionStage> result = - cartDiscountService.fetchCartDiscount("any_key"); - - // assertions - assertThat(result).isCompletedWithValue(Optional.of(mockCartDiscount)); - verify(sphereClient, only()).execute(any()); - } - - @Test - void createCartDiscount_WithNullCartDiscountKey_ShouldNotCreateCartDiscount() { - // preparation - final CartDiscountDraft mockCartDiscountDraft = mock(CartDiscountDraft.class); - final Map errors = new HashMap<>(); - when(mockCartDiscountDraft.getKey()).thenReturn(null); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> - errors.put(exception.getMessage(), exception)) - .build(); - final CartDiscountService cartDiscountService = new CartDiscountServiceImpl(cartDiscountSyncOptions); - - // test - final CompletionStage> result = cartDiscountService - .createCartDiscount(mockCartDiscountDraft); - - // assertions - assertThat(result).isCompletedWithValue(Optional.empty()); - assertThat(errors.keySet()) - .containsExactly("Failed to create draft with key: 'null'. Reason: Draft key is blank!"); - verify(cartDiscountSyncOptions.getCtpClient(), times(0)).execute(any()); - } - - @Test - void createCartDiscount_WithEmptyCartDiscountKey_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final SphereClient sphereClient = mock(SphereClient.class); - final CartDiscountDraft mockCartDiscountDraft = mock(CartDiscountDraft.class); - final Map errors = new HashMap<>(); - when(mockCartDiscountDraft.getKey()).thenReturn(""); - - final CartDiscountSyncOptions options = CartDiscountSyncOptionsBuilder - .of(sphereClient) - .errorCallback((exception, oldResource, newResource, actions) -> + @Test + void fetchCartDiscount_WithEmptyKey_ShouldNotFetchAnyCartDiscount() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(sphereClient).build(); + final CartDiscountService cartDiscountService = + new CartDiscountServiceImpl(cartDiscountSyncOptions); + + // test + final CompletionStage> result = + cartDiscountService.fetchCartDiscount(""); + + // assertions + assertThat(result).isCompletedWithValue(Optional.empty()); + verify(sphereClient, never()).execute(any()); + } + + @Test + void fetchCartDiscount_WithNullKey_ShouldNotFetchAnyCartDiscount() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(sphereClient).build(); + final CartDiscountService cartDiscountService = + new CartDiscountServiceImpl(cartDiscountSyncOptions); + + // test + final CompletionStage> result = + cartDiscountService.fetchCartDiscount(null); + + // assertions + assertThat(result).isCompletedWithValue(Optional.empty()); + verify(sphereClient, never()).execute(any()); + } + + @Test + void fetchCartDiscount_WithValidKey_ShouldReturnMockCartDiscount() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final CartDiscount mockCartDiscount = mock(CartDiscount.class); + when(mockCartDiscount.getId()).thenReturn("testId"); + when(mockCartDiscount.getKey()).thenReturn("any_key"); + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(sphereClient).build(); + final CartDiscountService cartDiscountService = + new CartDiscountServiceImpl(cartDiscountSyncOptions); + + @SuppressWarnings("unchecked") + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + when(pagedQueryResult.head()).thenReturn(Optional.of(mockCartDiscount)); + when(cartDiscountSyncOptions.getCtpClient().execute(any(CartDiscountQuery.class))) + .thenReturn(completedFuture(pagedQueryResult)); + + // test + final CompletionStage> result = + cartDiscountService.fetchCartDiscount("any_key"); + + // assertions + assertThat(result).isCompletedWithValue(Optional.of(mockCartDiscount)); + verify(sphereClient, only()).execute(any()); + } + + @Test + void createCartDiscount_WithNullCartDiscountKey_ShouldNotCreateCartDiscount() { + // preparation + final CartDiscountDraft mockCartDiscountDraft = mock(CartDiscountDraft.class); + final Map errors = new HashMap<>(); + when(mockCartDiscountDraft.getKey()).thenReturn(null); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> + errors.put(exception.getMessage(), exception)) + .build(); + final CartDiscountService cartDiscountService = + new CartDiscountServiceImpl(cartDiscountSyncOptions); + + // test + final CompletionStage> result = + cartDiscountService.createCartDiscount(mockCartDiscountDraft); + + // assertions + assertThat(result).isCompletedWithValue(Optional.empty()); + assertThat(errors.keySet()) + .containsExactly("Failed to create draft with key: 'null'. Reason: Draft key is blank!"); + verify(cartDiscountSyncOptions.getCtpClient(), times(0)).execute(any()); + } + + @Test + void createCartDiscount_WithEmptyCartDiscountKey_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final CartDiscountDraft mockCartDiscountDraft = mock(CartDiscountDraft.class); + final Map errors = new HashMap<>(); + when(mockCartDiscountDraft.getKey()).thenReturn(""); + + final CartDiscountSyncOptions options = + CartDiscountSyncOptionsBuilder.of(sphereClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> + errors.put(exception.getMessage(), exception)) + .build(); + + final CartDiscountServiceImpl cartDiscountService = new CartDiscountServiceImpl(options); + + // test + final CompletionStage> result = + cartDiscountService.createCartDiscount(mockCartDiscountDraft); + + // assertion + assertThat(result).isCompletedWithValue(Optional.empty()); + assertThat(errors.keySet()) + .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); + verify(options.getCtpClient(), times(0)).execute(any()); + } + + @Test + void createCartDiscount_WithUnsuccessfulMockCtpResponse_ShouldNotCreateCartDiscount() { + // preparation + final CartDiscountDraft mockCartDiscountDraft = mock(CartDiscountDraft.class); + final Map errors = new HashMap<>(); + when(mockCartDiscountDraft.getKey()).thenReturn("cartDiscountKey"); + + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> errors.put(exception.getMessage(), exception)) .build(); - final CartDiscountServiceImpl cartDiscountService = new CartDiscountServiceImpl(options); - - // test - final CompletionStage> result = cartDiscountService - .createCartDiscount(mockCartDiscountDraft); - - // assertion - assertThat(result).isCompletedWithValue(Optional.empty()); - assertThat(errors.keySet()) - .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); - verify(options.getCtpClient(), times(0)).execute(any()); - } - - @Test - void createCartDiscount_WithUnsuccessfulMockCtpResponse_ShouldNotCreateCartDiscount() { - // preparation - final CartDiscountDraft mockCartDiscountDraft = mock(CartDiscountDraft.class); - final Map errors = new HashMap<>(); - when(mockCartDiscountDraft.getKey()).thenReturn("cartDiscountKey"); - - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> - errors.put(exception.getMessage(), exception)) - .build(); - - final CartDiscountService cartDiscountService = new CartDiscountServiceImpl(cartDiscountSyncOptions); - - when(cartDiscountSyncOptions.getCtpClient().execute(any())) - .thenReturn(CompletableFutureUtils.failed(new InternalServerErrorException())); - - // test - final CompletionStage> result = - cartDiscountService.createCartDiscount(mockCartDiscountDraft); - - // assertions - assertThat(result).isCompletedWithValue(Optional.empty()); - assertThat(errors.keySet()) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> { - assertThat(message).contains("Failed to create draft with key: 'cartDiscountKey'."); - }); - - assertThat(errors.values()) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> { - assertThat(exception).isExactlyInstanceOf(SyncException.class); - assertThat(exception.getCause()).isExactlyInstanceOf(InternalServerErrorException.class); - }); - } - - @Test - void updateCartDiscount_WithMockSuccessfulCtpResponse_ShouldCallCartDiscountUpdateCommand() { - // preparation - final CartDiscount mockCartDiscount = mock(CartDiscount.class); - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); - - when(cartDiscountSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(mockCartDiscount)); - final CartDiscountService cartDiscountService = new CartDiscountServiceImpl(cartDiscountSyncOptions); - - final List> updateActions = - singletonList(SetDescription.of(LocalizedString.ofEnglish("new_desc"))); - // test - final CompletionStage result = - cartDiscountService.updateCartDiscount(mockCartDiscount, updateActions); - - // assertions - assertThat(result).isCompletedWithValue(mockCartDiscount); - verify(cartDiscountSyncOptions.getCtpClient()) - .execute(eq(CartDiscountUpdateCommand.of(mockCartDiscount, updateActions))); - } - - @Test - void updateCartDiscount_WithMockUnsuccessfulCtpResponse_ShouldCompleteExceptionally() { - // preparation - final CartDiscount mockCartDiscount = mock(CartDiscount.class); - final CartDiscountSyncOptions cartDiscountSyncOptions = CartDiscountSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); - - when(cartDiscountSyncOptions.getCtpClient().execute(any())) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new InternalServerErrorException())); - - final CartDiscountService cartDiscountService = new CartDiscountServiceImpl(cartDiscountSyncOptions); - - final List> updateActions = - singletonList(SetDescription.of(LocalizedString.ofEnglish("new_desc"))); - // test - final CompletionStage result = - cartDiscountService.updateCartDiscount(mockCartDiscount, updateActions); - - // assertions - assertThat(result).hasFailedWithThrowableThat() - .isExactlyInstanceOf(InternalServerErrorException.class); - } + final CartDiscountService cartDiscountService = + new CartDiscountServiceImpl(cartDiscountSyncOptions); + + when(cartDiscountSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new InternalServerErrorException())); + + // test + final CompletionStage> result = + cartDiscountService.createCartDiscount(mockCartDiscountDraft); + + // assertions + assertThat(result).isCompletedWithValue(Optional.empty()); + assertThat(errors.keySet()) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> { + assertThat(message).contains("Failed to create draft with key: 'cartDiscountKey'."); + }); + + assertThat(errors.values()) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> { + assertThat(exception).isExactlyInstanceOf(SyncException.class); + assertThat(exception.getCause()) + .isExactlyInstanceOf(InternalServerErrorException.class); + }); + } + + @Test + void updateCartDiscount_WithMockSuccessfulCtpResponse_ShouldCallCartDiscountUpdateCommand() { + // preparation + final CartDiscount mockCartDiscount = mock(CartDiscount.class); + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + when(cartDiscountSyncOptions.getCtpClient().execute(any())) + .thenReturn(completedFuture(mockCartDiscount)); + final CartDiscountService cartDiscountService = + new CartDiscountServiceImpl(cartDiscountSyncOptions); + + final List> updateActions = + singletonList(SetDescription.of(LocalizedString.ofEnglish("new_desc"))); + // test + final CompletionStage result = + cartDiscountService.updateCartDiscount(mockCartDiscount, updateActions); + + // assertions + assertThat(result).isCompletedWithValue(mockCartDiscount); + verify(cartDiscountSyncOptions.getCtpClient()) + .execute(eq(CartDiscountUpdateCommand.of(mockCartDiscount, updateActions))); + } + + @Test + void updateCartDiscount_WithMockUnsuccessfulCtpResponse_ShouldCompleteExceptionally() { + // preparation + final CartDiscount mockCartDiscount = mock(CartDiscount.class); + final CartDiscountSyncOptions cartDiscountSyncOptions = + CartDiscountSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + when(cartDiscountSyncOptions.getCtpClient().execute(any())) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new InternalServerErrorException())); + + final CartDiscountService cartDiscountService = + new CartDiscountServiceImpl(cartDiscountSyncOptions); + + final List> updateActions = + singletonList(SetDescription.of(LocalizedString.ofEnglish("new_desc"))); + // test + final CompletionStage result = + cartDiscountService.updateCartDiscount(mockCartDiscount, updateActions); + + // assertions + assertThat(result) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(InternalServerErrorException.class); + } } diff --git a/src/test/java/com/commercetools/sync/services/impl/CustomObjectServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/CustomObjectServiceImplTest.java index dc9bb62eac..b3fbb39cbb 100644 --- a/src/test/java/com/commercetools/sync/services/impl/CustomObjectServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/CustomObjectServiceImplTest.java @@ -1,5 +1,16 @@ package com.commercetools.sync.services.impl; +import static io.sphere.sdk.customobjects.CustomObjectUtils.getCustomObjectJavaTypeForValue; +import static io.sphere.sdk.json.SphereJsonUtils.convertToJavaType; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customobjects.CustomObjectSyncOptions; import com.commercetools.sync.customobjects.CustomObjectSyncOptionsBuilder; import com.commercetools.sync.customobjects.helpers.CustomObjectCompositeIdentifier; @@ -14,11 +25,6 @@ import io.sphere.sdk.customobjects.queries.CustomObjectQuery; import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -27,182 +33,189 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; - -import static io.sphere.sdk.customobjects.CustomObjectUtils.getCustomObjectJavaTypeForValue; -import static io.sphere.sdk.json.SphereJsonUtils.convertToJavaType; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomObjectServiceImplTest { - private SphereClient client = mock(SphereClient.class); - - private CustomObjectServiceImpl service; + private SphereClient client = mock(SphereClient.class); - private String customObjectId; - private String customObjectContainer; - private String customObjectKey; + private CustomObjectServiceImpl service; - @BeforeEach - void setup() { - customObjectId = RandomStringUtils.random(15, true, true); - customObjectContainer = RandomStringUtils.random(15, true, true); - customObjectKey = RandomStringUtils.random(15, true, true); + private String customObjectId; + private String customObjectContainer; + private String customObjectKey; - CustomObjectSyncOptions customObjectSyncOptions = CustomObjectSyncOptionsBuilder.of(client).build(); + @BeforeEach + void setup() { + customObjectId = RandomStringUtils.random(15, true, true); + customObjectContainer = RandomStringUtils.random(15, true, true); + customObjectKey = RandomStringUtils.random(15, true, true); - service = new CustomObjectServiceImpl(customObjectSyncOptions); - } + CustomObjectSyncOptions customObjectSyncOptions = + CustomObjectSyncOptionsBuilder.of(client).build(); - @AfterEach - void cleanup() { - reset(client); - } + service = new CustomObjectServiceImpl(customObjectSyncOptions); + } - private interface CustomObjectPagedQueryResult extends PagedQueryResult { - } + @AfterEach + void cleanup() { + reset(client); + } - @Test - void fetchCachedCustomObjectId_WithKeyAndContainer_ShouldFetchCustomObject() { - final String key = RandomStringUtils.random(15, true, true); - final String container = RandomStringUtils.random(15, true, true); - final String id = RandomStringUtils.random(15, true, true); + private interface CustomObjectPagedQueryResult extends PagedQueryResult {} - final CustomObject mock = mock(CustomObject.class); - when(mock.getId()).thenReturn(id); - when(mock.getContainer()).thenReturn(container); - when(mock.getKey()).thenReturn(key); + @Test + void fetchCachedCustomObjectId_WithKeyAndContainer_ShouldFetchCustomObject() { + final String key = RandomStringUtils.random(15, true, true); + final String container = RandomStringUtils.random(15, true, true); + final String id = RandomStringUtils.random(15, true, true); - final CustomObjectPagedQueryResult result = mock(CustomObjectPagedQueryResult.class); - when(result.getResults()).thenReturn(Collections.singletonList(mock)); + final CustomObject mock = mock(CustomObject.class); + when(mock.getId()).thenReturn(id); + when(mock.getContainer()).thenReturn(container); + when(mock.getKey()).thenReturn(key); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + final CustomObjectPagedQueryResult result = mock(CustomObjectPagedQueryResult.class); + when(result.getResults()).thenReturn(Collections.singletonList(mock)); + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - final Optional fetchedId = service + final Optional fetchedId = + service .fetchCachedCustomObjectId(CustomObjectCompositeIdentifier.of(key, container)) - .toCompletableFuture().join(); + .toCompletableFuture() + .join(); - assertThat(fetchedId).contains(id); - verify(client).execute(any(CustomObjectQuery.class)); - } + assertThat(fetchedId).contains(id); + verify(client).execute(any(CustomObjectQuery.class)); + } - @Test - void fetchMatchingCustomObjects_WithKeySet_ShouldFetchCustomObjects() { - final String key1 = RandomStringUtils.random(15, true, true); - final String key2 = RandomStringUtils.random(15, true, true); - final String container1 = RandomStringUtils.random(15, true, true); - final String container2 = RandomStringUtils.random(15, true, true); + @Test + void fetchMatchingCustomObjects_WithKeySet_ShouldFetchCustomObjects() { + final String key1 = RandomStringUtils.random(15, true, true); + final String key2 = RandomStringUtils.random(15, true, true); + final String container1 = RandomStringUtils.random(15, true, true); + final String container2 = RandomStringUtils.random(15, true, true); - final Set customObjectCompositeIdentifiers = new HashSet<>(); - customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of(key1, container1)); - customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of(key2, container2)); + final Set customObjectCompositeIdentifiers = new HashSet<>(); + customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of(key1, container1)); + customObjectCompositeIdentifiers.add(CustomObjectCompositeIdentifier.of(key2, container2)); - final CustomObject mock1 = mock(CustomObject.class); - when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock1.getKey()).thenReturn(key1); - when(mock1.getContainer()).thenReturn(container1); + final CustomObject mock1 = mock(CustomObject.class); + when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock1.getKey()).thenReturn(key1); + when(mock1.getContainer()).thenReturn(container1); - final CustomObject mock2 = mock(CustomObject.class); - when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock2.getKey()).thenReturn(key2); - when(mock2.getContainer()).thenReturn(container2); + final CustomObject mock2 = mock(CustomObject.class); + when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock2.getKey()).thenReturn(key2); + when(mock2.getContainer()).thenReturn(container2); - final CustomObjectPagedQueryResult result = mock(CustomObjectPagedQueryResult.class); - when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); + final CustomObjectPagedQueryResult result = mock(CustomObjectPagedQueryResult.class); + when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - final Set> customObjects = service + final Set> customObjects = + service .fetchMatchingCustomObjects(customObjectCompositeIdentifiers) - .toCompletableFuture().join(); - - List customObjectCompositeIdlist = - new ArrayList(customObjectCompositeIdentifiers); - - assertAll( - () -> assertThat(customObjects).contains(mock1, mock2), - () -> assertThat(service.keyToIdCache.asMap()).containsKeys( - String.valueOf(customObjectCompositeIdlist.get(0)), - String.valueOf(customObjectCompositeIdlist.get(1))) - ); - verify(client).execute(any(CustomObjectQuery.class)); - } - - @Test - void fetchCustomObject_WithKeyAndContainer_ShouldFetchCustomObject() { - final CustomObject mock = mock(CustomObject.class); - when(mock.getId()).thenReturn(customObjectId); - when(mock.getKey()).thenReturn(customObjectKey); - when(mock.getContainer()).thenReturn(customObjectContainer); - final CustomObjectPagedQueryResult result = mock(CustomObjectPagedQueryResult.class); - when(result.head()).thenReturn(Optional.of(mock)); - - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - - - final Optional> customObjectOptional = service - .fetchCustomObject(CustomObjectCompositeIdentifier.of(customObjectKey, customObjectContainer)) - .toCompletableFuture().join(); - - assertAll( - () -> assertThat(customObjectOptional).containsSame(mock), - () -> assertThat( - service.keyToIdCache.asMap().get( - CustomObjectCompositeIdentifier.of(customObjectKey, customObjectContainer).toString()) - ).isEqualTo(customObjectId) - ); - verify(client).execute(any(CustomObjectQuery.class)); - } - - @Test - void createCustomObject_WithDraft_ShouldCreateCustomObject() { - final CustomObject mock = mock(CustomObject.class); - when(mock.getId()).thenReturn(customObjectId); - when(mock.getKey()).thenReturn(customObjectKey); - when(mock.getContainer()).thenReturn(customObjectContainer); - - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); - - final ObjectNode customObjectValue = JsonNodeFactory.instance.objectNode(); - customObjectValue.put("currentHash", "1234-5678-0912-3456"); - customObjectValue.put("convertedAmount", "100"); - - - final CustomObjectDraft draft = CustomObjectDraft - .ofUnversionedUpsert(customObjectContainer, customObjectKey, customObjectValue); - - final Optional> customObjectOptional = - service.upsertCustomObject(draft).toCompletableFuture().join(); - - assertThat(customObjectOptional).containsSame(mock); - verify(client).execute(eq(CustomObjectUpsertCommand.of(draft))); - } - - @Test - void createCustomObject_WithRequestException_ShouldNotCreateCustomObject() { - final CustomObject mock = mock(CustomObject.class); - when(mock.getId()).thenReturn(customObjectId); - - when(client.execute(any())).thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); - - final CustomObjectDraft draftMock = mock(CustomObjectDraft.class); - when(draftMock.getKey()).thenReturn(customObjectKey); - when(draftMock.getContainer()).thenReturn(customObjectContainer); - when(draftMock.getJavaType()).thenReturn(getCustomObjectJavaTypeForValue(convertToJavaType(JsonNode.class))); - - - CompletableFuture future = service.upsertCustomObject(draftMock).toCompletableFuture(); - - assertAll( - () -> assertThat(future.isCompletedExceptionally()).isTrue(), - () -> assertThat(future).hasFailedWithThrowableThat().isExactlyInstanceOf(BadRequestException.class) - ); - } + .toCompletableFuture() + .join(); + + List customObjectCompositeIdlist = + new ArrayList(customObjectCompositeIdentifiers); + + assertAll( + () -> assertThat(customObjects).contains(mock1, mock2), + () -> + assertThat(service.keyToIdCache.asMap()) + .containsKeys( + String.valueOf(customObjectCompositeIdlist.get(0)), + String.valueOf(customObjectCompositeIdlist.get(1)))); + verify(client).execute(any(CustomObjectQuery.class)); + } + + @Test + void fetchCustomObject_WithKeyAndContainer_ShouldFetchCustomObject() { + final CustomObject mock = mock(CustomObject.class); + when(mock.getId()).thenReturn(customObjectId); + when(mock.getKey()).thenReturn(customObjectKey); + when(mock.getContainer()).thenReturn(customObjectContainer); + final CustomObjectPagedQueryResult result = mock(CustomObjectPagedQueryResult.class); + when(result.head()).thenReturn(Optional.of(mock)); + + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + + final Optional> customObjectOptional = + service + .fetchCustomObject( + CustomObjectCompositeIdentifier.of(customObjectKey, customObjectContainer)) + .toCompletableFuture() + .join(); + + assertAll( + () -> assertThat(customObjectOptional).containsSame(mock), + () -> + assertThat( + service + .keyToIdCache + .asMap() + .get( + CustomObjectCompositeIdentifier.of( + customObjectKey, customObjectContainer) + .toString())) + .isEqualTo(customObjectId)); + verify(client).execute(any(CustomObjectQuery.class)); + } + + @Test + void createCustomObject_WithDraft_ShouldCreateCustomObject() { + final CustomObject mock = mock(CustomObject.class); + when(mock.getId()).thenReturn(customObjectId); + when(mock.getKey()).thenReturn(customObjectKey); + when(mock.getContainer()).thenReturn(customObjectContainer); + + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); + + final ObjectNode customObjectValue = JsonNodeFactory.instance.objectNode(); + customObjectValue.put("currentHash", "1234-5678-0912-3456"); + customObjectValue.put("convertedAmount", "100"); + + final CustomObjectDraft draft = + CustomObjectDraft.ofUnversionedUpsert( + customObjectContainer, customObjectKey, customObjectValue); + + final Optional> customObjectOptional = + service.upsertCustomObject(draft).toCompletableFuture().join(); + + assertThat(customObjectOptional).containsSame(mock); + verify(client).execute(eq(CustomObjectUpsertCommand.of(draft))); + } + + @Test + void createCustomObject_WithRequestException_ShouldNotCreateCustomObject() { + final CustomObject mock = mock(CustomObject.class); + when(mock.getId()).thenReturn(customObjectId); + + when(client.execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); + + final CustomObjectDraft draftMock = mock(CustomObjectDraft.class); + when(draftMock.getKey()).thenReturn(customObjectKey); + when(draftMock.getContainer()).thenReturn(customObjectContainer); + when(draftMock.getJavaType()) + .thenReturn(getCustomObjectJavaTypeForValue(convertToJavaType(JsonNode.class))); + + CompletableFuture future = service.upsertCustomObject(draftMock).toCompletableFuture(); + + assertAll( + () -> assertThat(future.isCompletedExceptionally()).isTrue(), + () -> + assertThat(future) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadRequestException.class)); + } } diff --git a/src/test/java/com/commercetools/sync/services/impl/CustomerServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/CustomerServiceImplTest.java index a5a1edf775..e712e2db23 100644 --- a/src/test/java/com/commercetools/sync/services/impl/CustomerServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/CustomerServiceImplTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.services.impl; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + import com.commercetools.sync.customers.CustomerSyncOptions; import com.commercetools.sync.customers.CustomerSyncOptionsBuilder; import io.sphere.sdk.client.BadGatewayException; @@ -14,185 +24,173 @@ import io.sphere.sdk.customers.commands.CustomerUpdateCommand; import io.sphere.sdk.customers.commands.updateactions.ChangeName; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.Optional; - -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomerServiceImplTest { - private CustomerServiceImpl service; - private CustomerSyncOptions customerSyncOptions; - private List errorMessages; - private List errorExceptions; - - @BeforeEach - void setUp() { - errorMessages = new ArrayList<>(); - errorExceptions = new ArrayList<>(); - customerSyncOptions = CustomerSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errorExceptions.add(exception.getCause()); - }) + private CustomerServiceImpl service; + private CustomerSyncOptions customerSyncOptions; + private List errorMessages; + private List errorExceptions; + + @BeforeEach + void setUp() { + errorMessages = new ArrayList<>(); + errorExceptions = new ArrayList<>(); + customerSyncOptions = + CustomerSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errorExceptions.add(exception.getCause()); + }) .build(); - service = new CustomerServiceImpl(customerSyncOptions); - } - - @Test - void createCustomer_WithSuccessfulMockCtpResponse_ShouldReturnMock() { - final CustomerSignInResult resultMock = mock(CustomerSignInResult.class); - final Customer customerMock = mock(Customer.class); - when(customerMock.getId()).thenReturn("customerId"); - when(customerMock.getKey()).thenReturn("customerKey"); - when(resultMock.getCustomer()).thenReturn(customerMock); - - when(customerSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(resultMock)); - - final CustomerDraft draft = mock(CustomerDraft.class); - when(draft.getKey()).thenReturn("customerKey"); - final Optional customerOptional = service.createCustomer(draft).toCompletableFuture().join(); - - assertThat(customerOptional).isNotEmpty(); - assertThat(customerOptional).containsSame(customerMock); - verify(customerSyncOptions.getCtpClient()).execute(eq(CustomerCreateCommand.of(draft))); - } - - @Test - void createCustomer_WithUnSuccessfulMockResponse_ShouldNotCreate() { - final CustomerSignInResult resultMock = mock(CustomerSignInResult.class); - final Customer customerMock = mock(Customer.class); - when(customerMock.getId()).thenReturn("customerId"); - when(customerMock.getKey()).thenReturn("customerKey"); - when(resultMock.getCustomer()).thenReturn(customerMock); - - when(customerSyncOptions.getCtpClient().execute(any())).thenReturn( - CompletableFutureUtils.failed(new BadRequestException("bad request"))); - - final CustomerDraft draft = mock(CustomerDraft.class); - when(draft.getKey()).thenReturn("customerKey"); - final Optional customerOptional = service.createCustomer(draft).toCompletableFuture().join(); - - assertThat(customerOptional).isEmpty(); - assertThat(errorExceptions).hasSize(1); - assertThat(errorExceptions.get(0)).isExactlyInstanceOf(BadRequestException.class); - assertThat(errorMessages).hasSize(1); - assertThat(errorMessages.get(0)).contains("Failed to create draft with key: 'customerKey'."); - verify(customerSyncOptions.getCtpClient()).execute(eq(CustomerCreateCommand.of(draft))); - } - - @Test - void createCustomer_WithDraftWithEmptyKey_ShouldNotCreate() { - final CustomerDraft draft = mock(CustomerDraft.class); - final Optional customerOptional = service.createCustomer(draft).toCompletableFuture().join(); - - assertThat(customerOptional).isEmpty(); - assertThat(errorExceptions).hasSize(1); - assertThat(errorMessages).hasSize(1); - assertThat(errorMessages.get(0)) - .contains("Failed to create draft with key: 'null'. Reason: Draft key is blank!"); - verifyNoInteractions(customerSyncOptions.getCtpClient()); - } - - @Test - void createCustomer_WithResponseIsNull_ShouldReturnEmpty() { - CustomerSignInResult resultMock = mock(CustomerSignInResult.class); - when(customerSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(resultMock)); - - final CustomerDraft draft = mock(CustomerDraft.class); - when(draft.getKey()).thenReturn("key"); - final Optional customerOptional = service.createCustomer(draft).toCompletableFuture().join(); - - assertThat(customerOptional).isEmpty(); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - } - - @Test - void updateCustomer_WithSuccessfulMockCtpResponse_ShouldReturnMock() { - Customer customer = mock(Customer.class); - when(customerSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(customer)); - - List> updateActions = - singletonList(ChangeName.of(CustomerName.of("title", "Max", "", "Mustermann"))); - Customer result = service.updateCustomer(customer, updateActions).toCompletableFuture().join(); - - assertThat(result).isSameAs(customer); - verify(customerSyncOptions.getCtpClient()).execute(eq(CustomerUpdateCommand.of(customer, updateActions))); - } - - @Test - void fetchCachedCustomerId_WithBlankKey_ShouldNotFetchCustomerId() { - Optional customerId = service.fetchCachedCustomerId("") - .toCompletableFuture() - .join(); - - assertThat(customerId).isEmpty(); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - verifyNoInteractions(customerSyncOptions.getCtpClient()); - } - - @Test - void fetchCachedCustomerId_WithCachedCustomer_ShouldFetchIdFromCache() { - service.keyToIdCache.put("key", "id"); - Optional customerId = service.fetchCachedCustomerId("key") - .toCompletableFuture() - .join(); - - assertThat(customerId).contains("id"); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - verifyNoInteractions(customerSyncOptions.getCtpClient()); - } - - @Test - void fetchCachedCustomerId_WithUnexpectedException_ShouldFail() { - when(customerSyncOptions.getCtpClient().execute(any())).thenReturn( - CompletableFutureUtils.failed(new BadGatewayException("bad gateway"))); - - assertThat(service.fetchCachedCustomerId("key")) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - } - - @Test - void fetchCustomerByKey_WithUnexpectedException_ShouldFail() { - when(customerSyncOptions.getCtpClient().execute(any())).thenReturn( - CompletableFutureUtils.failed(new BadGatewayException("bad gateway"))); - - assertThat(service.fetchCustomerByKey("key")) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - } - - @Test - void fetchCustomerByKey_WithBlankKey_ShouldNotFetchCustomer() { - Optional customer = service.fetchCustomerByKey("") - .toCompletableFuture() - .join(); - - assertThat(customer).isEmpty(); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - verifyNoInteractions(customerSyncOptions.getCtpClient()); - } - -} \ No newline at end of file + service = new CustomerServiceImpl(customerSyncOptions); + } + + @Test + void createCustomer_WithSuccessfulMockCtpResponse_ShouldReturnMock() { + final CustomerSignInResult resultMock = mock(CustomerSignInResult.class); + final Customer customerMock = mock(Customer.class); + when(customerMock.getId()).thenReturn("customerId"); + when(customerMock.getKey()).thenReturn("customerKey"); + when(resultMock.getCustomer()).thenReturn(customerMock); + + when(customerSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(resultMock)); + + final CustomerDraft draft = mock(CustomerDraft.class); + when(draft.getKey()).thenReturn("customerKey"); + final Optional customerOptional = + service.createCustomer(draft).toCompletableFuture().join(); + + assertThat(customerOptional).isNotEmpty(); + assertThat(customerOptional).containsSame(customerMock); + verify(customerSyncOptions.getCtpClient()).execute(eq(CustomerCreateCommand.of(draft))); + } + + @Test + void createCustomer_WithUnSuccessfulMockResponse_ShouldNotCreate() { + final CustomerSignInResult resultMock = mock(CustomerSignInResult.class); + final Customer customerMock = mock(Customer.class); + when(customerMock.getId()).thenReturn("customerId"); + when(customerMock.getKey()).thenReturn("customerKey"); + when(resultMock.getCustomer()).thenReturn(customerMock); + + when(customerSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); + + final CustomerDraft draft = mock(CustomerDraft.class); + when(draft.getKey()).thenReturn("customerKey"); + final Optional customerOptional = + service.createCustomer(draft).toCompletableFuture().join(); + + assertThat(customerOptional).isEmpty(); + assertThat(errorExceptions).hasSize(1); + assertThat(errorExceptions.get(0)).isExactlyInstanceOf(BadRequestException.class); + assertThat(errorMessages).hasSize(1); + assertThat(errorMessages.get(0)).contains("Failed to create draft with key: 'customerKey'."); + verify(customerSyncOptions.getCtpClient()).execute(eq(CustomerCreateCommand.of(draft))); + } + + @Test + void createCustomer_WithDraftWithEmptyKey_ShouldNotCreate() { + final CustomerDraft draft = mock(CustomerDraft.class); + final Optional customerOptional = + service.createCustomer(draft).toCompletableFuture().join(); + + assertThat(customerOptional).isEmpty(); + assertThat(errorExceptions).hasSize(1); + assertThat(errorMessages).hasSize(1); + assertThat(errorMessages.get(0)) + .contains("Failed to create draft with key: 'null'. Reason: Draft key is blank!"); + verifyNoInteractions(customerSyncOptions.getCtpClient()); + } + + @Test + void createCustomer_WithResponseIsNull_ShouldReturnEmpty() { + CustomerSignInResult resultMock = mock(CustomerSignInResult.class); + when(customerSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(resultMock)); + + final CustomerDraft draft = mock(CustomerDraft.class); + when(draft.getKey()).thenReturn("key"); + final Optional customerOptional = + service.createCustomer(draft).toCompletableFuture().join(); + + assertThat(customerOptional).isEmpty(); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + } + + @Test + void updateCustomer_WithSuccessfulMockCtpResponse_ShouldReturnMock() { + Customer customer = mock(Customer.class); + when(customerSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(customer)); + + List> updateActions = + singletonList(ChangeName.of(CustomerName.of("title", "Max", "", "Mustermann"))); + Customer result = service.updateCustomer(customer, updateActions).toCompletableFuture().join(); + + assertThat(result).isSameAs(customer); + verify(customerSyncOptions.getCtpClient()) + .execute(eq(CustomerUpdateCommand.of(customer, updateActions))); + } + + @Test + void fetchCachedCustomerId_WithBlankKey_ShouldNotFetchCustomerId() { + Optional customerId = service.fetchCachedCustomerId("").toCompletableFuture().join(); + + assertThat(customerId).isEmpty(); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + verifyNoInteractions(customerSyncOptions.getCtpClient()); + } + + @Test + void fetchCachedCustomerId_WithCachedCustomer_ShouldFetchIdFromCache() { + service.keyToIdCache.put("key", "id"); + Optional customerId = service.fetchCachedCustomerId("key").toCompletableFuture().join(); + + assertThat(customerId).contains("id"); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + verifyNoInteractions(customerSyncOptions.getCtpClient()); + } + + @Test + void fetchCachedCustomerId_WithUnexpectedException_ShouldFail() { + when(customerSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadGatewayException("bad gateway"))); + + assertThat(service.fetchCachedCustomerId("key")) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + } + + @Test + void fetchCustomerByKey_WithUnexpectedException_ShouldFail() { + when(customerSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadGatewayException("bad gateway"))); + + assertThat(service.fetchCustomerByKey("key")) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + } + + @Test + void fetchCustomerByKey_WithBlankKey_ShouldNotFetchCustomer() { + Optional customer = service.fetchCustomerByKey("").toCompletableFuture().join(); + + assertThat(customer).isEmpty(); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + verifyNoInteractions(customerSyncOptions.getCtpClient()); + } +} diff --git a/src/test/java/com/commercetools/sync/services/impl/ProductServiceTest.java b/src/test/java/com/commercetools/sync/services/impl/ProductServiceTest.java index e1c5cd85e5..0be85f06e7 100644 --- a/src/test/java/com/commercetools/sync/services/impl/ProductServiceTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/ProductServiceTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.services.impl; +import static java.util.Collections.singletonList; +import static java.util.Locale.ENGLISH; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.products.ProductSyncOptions; import com.commercetools.sync.products.ProductSyncOptionsBuilder; import io.sphere.sdk.client.BadRequestException; @@ -13,140 +23,138 @@ import io.sphere.sdk.products.commands.updateactions.ChangeName; import io.sphere.sdk.queries.QueryPredicate; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; - -import static java.util.Collections.singletonList; -import static java.util.Locale.ENGLISH; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ProductServiceTest { - private ProductServiceImpl service; - private ProductSyncOptions productSyncOptions; - private List errorMessages; - private List errorExceptions; - - @BeforeEach - void setUp() { - errorMessages = new ArrayList<>(); - errorExceptions = new ArrayList<>(); - productSyncOptions = ProductSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errorExceptions.add(exception.getCause()); - }) + private ProductServiceImpl service; + private ProductSyncOptions productSyncOptions; + private List errorMessages; + private List errorExceptions; + + @BeforeEach + void setUp() { + errorMessages = new ArrayList<>(); + errorExceptions = new ArrayList<>(); + productSyncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errorExceptions.add(exception.getCause()); + }) .build(); - service = new ProductServiceImpl(productSyncOptions); - } - - @Test - void createProduct_WithSuccessfulMockCtpResponse_ShouldReturnMock() { - final Product mock = mock(Product.class); - when(mock.getId()).thenReturn("productId"); - when(mock.getKey()).thenReturn("productKey"); - - when(productSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(mock)); - - final ProductDraft draft = mock(ProductDraft.class); - when(draft.getKey()).thenReturn("productKey"); - final Optional productOptional = service.createProduct(draft).toCompletableFuture().join(); - - assertThat(productOptional).isNotEmpty(); - assertThat(productOptional).containsSame(mock); - verify(productSyncOptions.getCtpClient()).execute(eq(ProductCreateCommand.of(draft))); - } - - @Test - void createProduct_WithUnSuccessfulMockCtpResponse_ShouldNotCreateProduct() { - // preparation - final Product mock = mock(Product.class); - when(mock.getId()).thenReturn("productId"); - - when(productSyncOptions.getCtpClient().execute(any())) - .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); - - final ProductDraft draft = mock(ProductDraft.class); - when(draft.getKey()).thenReturn("productKey"); - - // test - final Optional productOptional = service.createProduct(draft).toCompletableFuture().join(); - - // assertion - assertThat(productOptional).isEmpty(); - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> { - assertThat(message).contains("Failed to create draft with key: 'productKey'."); - assertThat(message).contains("BadRequestException"); + service = new ProductServiceImpl(productSyncOptions); + } + + @Test + void createProduct_WithSuccessfulMockCtpResponse_ShouldReturnMock() { + final Product mock = mock(Product.class); + when(mock.getId()).thenReturn("productId"); + when(mock.getKey()).thenReturn("productKey"); + + when(productSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(mock)); + + final ProductDraft draft = mock(ProductDraft.class); + when(draft.getKey()).thenReturn("productKey"); + final Optional productOptional = + service.createProduct(draft).toCompletableFuture().join(); + + assertThat(productOptional).isNotEmpty(); + assertThat(productOptional).containsSame(mock); + verify(productSyncOptions.getCtpClient()).execute(eq(ProductCreateCommand.of(draft))); + } + + @Test + void createProduct_WithUnSuccessfulMockCtpResponse_ShouldNotCreateProduct() { + // preparation + final Product mock = mock(Product.class); + when(mock.getId()).thenReturn("productId"); + + when(productSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); + + final ProductDraft draft = mock(ProductDraft.class); + when(draft.getKey()).thenReturn("productKey"); + + // test + final Optional productOptional = + service.createProduct(draft).toCompletableFuture().join(); + + // assertion + assertThat(productOptional).isEmpty(); + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> { + assertThat(message).contains("Failed to create draft with key: 'productKey'."); + assertThat(message).contains("BadRequestException"); }); - assertThat(errorExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> - assertThat(exception).isExactlyInstanceOf(BadRequestException.class)); - } - - @Test - void createProduct_WithDraftWithoutKey_ShouldNotCreateProduct() { - final ProductDraft draft = mock(ProductDraft.class); - final Optional productOptional = service.createProduct(draft).toCompletableFuture().join(); - - assertThat(productOptional).isEmpty(); - assertThat(errorMessages).hasSize(1); - assertThat(errorExceptions).hasSize(1); - assertThat(errorMessages.get(0)) - .isEqualTo("Failed to create draft with key: 'null'. Reason: Draft key is blank!"); - } - - @Test - void updateProduct_WithMockCtpResponse_ShouldReturnMock() { - final Product mock = mock(Product.class); - when(productSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(mock)); - - final List> updateActions = - singletonList(ChangeName.of(LocalizedString.of(ENGLISH, "new name"))); - final Product product = service.updateProduct(mock, updateActions).toCompletableFuture().join(); - - assertThat(product).isSameAs(mock); - verify(productSyncOptions.getCtpClient()).execute(eq(ProductUpdateCommand.of(mock, updateActions))); - } - - @Test - void buildProductKeysQueryPredicate_WithEmptyProductKeysSet_ShouldBuildCorrectQuery() { - final QueryPredicate queryPredicate = service.buildProductKeysQueryPredicate(new HashSet<>()); - assertThat(queryPredicate.toSphereQuery()).isEqualTo("key in ()"); - } - - @Test - void buildProductKeysQueryPredicate_WithProductKeysSet_ShouldBuildCorrectQuery() { - final HashSet productKeys = new HashSet<>(); - productKeys.add("key1"); - productKeys.add("key2"); - final QueryPredicate queryPredicate = service.buildProductKeysQueryPredicate(productKeys); - assertThat(queryPredicate.toSphereQuery()).isEqualTo("key in (\"key1\", \"key2\")"); - } - - @Test - void buildProductKeysQueryPredicate_WithSomeBlankProductKeys_ShouldBuildCorrectQuery() { - final HashSet productKeys = new HashSet<>(); - productKeys.add("key1"); - productKeys.add("key2"); - productKeys.add(""); - productKeys.add(null); - final QueryPredicate queryPredicate = service.buildProductKeysQueryPredicate(productKeys); - assertThat(queryPredicate.toSphereQuery()).isEqualTo("key in (\"key1\", \"key2\")"); - } -} \ No newline at end of file + assertThat(errorExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> assertThat(exception).isExactlyInstanceOf(BadRequestException.class)); + } + + @Test + void createProduct_WithDraftWithoutKey_ShouldNotCreateProduct() { + final ProductDraft draft = mock(ProductDraft.class); + final Optional productOptional = + service.createProduct(draft).toCompletableFuture().join(); + + assertThat(productOptional).isEmpty(); + assertThat(errorMessages).hasSize(1); + assertThat(errorExceptions).hasSize(1); + assertThat(errorMessages.get(0)) + .isEqualTo("Failed to create draft with key: 'null'. Reason: Draft key is blank!"); + } + + @Test + void updateProduct_WithMockCtpResponse_ShouldReturnMock() { + final Product mock = mock(Product.class); + when(productSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(mock)); + + final List> updateActions = + singletonList(ChangeName.of(LocalizedString.of(ENGLISH, "new name"))); + final Product product = service.updateProduct(mock, updateActions).toCompletableFuture().join(); + + assertThat(product).isSameAs(mock); + verify(productSyncOptions.getCtpClient()) + .execute(eq(ProductUpdateCommand.of(mock, updateActions))); + } + + @Test + void buildProductKeysQueryPredicate_WithEmptyProductKeysSet_ShouldBuildCorrectQuery() { + final QueryPredicate queryPredicate = + service.buildProductKeysQueryPredicate(new HashSet<>()); + assertThat(queryPredicate.toSphereQuery()).isEqualTo("key in ()"); + } + + @Test + void buildProductKeysQueryPredicate_WithProductKeysSet_ShouldBuildCorrectQuery() { + final HashSet productKeys = new HashSet<>(); + productKeys.add("key1"); + productKeys.add("key2"); + final QueryPredicate queryPredicate = + service.buildProductKeysQueryPredicate(productKeys); + assertThat(queryPredicate.toSphereQuery()).isEqualTo("key in (\"key1\", \"key2\")"); + } + + @Test + void buildProductKeysQueryPredicate_WithSomeBlankProductKeys_ShouldBuildCorrectQuery() { + final HashSet productKeys = new HashSet<>(); + productKeys.add("key1"); + productKeys.add("key2"); + productKeys.add(""); + productKeys.add(null); + final QueryPredicate queryPredicate = + service.buildProductKeysQueryPredicate(productKeys); + assertThat(queryPredicate.toSphereQuery()).isEqualTo("key in (\"key1\", \"key2\")"); + } +} diff --git a/src/test/java/com/commercetools/sync/services/impl/ProductTypeServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/ProductTypeServiceImplTest.java index af10fd22e2..7a3df2afd1 100644 --- a/src/test/java/com/commercetools/sync/services/impl/ProductTypeServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/ProductTypeServiceImplTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.services.impl; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.producttypes.ProductTypeSyncOptions; import com.commercetools.sync.producttypes.ProductTypeSyncOptionsBuilder; import com.commercetools.sync.services.ProductTypeService; @@ -8,72 +15,60 @@ import io.sphere.sdk.producttypes.ProductType; import io.sphere.sdk.producttypes.queries.ProductTypeQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.Test; - import java.util.Optional; import java.util.concurrent.CompletionStage; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ProductTypeServiceImplTest { - @Test - void fetchProductType_WithEmptyKey_ShouldNotFetchProductType() { - // preparation - final SphereClient sphereClient = mock(SphereClient.class); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(sphereClient) - .build(); - final ProductTypeService productTypeService = new ProductTypeServiceImpl(syncOptions); - - // test - final CompletionStage> result = productTypeService.fetchProductType(""); - - // assertions - assertThat(result).isCompletedWithValue(Optional.empty()); - verify(sphereClient, never()).execute(any()); - } - - @Test - void fetchProductType_WithNullKey_ShouldNotFetchProductType() { - // preparation - final SphereClient sphereClient = mock(SphereClient.class); - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(sphereClient) - .build(); - final ProductTypeService productTypeService = new ProductTypeServiceImpl(syncOptions); - - // test - final CompletionStage> result = productTypeService.fetchProductType(null); - - // assertions - assertThat(result).isCompletedWithValue(Optional.empty()); - verify(sphereClient, never()).execute(any()); - } - - @Test - void fetchProductType_WithBadGateWayException_ShouldCompleteExceptionally() { - // preparation - final SphereClient sphereClient = mock(SphereClient.class); - when(sphereClient.execute(any(ProductTypeQuery.class))) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); - - - final ProductTypeSyncOptions syncOptions = ProductTypeSyncOptionsBuilder - .of(sphereClient) - .build(); - final ProductTypeService productTypeService = new ProductTypeServiceImpl(syncOptions); - - // test - final CompletionStage> result = productTypeService.fetchProductType("foo"); - - // assertions - assertThat(result).hasFailedWithThrowableThat().isExactlyInstanceOf(BadGatewayException.class); - } - -} \ No newline at end of file + @Test + void fetchProductType_WithEmptyKey_ShouldNotFetchProductType() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(sphereClient).build(); + final ProductTypeService productTypeService = new ProductTypeServiceImpl(syncOptions); + + // test + final CompletionStage> result = productTypeService.fetchProductType(""); + + // assertions + assertThat(result).isCompletedWithValue(Optional.empty()); + verify(sphereClient, never()).execute(any()); + } + + @Test + void fetchProductType_WithNullKey_ShouldNotFetchProductType() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(sphereClient).build(); + final ProductTypeService productTypeService = new ProductTypeServiceImpl(syncOptions); + + // test + final CompletionStage> result = productTypeService.fetchProductType(null); + + // assertions + assertThat(result).isCompletedWithValue(Optional.empty()); + verify(sphereClient, never()).execute(any()); + } + + @Test + void fetchProductType_WithBadGateWayException_ShouldCompleteExceptionally() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + when(sphereClient.execute(any(ProductTypeQuery.class))) + .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new BadGatewayException())); + + final ProductTypeSyncOptions syncOptions = + ProductTypeSyncOptionsBuilder.of(sphereClient).build(); + final ProductTypeService productTypeService = new ProductTypeServiceImpl(syncOptions); + + // test + final CompletionStage> result = + productTypeService.fetchProductType("foo"); + + // assertions + assertThat(result).hasFailedWithThrowableThat().isExactlyInstanceOf(BadGatewayException.class); + } +} diff --git a/src/test/java/com/commercetools/sync/services/impl/ShoppingListServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/ShoppingListServiceImplTest.java index f0b16db73d..70de516aa0 100644 --- a/src/test/java/com/commercetools/sync/services/impl/ShoppingListServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/ShoppingListServiceImplTest.java @@ -1,5 +1,20 @@ package com.commercetools.sync.services.impl; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.internal.verification.VerificationModeFactory.only; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.services.ShoppingListService; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; @@ -16,9 +31,6 @@ import io.sphere.sdk.shoppinglists.commands.updateactions.ChangeName; import io.sphere.sdk.shoppinglists.queries.ShoppingListQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -26,249 +38,248 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; - -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static org.mockito.internal.verification.VerificationModeFactory.only; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ShoppingListServiceImplTest { - private ShoppingListServiceImpl service; - private ShoppingListSyncOptions shoppingListSyncOptions; - private List errorMessages; - private List errorExceptions; - - @BeforeEach - void setUp() { - errorMessages = new ArrayList<>(); - errorExceptions = new ArrayList<>(); - shoppingListSyncOptions = ShoppingListSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errorExceptions.add(exception.getCause()); - }) + private ShoppingListServiceImpl service; + private ShoppingListSyncOptions shoppingListSyncOptions; + private List errorMessages; + private List errorExceptions; + + @BeforeEach + void setUp() { + errorMessages = new ArrayList<>(); + errorExceptions = new ArrayList<>(); + shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errorExceptions.add(exception.getCause()); + }) .build(); - service = new ShoppingListServiceImpl(shoppingListSyncOptions); - } - - @Test - void fetchShoppingList_WithEmptyKey_ShouldNotFetchAnyShoppingList() { - // test - final Optional result = service.fetchShoppingList("").toCompletableFuture().join(); - - - // assertions - assertThat(result).isEmpty(); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - verify(shoppingListSyncOptions.getCtpClient(), never()).execute(any()); - } - - @Test - void fetchShoppingList_WithNullKey_ShouldNotFetchAnyShoppingList() { - // test - final Optional result = service.fetchShoppingList(null).toCompletableFuture().join(); - - // assertions - assertThat(result).isEmpty(); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - verify(shoppingListSyncOptions.getCtpClient(), never()).execute(any()); - } - - @Test - void fetchShoppingList_WithValidKey_ShouldReturnMockShoppingList() { - // preparation - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getId()).thenReturn("testId"); - when(mockShoppingList.getKey()).thenReturn("any_key"); - - @SuppressWarnings("unchecked") - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - when(pagedQueryResult.head()).thenReturn(Optional.of(mockShoppingList)); - when(shoppingListSyncOptions.getCtpClient().execute(any(ShoppingListQuery.class))) - .thenReturn(completedFuture(pagedQueryResult)); - - // test - final Optional result = - service.fetchShoppingList("any_key").toCompletableFuture().join(); - - // assertions - assertThat(result).containsSame(mockShoppingList); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - verify(shoppingListSyncOptions.getCtpClient(), only()).execute(any()); - } - - @Test - void fetchMatchingShoppingListsByKeys_WithUnexpectedException_ShouldFail() { - when(shoppingListSyncOptions.getCtpClient().execute(any())).thenReturn( - CompletableFutureUtils.failed(new BadGatewayException("bad gateway"))); - - assertThat(service.fetchMatchingShoppingListsByKeys(singleton("key"))) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(BadGatewayException.class); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - } - - @Test - void fetchMatchingShoppingListsByKeys_WithEmptyKeys_ShouldReturnEmptyOptional() { - Set customer = service.fetchMatchingShoppingListsByKeys(emptySet()).toCompletableFuture().join(); - - assertThat(customer).isEmpty(); - assertThat(errorExceptions).isEmpty(); - assertThat(errorMessages).isEmpty(); - verifyNoInteractions(shoppingListSyncOptions.getCtpClient()); - } - - - @Test - void createShoppingList_WithNullShoppingListKey_ShouldNotCreateShoppingList() { - // preparation - final ShoppingListDraft mockShoppingListDraft = mock(ShoppingListDraft.class); - final Map errors = new HashMap<>(); - when(mockShoppingListDraft.getKey()).thenReturn(null); - - final ShoppingListSyncOptions shoppingListSyncOptions = ShoppingListSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> - errors.put(exception.getMessage(), exception)) - .build(); - final ShoppingListService shoppingListService = new ShoppingListServiceImpl(shoppingListSyncOptions); - - // test - final CompletionStage> result = shoppingListService - .createShoppingList(mockShoppingListDraft); - - // assertions - assertThat(result).isCompletedWithValue(Optional.empty()); - assertThat(errors.keySet()) - .containsExactly("Failed to create draft with key: 'null'. Reason: Draft key is blank!"); - verify(shoppingListSyncOptions.getCtpClient(), times(0)).execute(any()); - } - - @Test - void createShoppingList_WithEmptyShoppingListKey_ShouldHaveEmptyOptionalAsAResult() { - //preparation - final SphereClient sphereClient = mock(SphereClient.class); - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - final Map errors = new HashMap<>(); - when(shoppingListDraft.getKey()).thenReturn(""); - - final ShoppingListSyncOptions options = ShoppingListSyncOptionsBuilder - .of(sphereClient) - .errorCallback((exception, oldResource, newResource, actions) -> + service = new ShoppingListServiceImpl(shoppingListSyncOptions); + } + + @Test + void fetchShoppingList_WithEmptyKey_ShouldNotFetchAnyShoppingList() { + // test + final Optional result = + service.fetchShoppingList("").toCompletableFuture().join(); + + // assertions + assertThat(result).isEmpty(); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + verify(shoppingListSyncOptions.getCtpClient(), never()).execute(any()); + } + + @Test + void fetchShoppingList_WithNullKey_ShouldNotFetchAnyShoppingList() { + // test + final Optional result = + service.fetchShoppingList(null).toCompletableFuture().join(); + + // assertions + assertThat(result).isEmpty(); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + verify(shoppingListSyncOptions.getCtpClient(), never()).execute(any()); + } + + @Test + void fetchShoppingList_WithValidKey_ShouldReturnMockShoppingList() { + // preparation + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getId()).thenReturn("testId"); + when(mockShoppingList.getKey()).thenReturn("any_key"); + + @SuppressWarnings("unchecked") + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + when(pagedQueryResult.head()).thenReturn(Optional.of(mockShoppingList)); + when(shoppingListSyncOptions.getCtpClient().execute(any(ShoppingListQuery.class))) + .thenReturn(completedFuture(pagedQueryResult)); + + // test + final Optional result = + service.fetchShoppingList("any_key").toCompletableFuture().join(); + + // assertions + assertThat(result).containsSame(mockShoppingList); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + verify(shoppingListSyncOptions.getCtpClient(), only()).execute(any()); + } + + @Test + void fetchMatchingShoppingListsByKeys_WithUnexpectedException_ShouldFail() { + when(shoppingListSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadGatewayException("bad gateway"))); + + assertThat(service.fetchMatchingShoppingListsByKeys(singleton("key"))) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(BadGatewayException.class); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + } + + @Test + void fetchMatchingShoppingListsByKeys_WithEmptyKeys_ShouldReturnEmptyOptional() { + Set customer = + service.fetchMatchingShoppingListsByKeys(emptySet()).toCompletableFuture().join(); + + assertThat(customer).isEmpty(); + assertThat(errorExceptions).isEmpty(); + assertThat(errorMessages).isEmpty(); + verifyNoInteractions(shoppingListSyncOptions.getCtpClient()); + } + + @Test + void createShoppingList_WithNullShoppingListKey_ShouldNotCreateShoppingList() { + // preparation + final ShoppingListDraft mockShoppingListDraft = mock(ShoppingListDraft.class); + final Map errors = new HashMap<>(); + when(mockShoppingListDraft.getKey()).thenReturn(null); + + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> + errors.put(exception.getMessage(), exception)) + .build(); + final ShoppingListService shoppingListService = + new ShoppingListServiceImpl(shoppingListSyncOptions); + + // test + final CompletionStage> result = + shoppingListService.createShoppingList(mockShoppingListDraft); + + // assertions + assertThat(result).isCompletedWithValue(Optional.empty()); + assertThat(errors.keySet()) + .containsExactly("Failed to create draft with key: 'null'. Reason: Draft key is blank!"); + verify(shoppingListSyncOptions.getCtpClient(), times(0)).execute(any()); + } + + @Test + void createShoppingList_WithEmptyShoppingListKey_ShouldHaveEmptyOptionalAsAResult() { + // preparation + final SphereClient sphereClient = mock(SphereClient.class); + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + final Map errors = new HashMap<>(); + when(shoppingListDraft.getKey()).thenReturn(""); + + final ShoppingListSyncOptions options = + ShoppingListSyncOptionsBuilder.of(sphereClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> + errors.put(exception.getMessage(), exception)) + .build(); + + final ShoppingListService shoppingListService = new ShoppingListServiceImpl(options); + + // test + final CompletionStage> result = + shoppingListService.createShoppingList(shoppingListDraft); + + // assertion + assertThat(result).isCompletedWithValue(Optional.empty()); + assertThat(errors.keySet()) + .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); + verify(options.getCtpClient(), times(0)).execute(any()); + } + + @Test + void createShoppingList_WithUnsuccessfulMockCtpResponse_ShouldNotCreateShoppingList() { + // preparation + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + final Map errors = new HashMap<>(); + when(shoppingListDraft.getKey()).thenReturn("key"); + + final ShoppingListSyncOptions options = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> errors.put(exception.getMessage(), exception)) .build(); - final ShoppingListService shoppingListService = new ShoppingListServiceImpl(options); - - // test - final CompletionStage> result = shoppingListService - .createShoppingList(shoppingListDraft); - - // assertion - assertThat(result).isCompletedWithValue(Optional.empty()); - assertThat(errors.keySet()) - .containsExactly("Failed to create draft with key: ''. Reason: Draft key is blank!"); - verify(options.getCtpClient(), times(0)).execute(any()); - } - - @Test - void createShoppingList_WithUnsuccessfulMockCtpResponse_ShouldNotCreateShoppingList() { - // preparation - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - final Map errors = new HashMap<>(); - when(shoppingListDraft.getKey()).thenReturn("key"); - - final ShoppingListSyncOptions options = ShoppingListSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> - errors.put(exception.getMessage(), exception)) - .build(); - - final ShoppingListService shoppingListService = new ShoppingListServiceImpl(options); - - when(options.getCtpClient().execute(any())) - .thenReturn(CompletableFutureUtils.failed(new InternalServerErrorException())); - - // test - final CompletionStage> result = - shoppingListService.createShoppingList(shoppingListDraft); - - // assertions - assertThat(result).isCompletedWithValue(Optional.empty()); - assertThat(errors.keySet()) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> { - assertThat(message).contains("Failed to create draft with key: 'key'."); - }); - - assertThat(errors.values()) - .hasSize(1) - .singleElement().satisfies(exception -> { - assertThat(exception).isExactlyInstanceOf(SyncException.class); - assertThat(exception.getCause()).isExactlyInstanceOf(InternalServerErrorException.class); - }); - } - - @Test - void updateShoppingList_WithMockSuccessfulCtpResponse_ShouldCallShoppingListUpdateCommand() { - // preparation - final ShoppingList shoppingList = mock(ShoppingList.class); - final ShoppingListSyncOptions options = ShoppingListSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); - - when(options.getCtpClient().execute(any())).thenReturn(completedFuture(shoppingList)); - final ShoppingListService shoppingListService = new ShoppingListServiceImpl(options); - - final List> updateActions = - singletonList(ChangeName.of(LocalizedString.ofEnglish("new_name"))); - // test - final CompletionStage result = - shoppingListService.updateShoppingList(shoppingList, updateActions); - - // assertions - assertThat(result).isCompletedWithValue(shoppingList); - verify(options.getCtpClient()) - .execute(eq(ShoppingListUpdateCommand.of(shoppingList, updateActions))); - } - - @Test - void updateShoppingList_WithMockUnsuccessfulCtpResponse_ShouldCompleteExceptionally() { - // preparation - final ShoppingList shoppingList = mock(ShoppingList.class); - final ShoppingListSyncOptions shoppingListSyncOptions = ShoppingListSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); - - when(shoppingListSyncOptions.getCtpClient().execute(any())) - .thenReturn(CompletableFutureUtils.exceptionallyCompletedFuture(new InternalServerErrorException())); - - final ShoppingListService shoppingListService = new ShoppingListServiceImpl(shoppingListSyncOptions); - - final List> updateActions = - singletonList(ChangeName.of(LocalizedString.ofEnglish("new_name"))); - // test - final CompletionStage result = - shoppingListService.updateShoppingList(shoppingList, updateActions); - - // assertions - assertThat(result).hasFailedWithThrowableThat() - .isExactlyInstanceOf(InternalServerErrorException.class); - } + final ShoppingListService shoppingListService = new ShoppingListServiceImpl(options); + + when(options.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new InternalServerErrorException())); + + // test + final CompletionStage> result = + shoppingListService.createShoppingList(shoppingListDraft); + + // assertions + assertThat(result).isCompletedWithValue(Optional.empty()); + assertThat(errors.keySet()) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> { + assertThat(message).contains("Failed to create draft with key: 'key'."); + }); + + assertThat(errors.values()) + .hasSize(1) + .singleElement() + .satisfies( + exception -> { + assertThat(exception).isExactlyInstanceOf(SyncException.class); + assertThat(exception.getCause()) + .isExactlyInstanceOf(InternalServerErrorException.class); + }); + } + + @Test + void updateShoppingList_WithMockSuccessfulCtpResponse_ShouldCallShoppingListUpdateCommand() { + // preparation + final ShoppingList shoppingList = mock(ShoppingList.class); + final ShoppingListSyncOptions options = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + when(options.getCtpClient().execute(any())).thenReturn(completedFuture(shoppingList)); + final ShoppingListService shoppingListService = new ShoppingListServiceImpl(options); + + final List> updateActions = + singletonList(ChangeName.of(LocalizedString.ofEnglish("new_name"))); + // test + final CompletionStage result = + shoppingListService.updateShoppingList(shoppingList, updateActions); + + // assertions + assertThat(result).isCompletedWithValue(shoppingList); + verify(options.getCtpClient()) + .execute(eq(ShoppingListUpdateCommand.of(shoppingList, updateActions))); + } + + @Test + void updateShoppingList_WithMockUnsuccessfulCtpResponse_ShouldCompleteExceptionally() { + // preparation + final ShoppingList shoppingList = mock(ShoppingList.class); + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + when(shoppingListSyncOptions.getCtpClient().execute(any())) + .thenReturn( + CompletableFutureUtils.exceptionallyCompletedFuture( + new InternalServerErrorException())); + + final ShoppingListService shoppingListService = + new ShoppingListServiceImpl(shoppingListSyncOptions); + + final List> updateActions = + singletonList(ChangeName.of(LocalizedString.ofEnglish("new_name"))); + // test + final CompletionStage result = + shoppingListService.updateShoppingList(shoppingList, updateActions); + + // assertions + assertThat(result) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(InternalServerErrorException.class); + } } diff --git a/src/test/java/com/commercetools/sync/services/impl/StateServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/StateServiceImplTest.java index 42f3589ea9..fd31744529 100644 --- a/src/test/java/com/commercetools/sync/services/impl/StateServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/StateServiceImplTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.services.impl; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.states.StateSyncOptions; import com.commercetools.sync.states.StateSyncOptionsBuilder; import io.sphere.sdk.client.BadRequestException; @@ -15,12 +25,6 @@ import io.sphere.sdk.states.commands.updateactions.ChangeInitial; import io.sphere.sdk.states.queries.StateQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,236 +33,237 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; - -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; class StateServiceImplTest { - private SphereClient client = mock(SphereClient.class); - private StateServiceImpl service; - private List errorMessages; - private List errorExceptions; - - private String stateId; - private String stateKey; - - @BeforeEach - void setup() { - stateId = RandomStringUtils.random(15); - stateKey = RandomStringUtils.random(15); - - errorMessages = new ArrayList<>(); - errorExceptions = new ArrayList<>(); - StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(client) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errorExceptions.add(exception.getCause()); - }) + private SphereClient client = mock(SphereClient.class); + private StateServiceImpl service; + private List errorMessages; + private List errorExceptions; + + private String stateId; + private String stateKey; + + @BeforeEach + void setup() { + stateId = RandomStringUtils.random(15); + stateKey = RandomStringUtils.random(15); + + errorMessages = new ArrayList<>(); + errorExceptions = new ArrayList<>(); + StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(client) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errorExceptions.add(exception.getCause()); + }) .build(); - service = new StateServiceImpl(stateSyncOptions); - } + service = new StateServiceImpl(stateSyncOptions); + } + + @AfterEach + void cleanup() { + reset(client); + } + + private interface StatePagedQueryResult extends PagedQueryResult {} + + @Test + void fetchCachedStateId_WithKey_ShouldFetchState() { + final String key = RandomStringUtils.random(15); + final String id = RandomStringUtils.random(15); + + State mock = mock(State.class); + when(mock.getId()).thenReturn(id); + when(mock.getKey()).thenReturn(key); + + StatePagedQueryResult result = mock(StatePagedQueryResult.class); + when(result.getResults()).thenReturn(Collections.singletonList(mock)); + + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - @AfterEach - void cleanup() { - reset(client); - } + Optional fetchedId = service.fetchCachedStateId(key).toCompletableFuture().join(); - private interface StatePagedQueryResult extends PagedQueryResult { - } + assertThat(fetchedId).contains(id); + } - @Test - void fetchCachedStateId_WithKey_ShouldFetchState() { - final String key = RandomStringUtils.random(15); - final String id = RandomStringUtils.random(15); - - State mock = mock(State.class); - when(mock.getId()).thenReturn(id); - when(mock.getKey()).thenReturn(key); - - StatePagedQueryResult result = mock(StatePagedQueryResult.class); - when(result.getResults()).thenReturn(Collections.singletonList(mock)); + @Test + void buildStateQuery_WithType_ShouldBuildQuery() { + final QueryPredicate stateQueryPredicate = + QueryPredicate.of(format("type= \"%s\"", StateType.LINE_ITEM_STATE.toSphereName())); + final StateQuery stateQuery = StateQuery.of().withPredicates(stateQueryPredicate); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + assertThat(stateQuery).isNotNull(); + assertThat(stateQuery.toString()).contains(StateType.LINE_ITEM_STATE.toSphereName()); + } - Optional fetchedId = service.fetchCachedStateId(key).toCompletableFuture().join(); + @Test + void fetchMatchingStatesByKeys_WithKeySet_ShouldFetchStates() { + String key1 = RandomStringUtils.random(15); + String key2 = RandomStringUtils.random(15); - assertThat(fetchedId).contains(id); - } - - @Test - void buildStateQuery_WithType_ShouldBuildQuery() { - final QueryPredicate stateQueryPredicate = - QueryPredicate.of(format("type= \"%s\"", StateType.LINE_ITEM_STATE.toSphereName())); - final StateQuery stateQuery = StateQuery.of().withPredicates(stateQueryPredicate); - - assertThat(stateQuery).isNotNull(); - assertThat(stateQuery.toString()).contains(StateType.LINE_ITEM_STATE.toSphereName()); - } - - @Test - void fetchMatchingStatesByKeys_WithKeySet_ShouldFetchStates() { - String key1 = RandomStringUtils.random(15); - String key2 = RandomStringUtils.random(15); - - HashSet stateKeys = new HashSet<>(); - stateKeys.add(key1); - stateKeys.add(key2); - - State mock1 = mock(State.class); - when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock1.getKey()).thenReturn(key1); + HashSet stateKeys = new HashSet<>(); + stateKeys.add(key1); + stateKeys.add(key2); + + State mock1 = mock(State.class); + when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock1.getKey()).thenReturn(key1); - State mock2 = mock(State.class); - when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock2.getKey()).thenReturn(key2); + State mock2 = mock(State.class); + when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock2.getKey()).thenReturn(key2); - StatePagedQueryResult result = mock(StatePagedQueryResult.class); - when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); + StatePagedQueryResult result = mock(StatePagedQueryResult.class); + when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - Set states = service.fetchMatchingStatesByKeys(stateKeys).toCompletableFuture().join(); - - assertAll( - () -> assertThat(states).isNotEmpty(), - () -> assertThat(states).contains(mock1, mock2), - () -> assertThat(service.keyToIdCache.asMap()).containsKeys(key1, key2) - ); - ArgumentCaptor captor = ArgumentCaptor.forClass(StateQuery.class); - verify(client).execute(captor.capture()); - assertThat(captor.getValue().expansionPaths()).isEmpty(); - } - - @Test - void shouldFetchStatesByKeysWithExpandedTransitions() { - String key1 = RandomStringUtils.random(15); - String key2 = RandomStringUtils.random(15); - - HashSet stateKeys = new HashSet<>(); - stateKeys.add(key1); - stateKeys.add(key2); - - State mock1 = mock(State.class); - when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock1.getKey()).thenReturn(key1); - - State mock2 = mock(State.class); - when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock2.getKey()).thenReturn(key2); - - StatePagedQueryResult result = mock(StatePagedQueryResult.class); - when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); - - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - - Set states = service.fetchMatchingStatesByKeysWithTransitions(stateKeys).toCompletableFuture().join(); - - assertAll( - () -> assertThat(states).isNotEmpty(), - () -> assertThat(states).contains(mock1, mock2), - () -> assertThat(service.keyToIdCache.asMap()).containsKeys(key1, key2) - ); - - ArgumentCaptor captor = ArgumentCaptor.forClass(StateQuery.class); - verify(client).execute(captor.capture()); - assertAll( - () -> assertThat(captor.getValue().expansionPaths()).hasSize(1), - () -> assertThat(captor.getValue().expansionPaths().get(0).toSphereExpand()).contains("transitions[*]") - ); - } - - @Test - void fetchState_WithKey_ShouldFetchState() { - State mock = mock(State.class); - when(mock.getId()).thenReturn(stateId); - when(mock.getKey()).thenReturn(stateKey); - StatePagedQueryResult result = mock(StatePagedQueryResult.class); - when(result.head()).thenReturn(Optional.of(mock)); - - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - - Optional stateOptional = service.fetchState(stateKey).toCompletableFuture().join(); - - assertAll( - () -> assertThat(stateOptional).containsSame(mock), - () -> assertThat(service.keyToIdCache.getIfPresent(stateKey)).isEqualTo(stateId) - ); - verify(client).execute(any(StateQuery.class)); - } - - @Test - void createState_WithDraft_ShouldCreateState() { - State mock = mock(State.class); - when(mock.getId()).thenReturn(stateId); - when(mock.getKey()).thenReturn(stateKey); - - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); - - StateDraft draft = StateDraft.of(stateKey, StateType.LINE_ITEM_STATE); - Optional stateOptional = service.createState(draft).toCompletableFuture().join(); - - assertThat(stateOptional).containsSame(mock); - verify(client).execute(eq(StateCreateCommand.of(draft))); - } - - @Test - void createState_WithRequestException_ShouldNotCreateState() { - State mock = mock(State.class); - when(mock.getId()).thenReturn(stateId); - - when(client.execute(any())).thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); - - StateDraft draft = mock(StateDraft.class); - when(draft.getKey()).thenReturn(stateKey); - - Optional stateOptional = service.createState(draft).toCompletableFuture().join(); - - assertAll( - () -> assertThat(stateOptional).isEmpty(), - () -> assertThat(errorMessages).hasSize(1), - () -> assertThat(errorMessages).hasOnlyOneElementSatisfying(message -> { - assertThat(message).contains("Failed to create draft with key: '" + stateKey + "'."); - assertThat(message).contains("BadRequestException"); - }), - () -> assertThat(errorExceptions).hasSize(1), - () -> assertThat(errorExceptions).hasOnlyOneElementSatisfying(exception -> - assertThat(exception).isExactlyInstanceOf(BadRequestException.class)) - ); - } - - @Test - void createState_WithDraftHasNoKey_ShouldNotCreateState() { - StateDraft draft = mock(StateDraft.class); - - Optional stateOptional = service.createState(draft).toCompletableFuture().join(); - - assertAll( - () -> assertThat(stateOptional).isEmpty(), - () -> assertThat(errorMessages).hasSize(1), - () -> assertThat(errorExceptions).hasSize(1), - () -> assertThat(errorMessages.get(0)) - .isEqualTo("Failed to create draft with key: 'null'. Reason: Draft key is blank!") - ); - } - - @Test - void updateState_WithNoError_ShouldUpdateState() { - State mock = mock(State.class); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); - List> updateActions = Collections.singletonList(ChangeInitial.of(false)); - - State state = service.updateState(mock, updateActions).toCompletableFuture().join(); - - assertThat(state).isSameAs(mock); - verify(client).execute(eq(StateUpdateCommand.of(mock, updateActions))); - } + Set states = service.fetchMatchingStatesByKeys(stateKeys).toCompletableFuture().join(); + assertAll( + () -> assertThat(states).isNotEmpty(), + () -> assertThat(states).contains(mock1, mock2), + () -> assertThat(service.keyToIdCache.asMap()).containsKeys(key1, key2)); + ArgumentCaptor captor = ArgumentCaptor.forClass(StateQuery.class); + verify(client).execute(captor.capture()); + assertThat(captor.getValue().expansionPaths()).isEmpty(); + } + + @Test + void shouldFetchStatesByKeysWithExpandedTransitions() { + String key1 = RandomStringUtils.random(15); + String key2 = RandomStringUtils.random(15); + + HashSet stateKeys = new HashSet<>(); + stateKeys.add(key1); + stateKeys.add(key2); + + State mock1 = mock(State.class); + when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock1.getKey()).thenReturn(key1); + + State mock2 = mock(State.class); + when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock2.getKey()).thenReturn(key2); + + StatePagedQueryResult result = mock(StatePagedQueryResult.class); + when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); + + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + + Set states = + service.fetchMatchingStatesByKeysWithTransitions(stateKeys).toCompletableFuture().join(); + + assertAll( + () -> assertThat(states).isNotEmpty(), + () -> assertThat(states).contains(mock1, mock2), + () -> assertThat(service.keyToIdCache.asMap()).containsKeys(key1, key2)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(StateQuery.class); + verify(client).execute(captor.capture()); + assertAll( + () -> assertThat(captor.getValue().expansionPaths()).hasSize(1), + () -> + assertThat(captor.getValue().expansionPaths().get(0).toSphereExpand()) + .contains("transitions[*]")); + } + + @Test + void fetchState_WithKey_ShouldFetchState() { + State mock = mock(State.class); + when(mock.getId()).thenReturn(stateId); + when(mock.getKey()).thenReturn(stateKey); + StatePagedQueryResult result = mock(StatePagedQueryResult.class); + when(result.head()).thenReturn(Optional.of(mock)); + + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + + Optional stateOptional = service.fetchState(stateKey).toCompletableFuture().join(); + + assertAll( + () -> assertThat(stateOptional).containsSame(mock), + () -> assertThat(service.keyToIdCache.getIfPresent(stateKey)).isEqualTo(stateId)); + verify(client).execute(any(StateQuery.class)); + } + + @Test + void createState_WithDraft_ShouldCreateState() { + State mock = mock(State.class); + when(mock.getId()).thenReturn(stateId); + when(mock.getKey()).thenReturn(stateKey); + + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); + + StateDraft draft = StateDraft.of(stateKey, StateType.LINE_ITEM_STATE); + Optional stateOptional = service.createState(draft).toCompletableFuture().join(); + + assertThat(stateOptional).containsSame(mock); + verify(client).execute(eq(StateCreateCommand.of(draft))); + } + + @Test + void createState_WithRequestException_ShouldNotCreateState() { + State mock = mock(State.class); + when(mock.getId()).thenReturn(stateId); + + when(client.execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); + + StateDraft draft = mock(StateDraft.class); + when(draft.getKey()).thenReturn(stateKey); + + Optional stateOptional = service.createState(draft).toCompletableFuture().join(); + + assertAll( + () -> assertThat(stateOptional).isEmpty(), + () -> assertThat(errorMessages).hasSize(1), + () -> + assertThat(errorMessages) + .hasOnlyOneElementSatisfying( + message -> { + assertThat(message) + .contains("Failed to create draft with key: '" + stateKey + "'."); + assertThat(message).contains("BadRequestException"); + }), + () -> assertThat(errorExceptions).hasSize(1), + () -> + assertThat(errorExceptions) + .hasOnlyOneElementSatisfying( + exception -> + assertThat(exception).isExactlyInstanceOf(BadRequestException.class))); + } + + @Test + void createState_WithDraftHasNoKey_ShouldNotCreateState() { + StateDraft draft = mock(StateDraft.class); + + Optional stateOptional = service.createState(draft).toCompletableFuture().join(); + + assertAll( + () -> assertThat(stateOptional).isEmpty(), + () -> assertThat(errorMessages).hasSize(1), + () -> assertThat(errorExceptions).hasSize(1), + () -> + assertThat(errorMessages.get(0)) + .isEqualTo("Failed to create draft with key: 'null'. Reason: Draft key is blank!")); + } + + @Test + void updateState_WithNoError_ShouldUpdateState() { + State mock = mock(State.class); + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); + List> updateActions = Collections.singletonList(ChangeInitial.of(false)); + + State state = service.updateState(mock, updateActions).toCompletableFuture().join(); + + assertThat(state).isSameAs(mock); + verify(client).execute(eq(StateUpdateCommand.of(mock, updateActions))); + } } diff --git a/src/test/java/com/commercetools/sync/services/impl/TaxCategoryServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/TaxCategoryServiceImplTest.java index 7a8e28bd9e..0538cdefe1 100644 --- a/src/test/java/com/commercetools/sync/services/impl/TaxCategoryServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/TaxCategoryServiceImplTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.services.impl; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.taxcategories.TaxCategorySyncOptions; import com.commercetools.sync.taxcategories.TaxCategorySyncOptionsBuilder; import io.sphere.sdk.client.BadRequestException; @@ -14,11 +23,6 @@ import io.sphere.sdk.taxcategories.commands.updateactions.ChangeName; import io.sphere.sdk.taxcategories.queries.TaxCategoryQuery; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -27,189 +31,196 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class TaxCategoryServiceImplTest { - private SphereClient client = mock(SphereClient.class); - private TaxCategoryServiceImpl service; - private List errorMessages; - private List errorExceptions; - - private String taxCategoryId; - private String taxCategoryName; - private String taxCategoryKey; - - @BeforeEach - void setup() { - taxCategoryId = RandomStringUtils.random(15); - taxCategoryName = RandomStringUtils.random(15); - taxCategoryKey = RandomStringUtils.random(15); - - errorMessages = new ArrayList<>(); - errorExceptions = new ArrayList<>(); - TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(client) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errorExceptions.add(exception.getCause()); - }) + private SphereClient client = mock(SphereClient.class); + private TaxCategoryServiceImpl service; + private List errorMessages; + private List errorExceptions; + + private String taxCategoryId; + private String taxCategoryName; + private String taxCategoryKey; + + @BeforeEach + void setup() { + taxCategoryId = RandomStringUtils.random(15); + taxCategoryName = RandomStringUtils.random(15); + taxCategoryKey = RandomStringUtils.random(15); + + errorMessages = new ArrayList<>(); + errorExceptions = new ArrayList<>(); + TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(client) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errorExceptions.add(exception.getCause()); + }) .build(); - service = new TaxCategoryServiceImpl(taxCategorySyncOptions); - } + service = new TaxCategoryServiceImpl(taxCategorySyncOptions); + } - @AfterEach - void cleanup() { - reset(client); - } + @AfterEach + void cleanup() { + reset(client); + } - private interface TaxCategoryPagedQueryResult extends PagedQueryResult { - } + private interface TaxCategoryPagedQueryResult extends PagedQueryResult {} - @Test - void fetchCachedTaxCategoryId_WithKey_ShouldFetchTaxCategory() { - final String key = RandomStringUtils.random(15); - final String id = RandomStringUtils.random(15); + @Test + void fetchCachedTaxCategoryId_WithKey_ShouldFetchTaxCategory() { + final String key = RandomStringUtils.random(15); + final String id = RandomStringUtils.random(15); - final TaxCategory mock = mock(TaxCategory.class); - when(mock.getId()).thenReturn(id); - when(mock.getKey()).thenReturn(key); + final TaxCategory mock = mock(TaxCategory.class); + when(mock.getId()).thenReturn(id); + when(mock.getKey()).thenReturn(key); - final TaxCategoryPagedQueryResult result = mock(TaxCategoryPagedQueryResult.class); - when(result.getResults()).thenReturn(Collections.singletonList(mock)); + final TaxCategoryPagedQueryResult result = mock(TaxCategoryPagedQueryResult.class); + when(result.getResults()).thenReturn(Collections.singletonList(mock)); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - final Optional fetchedId = service.fetchCachedTaxCategoryId(key).toCompletableFuture().join(); + final Optional fetchedId = + service.fetchCachedTaxCategoryId(key).toCompletableFuture().join(); - assertThat(fetchedId).contains(id); - } + assertThat(fetchedId).contains(id); + } - @Test - void fetchMatchingTaxCategoriesByKeys_WithKeySet_ShouldFetchTaxCategories() { - final String key1 = RandomStringUtils.random(15); - final String key2 = RandomStringUtils.random(15); + @Test + void fetchMatchingTaxCategoriesByKeys_WithKeySet_ShouldFetchTaxCategories() { + final String key1 = RandomStringUtils.random(15); + final String key2 = RandomStringUtils.random(15); - final HashSet taxCategoryKeys = new HashSet<>(); - taxCategoryKeys.add(key1); - taxCategoryKeys.add(key2); + final HashSet taxCategoryKeys = new HashSet<>(); + taxCategoryKeys.add(key1); + taxCategoryKeys.add(key2); - final TaxCategory mock1 = mock(TaxCategory.class); - when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock1.getKey()).thenReturn(key1); + final TaxCategory mock1 = mock(TaxCategory.class); + when(mock1.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock1.getKey()).thenReturn(key1); - final TaxCategory mock2 = mock(TaxCategory.class); - when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); - when(mock2.getKey()).thenReturn(key2); + final TaxCategory mock2 = mock(TaxCategory.class); + when(mock2.getId()).thenReturn(RandomStringUtils.random(15)); + when(mock2.getKey()).thenReturn(key2); - final TaxCategoryPagedQueryResult result = mock(TaxCategoryPagedQueryResult.class); - when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); + final TaxCategoryPagedQueryResult result = mock(TaxCategoryPagedQueryResult.class); + when(result.getResults()).thenReturn(Arrays.asList(mock1, mock2)); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - final Set taxCategories = service.fetchMatchingTaxCategoriesByKeys(taxCategoryKeys) - .toCompletableFuture().join(); + final Set taxCategories = + service.fetchMatchingTaxCategoriesByKeys(taxCategoryKeys).toCompletableFuture().join(); - assertAll( - () -> assertThat(taxCategories).contains(mock1, mock2), - () -> assertThat(service.keyToIdCache.asMap()).containsKeys(key1, key2) - ); - verify(client).execute(any(TaxCategoryQuery.class)); - } + assertAll( + () -> assertThat(taxCategories).contains(mock1, mock2), + () -> assertThat(service.keyToIdCache.asMap()).containsKeys(key1, key2)); + verify(client).execute(any(TaxCategoryQuery.class)); + } - @Test - void fetchTaxCategory_WithKey_ShouldFetchTaxCategory() { - final TaxCategory mock = mock(TaxCategory.class); - when(mock.getId()).thenReturn(taxCategoryId); - when(mock.getKey()).thenReturn(taxCategoryKey); - final TaxCategoryPagedQueryResult result = mock(TaxCategoryPagedQueryResult.class); - when(result.head()).thenReturn(Optional.of(mock)); + @Test + void fetchTaxCategory_WithKey_ShouldFetchTaxCategory() { + final TaxCategory mock = mock(TaxCategory.class); + when(mock.getId()).thenReturn(taxCategoryId); + when(mock.getKey()).thenReturn(taxCategoryKey); + final TaxCategoryPagedQueryResult result = mock(TaxCategoryPagedQueryResult.class); + when(result.head()).thenReturn(Optional.of(mock)); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(result)); - final Optional taxCategoryOptional = service.fetchTaxCategory(taxCategoryKey) - .toCompletableFuture().join(); + final Optional taxCategoryOptional = + service.fetchTaxCategory(taxCategoryKey).toCompletableFuture().join(); - assertAll( - () -> assertThat(taxCategoryOptional).containsSame(mock), - () -> assertThat(service.keyToIdCache.asMap().get(taxCategoryKey)).isEqualTo(taxCategoryId) - ); - verify(client).execute(any(TaxCategoryQuery.class)); - } + assertAll( + () -> assertThat(taxCategoryOptional).containsSame(mock), + () -> + assertThat(service.keyToIdCache.asMap().get(taxCategoryKey)).isEqualTo(taxCategoryId)); + verify(client).execute(any(TaxCategoryQuery.class)); + } - @Test - void createTaxCategory_WithDraft_ShouldCreateTaxCategory() { - final TaxCategory mock = mock(TaxCategory.class); - when(mock.getId()).thenReturn(taxCategoryId); - when(mock.getKey()).thenReturn(taxCategoryKey); + @Test + void createTaxCategory_WithDraft_ShouldCreateTaxCategory() { + final TaxCategory mock = mock(TaxCategory.class); + when(mock.getId()).thenReturn(taxCategoryId); + when(mock.getKey()).thenReturn(taxCategoryKey); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); - final TaxCategoryDraft draft = TaxCategoryDraftBuilder - .of(taxCategoryName, Collections.emptyList(), "description") + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of(taxCategoryName, Collections.emptyList(), "description") .key(taxCategoryKey) .build(); - final Optional taxCategoryOptional = service.createTaxCategory(draft).toCompletableFuture().join(); - - assertThat(taxCategoryOptional).containsSame(mock); - verify(client).execute(eq(TaxCategoryCreateCommand.of(draft))); - } - - @Test - void createTaxCategory_WithRequestException_ShouldNotCreateTaxCategory() { - final TaxCategory mock = mock(TaxCategory.class); - when(mock.getId()).thenReturn(taxCategoryId); - - when(client.execute(any())).thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); - - final TaxCategoryDraft draft = mock(TaxCategoryDraft.class); - when(draft.getKey()).thenReturn(taxCategoryKey); - - final Optional taxCategoryOptional = service.createTaxCategory(draft).toCompletableFuture().join(); - - assertAll( - () -> assertThat(taxCategoryOptional).isEmpty(), - () -> assertThat(errorMessages).hasOnlyOneElementSatisfying(message -> { - assertThat(message).contains("Failed to create draft with key: '" + taxCategoryKey + "'."); - assertThat(message).contains("BadRequestException"); - }), - () -> assertThat(errorExceptions).hasOnlyOneElementSatisfying(exception -> - assertThat(exception).isExactlyInstanceOf(BadRequestException.class)) - ); - } - - @Test - void createTaxCategory_WithDraftHasNoKey_ShouldNotCreateTaxCategory() { - final TaxCategoryDraft draft = mock(TaxCategoryDraft.class); - - final Optional taxCategoryOptional = service.createTaxCategory(draft).toCompletableFuture().join(); - - assertAll( - () -> assertThat(taxCategoryOptional).isEmpty(), - () -> assertThat(errorMessages).hasSize(1), - () -> assertThat(errorExceptions).hasSize(1), - () -> assertThat(errorMessages) - .contains("Failed to create draft with key: 'null'. Reason: Draft key is blank!") - ); - } - - @Test - void updateTaxCategory_WithNoError_ShouldUpdateTaxCategory() { - final TaxCategory mock = mock(TaxCategory.class); - when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); - final List> updateActions = Collections.singletonList(ChangeName.of("name")); - - final TaxCategory taxCategory = service.updateTaxCategory(mock, updateActions).toCompletableFuture().join(); - - assertThat(taxCategory).isSameAs(mock); - verify(client).execute(eq(TaxCategoryUpdateCommand.of(mock, updateActions))); - } - + final Optional taxCategoryOptional = + service.createTaxCategory(draft).toCompletableFuture().join(); + + assertThat(taxCategoryOptional).containsSame(mock); + verify(client).execute(eq(TaxCategoryCreateCommand.of(draft))); + } + + @Test + void createTaxCategory_WithRequestException_ShouldNotCreateTaxCategory() { + final TaxCategory mock = mock(TaxCategory.class); + when(mock.getId()).thenReturn(taxCategoryId); + + when(client.execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); + + final TaxCategoryDraft draft = mock(TaxCategoryDraft.class); + when(draft.getKey()).thenReturn(taxCategoryKey); + + final Optional taxCategoryOptional = + service.createTaxCategory(draft).toCompletableFuture().join(); + + assertAll( + () -> assertThat(taxCategoryOptional).isEmpty(), + () -> + assertThat(errorMessages) + .hasOnlyOneElementSatisfying( + message -> { + assertThat(message) + .contains("Failed to create draft with key: '" + taxCategoryKey + "'."); + assertThat(message).contains("BadRequestException"); + }), + () -> + assertThat(errorExceptions) + .hasOnlyOneElementSatisfying( + exception -> + assertThat(exception).isExactlyInstanceOf(BadRequestException.class))); + } + + @Test + void createTaxCategory_WithDraftHasNoKey_ShouldNotCreateTaxCategory() { + final TaxCategoryDraft draft = mock(TaxCategoryDraft.class); + + final Optional taxCategoryOptional = + service.createTaxCategory(draft).toCompletableFuture().join(); + + assertAll( + () -> assertThat(taxCategoryOptional).isEmpty(), + () -> assertThat(errorMessages).hasSize(1), + () -> assertThat(errorExceptions).hasSize(1), + () -> + assertThat(errorMessages) + .contains("Failed to create draft with key: 'null'. Reason: Draft key is blank!")); + } + + @Test + void updateTaxCategory_WithNoError_ShouldUpdateTaxCategory() { + final TaxCategory mock = mock(TaxCategory.class); + when(client.execute(any())).thenReturn(CompletableFuture.completedFuture(mock)); + final List> updateActions = + Collections.singletonList(ChangeName.of("name")); + + final TaxCategory taxCategory = + service.updateTaxCategory(mock, updateActions).toCompletableFuture().join(); + + assertThat(taxCategory).isSameAs(mock); + verify(client).execute(eq(TaxCategoryUpdateCommand.of(mock, updateActions))); + } } diff --git a/src/test/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImplTest.java index b5dafc0c4b..5157e3f728 100644 --- a/src/test/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/UnresolvedReferencesServiceImplTest.java @@ -1,5 +1,17 @@ package com.commercetools.sync.services.impl; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.lang.String.format; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.models.WaitingToBeResolved; import com.commercetools.sync.products.ProductSyncOptions; @@ -13,297 +25,288 @@ import io.sphere.sdk.products.ProductDraft; import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.lang.String.format; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; class UnresolvedReferencesServiceImplTest { - private UnresolvedReferencesServiceImpl service; - private ProductSyncOptions productSyncOptions; - private List errorMessages; - private List errorExceptions; - - @BeforeEach - void setUp() { - errorMessages = new ArrayList<>(); - errorExceptions = new ArrayList<>(); - productSyncOptions = ProductSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorMessages.add(exception.getMessage()); - errorExceptions.add(exception); - }) + private UnresolvedReferencesServiceImpl service; + private ProductSyncOptions productSyncOptions; + private List errorMessages; + private List errorExceptions; + + @BeforeEach + void setUp() { + errorMessages = new ArrayList<>(); + errorExceptions = new ArrayList<>(); + productSyncOptions = + ProductSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorMessages.add(exception.getMessage()); + errorExceptions.add(exception); + }) .build(); - service = new UnresolvedReferencesServiceImpl(productSyncOptions); - } - - @Test - void fetch_WithEmptyKeySet_ShouldReturnEmptySet() { - // preparation - final Set keys = new HashSet<>(); - - // test - final Set result = service - .fetch(keys) - .toCompletableFuture() - .join(); - - // assertions - assertThat(result).isEmpty(); - } - - @SuppressWarnings("unchecked") - @Test - void fetch_OnSuccess_ShouldReturnMock() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - final ProductDraft productDraftMock = mock(ProductDraft.class); - when(productDraftMock.getKey()).thenReturn("product-draft-key"); - - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(productDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); - - final PagedQueryResult result = getMockPagedQueryResult(singletonList(customObjectMock)); - when(productSyncOptions.getCtpClient().execute(any(CustomObjectQuery.class))) - .thenReturn(completedFuture(result)); - - // test - final Set toBeResolvedOptional = service - .fetch(singleton("product-draft-key")) - .toCompletableFuture() - .join(); - - // assertions - assertThat(toBeResolvedOptional).containsOnly(waitingToBeResolved); - } - - @SuppressWarnings("unchecked") - @Test - void fetch_OnSuccess_ShouldRequestHashedKeys() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - final ProductDraft productDraftMock = mock(ProductDraft.class); - when(productDraftMock.getKey()).thenReturn("product-draft-key"); - - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(productDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); - - final PagedQueryResult result = getMockPagedQueryResult(singletonList(customObjectMock)); - when(productSyncOptions.getCtpClient().execute(any(CustomObjectQuery.class))) - .thenReturn(completedFuture(result)); - final ArgumentCaptor> requestArgumentCaptor = - ArgumentCaptor.forClass(CustomObjectQuery.class); - - // test - final Set setOfSpecialCharKeys = asSet( + service = new UnresolvedReferencesServiceImpl(productSyncOptions); + } + + @Test + void fetch_WithEmptyKeySet_ShouldReturnEmptySet() { + // preparation + final Set keys = new HashSet<>(); + + // test + final Set result = service.fetch(keys).toCompletableFuture().join(); + + // assertions + assertThat(result).isEmpty(); + } + + @SuppressWarnings("unchecked") + @Test + void fetch_OnSuccess_ShouldReturnMock() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + final ProductDraft productDraftMock = mock(ProductDraft.class); + when(productDraftMock.getKey()).thenReturn("product-draft-key"); + + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(productDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); + + final PagedQueryResult result = getMockPagedQueryResult(singletonList(customObjectMock)); + when(productSyncOptions.getCtpClient().execute(any(CustomObjectQuery.class))) + .thenReturn(completedFuture(result)); + + // test + final Set toBeResolvedOptional = + service.fetch(singleton("product-draft-key")).toCompletableFuture().join(); + + // assertions + assertThat(toBeResolvedOptional).containsOnly(waitingToBeResolved); + } + + @SuppressWarnings("unchecked") + @Test + void fetch_OnSuccess_ShouldRequestHashedKeys() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + final ProductDraft productDraftMock = mock(ProductDraft.class); + when(productDraftMock.getKey()).thenReturn("product-draft-key"); + + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(productDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); + + final PagedQueryResult result = getMockPagedQueryResult(singletonList(customObjectMock)); + when(productSyncOptions.getCtpClient().execute(any(CustomObjectQuery.class))) + .thenReturn(completedFuture(result)); + final ArgumentCaptor> requestArgumentCaptor = + ArgumentCaptor.forClass(CustomObjectQuery.class); + + // test + final Set setOfSpecialCharKeys = + asSet( "Get a $100 Visa® Reward Card because you’re ordering TV", "product$", "Visa®", "Visa©"); - final Set toBeResolvedOptional = service - .fetch(setOfSpecialCharKeys) - .toCompletableFuture() - .join(); - - // assertions - verify(productSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); - assertThat(toBeResolvedOptional).containsOnly(waitingToBeResolved); - setOfSpecialCharKeys.forEach(key -> - assertThat(requestArgumentCaptor.getValue().httpRequestIntent().getPath()).contains(sha1Hex(key))); - } - - @Test - void save_OnSuccess_ShouldSaveMock() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - final ProductDraft productDraftMock = mock(ProductDraft.class); - when(productDraftMock.getKey()).thenReturn("product-draft-key"); - - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(productDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); - - when(productSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(customObjectMock)); - - // test - final Optional result = service - .save(waitingToBeResolved) - .toCompletableFuture() - .join(); - - // assertions - assertThat(result).contains(waitingToBeResolved); - } - - @SuppressWarnings("unchecked") - @Test - void save_OnSuccess_ShouldSaveMockWithSha1HashedKey() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - final ProductDraft productDraftMock = mock(ProductDraft.class); - when(productDraftMock.getKey()).thenReturn("product-draft-key"); - - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(productDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); - - when(productSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(customObjectMock)); - final ArgumentCaptor> requestArgumentCaptor = - ArgumentCaptor.forClass(CustomObjectUpsertCommand.class); - - // test - final Optional result = service - .save(waitingToBeResolved) - .toCompletableFuture() - .join(); - - // assertions - verify(productSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); - assertThat(result).contains(waitingToBeResolved); - assertThat(requestArgumentCaptor.getValue().getDraft().getKey()) - .isEqualTo(sha1Hex(productDraftMock.getKey())); - } - - @Test - void save_WithUnsuccessfulMockCtpResponse_ShouldNotSaveMock() { - // preparation - final String productKey = "product-draft-key"; - final ProductDraft productDraftMock = mock(ProductDraft.class); - when(productDraftMock.getKey()).thenReturn(productKey); - final WaitingToBeResolved waitingToBeResolved = - new WaitingToBeResolved(productDraftMock, singleton("test-ref")); - - when(productSyncOptions.getCtpClient().execute(any())) - .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); - - // test - final Optional result = service - .save(waitingToBeResolved) - .toCompletableFuture() - .join(); - - // assertions - assertThat(result).isEmpty(); - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> assertThat(message).contains( - format("Failed to save CustomObject with key: '%s' (hash of product key: '%s').", - sha1Hex(productKey), productKey))); - - assertThat(errorExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> { - assertThat(exception).isExactlyInstanceOf(SyncException.class); - assertThat(exception).hasCauseExactlyInstanceOf(BadRequestException.class); - }); - } - - @Test - void delete_WithUnsuccessfulMockCtpResponse_ShouldReturnProperException() { - // preparation - final ProductDraft productDraftMock = mock(ProductDraft.class); - final String key = "product-draft-key"; - when(productDraftMock.getKey()).thenReturn(key); - when(productSyncOptions.getCtpClient().execute(any())) - .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); - - // test - final Optional toBeResolvedOptional = service - .delete("product-draft-key").toCompletableFuture().join(); - - // assertions - assertThat(toBeResolvedOptional).isEmpty(); - assertThat(errorMessages).hasSize(1); - assertThat(errorExceptions).hasSize(1); - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> assertThat(message) - .contains(format("Failed to delete CustomObject with key: '%s' (hash of product key: '%s')", + final Set toBeResolvedOptional = + service.fetch(setOfSpecialCharKeys).toCompletableFuture().join(); + + // assertions + verify(productSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); + assertThat(toBeResolvedOptional).containsOnly(waitingToBeResolved); + setOfSpecialCharKeys.forEach( + key -> + assertThat(requestArgumentCaptor.getValue().httpRequestIntent().getPath()) + .contains(sha1Hex(key))); + } + + @Test + void save_OnSuccess_ShouldSaveMock() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + final ProductDraft productDraftMock = mock(ProductDraft.class); + when(productDraftMock.getKey()).thenReturn("product-draft-key"); + + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(productDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); + + when(productSyncOptions.getCtpClient().execute(any())) + .thenReturn(completedFuture(customObjectMock)); + + // test + final Optional result = + service.save(waitingToBeResolved).toCompletableFuture().join(); + + // assertions + assertThat(result).contains(waitingToBeResolved); + } + + @SuppressWarnings("unchecked") + @Test + void save_OnSuccess_ShouldSaveMockWithSha1HashedKey() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + final ProductDraft productDraftMock = mock(ProductDraft.class); + when(productDraftMock.getKey()).thenReturn("product-draft-key"); + + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(productDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); + + when(productSyncOptions.getCtpClient().execute(any())) + .thenReturn(completedFuture(customObjectMock)); + final ArgumentCaptor> requestArgumentCaptor = + ArgumentCaptor.forClass(CustomObjectUpsertCommand.class); + + // test + final Optional result = + service.save(waitingToBeResolved).toCompletableFuture().join(); + + // assertions + verify(productSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); + assertThat(result).contains(waitingToBeResolved); + assertThat(requestArgumentCaptor.getValue().getDraft().getKey()) + .isEqualTo(sha1Hex(productDraftMock.getKey())); + } + + @Test + void save_WithUnsuccessfulMockCtpResponse_ShouldNotSaveMock() { + // preparation + final String productKey = "product-draft-key"; + final ProductDraft productDraftMock = mock(ProductDraft.class); + when(productDraftMock.getKey()).thenReturn(productKey); + final WaitingToBeResolved waitingToBeResolved = + new WaitingToBeResolved(productDraftMock, singleton("test-ref")); + + when(productSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); + + // test + final Optional result = + service.save(waitingToBeResolved).toCompletableFuture().join(); + + // assertions + assertThat(result).isEmpty(); + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .contains( + format( + "Failed to save CustomObject with key: '%s' (hash of product key: '%s').", + sha1Hex(productKey), productKey))); + + assertThat(errorExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> { + assertThat(exception).isExactlyInstanceOf(SyncException.class); + assertThat(exception).hasCauseExactlyInstanceOf(BadRequestException.class); + }); + } + + @Test + void delete_WithUnsuccessfulMockCtpResponse_ShouldReturnProperException() { + // preparation + final ProductDraft productDraftMock = mock(ProductDraft.class); + final String key = "product-draft-key"; + when(productDraftMock.getKey()).thenReturn(key); + when(productSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); + + // test + final Optional toBeResolvedOptional = + service.delete("product-draft-key").toCompletableFuture().join(); + + // assertions + assertThat(toBeResolvedOptional).isEmpty(); + assertThat(errorMessages).hasSize(1); + assertThat(errorExceptions).hasSize(1); + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .contains( + format( + "Failed to delete CustomObject with key: '%s' (hash of product key: '%s')", sha1Hex(key), key))); - assertThat(errorExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> { - assertThat(exception).isExactlyInstanceOf(SyncException.class); - assertThat(exception).hasCauseExactlyInstanceOf(BadRequestException.class); - }); - } - - @SuppressWarnings("unchecked") - @Test - void delete_OnSuccess_ShouldRemoveTheResourceObject() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - - final ProductDraft productDraftMock = mock(ProductDraft.class); - when(productDraftMock.getKey()).thenReturn("product-draft-key"); - final WaitingToBeResolved waitingDraft = new WaitingToBeResolved(productDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingDraft); - - when(productSyncOptions.getCtpClient().execute(any(CustomObjectDeleteCommand.class))) - .thenReturn(completedFuture(customObjectMock)); - - // test - final Optional toBeResolvedOptional = service - .delete("product-draft-key").toCompletableFuture().join(); - - // assertions - assertThat(toBeResolvedOptional).contains(waitingDraft); - } - - @SuppressWarnings("unchecked") - @Test - void delete_OnSuccess_ShouldMakeRequestWithSha1HashedKey() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - - final ProductDraft productDraftMock = mock(ProductDraft.class); - when(productDraftMock.getKey()).thenReturn("product-draft-key"); - final WaitingToBeResolved waitingDraft = new WaitingToBeResolved(productDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingDraft); - - when(productSyncOptions.getCtpClient().execute(any(CustomObjectDeleteCommand.class))) - .thenReturn(completedFuture(customObjectMock)); - final ArgumentCaptor> requestArgumentCaptor = - ArgumentCaptor.forClass(CustomObjectDeleteCommand.class); - - // test - final Optional toBeResolvedOptional = service - .delete("product-draft-key").toCompletableFuture().join(); - - // assertions - verify(productSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); - assertThat(toBeResolvedOptional).contains(waitingDraft); - final CustomObjectDeleteCommand value = requestArgumentCaptor.getValue(); - assertThat(value.httpRequestIntent().getPath()) - .contains(sha1Hex(productDraftMock.getKey())); - } - - @Nonnull - private PagedQueryResult getMockPagedQueryResult(@Nonnull final List results) { - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - when(pagedQueryResult.getResults()).thenReturn(results); - return pagedQueryResult; - } + assertThat(errorExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> { + assertThat(exception).isExactlyInstanceOf(SyncException.class); + assertThat(exception).hasCauseExactlyInstanceOf(BadRequestException.class); + }); + } + + @SuppressWarnings("unchecked") + @Test + void delete_OnSuccess_ShouldRemoveTheResourceObject() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + + final ProductDraft productDraftMock = mock(ProductDraft.class); + when(productDraftMock.getKey()).thenReturn("product-draft-key"); + final WaitingToBeResolved waitingDraft = + new WaitingToBeResolved(productDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingDraft); + + when(productSyncOptions.getCtpClient().execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(completedFuture(customObjectMock)); + + // test + final Optional toBeResolvedOptional = + service.delete("product-draft-key").toCompletableFuture().join(); + + // assertions + assertThat(toBeResolvedOptional).contains(waitingDraft); + } + + @SuppressWarnings("unchecked") + @Test + void delete_OnSuccess_ShouldMakeRequestWithSha1HashedKey() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + + final ProductDraft productDraftMock = mock(ProductDraft.class); + when(productDraftMock.getKey()).thenReturn("product-draft-key"); + final WaitingToBeResolved waitingDraft = + new WaitingToBeResolved(productDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingDraft); + + when(productSyncOptions.getCtpClient().execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(completedFuture(customObjectMock)); + final ArgumentCaptor> requestArgumentCaptor = + ArgumentCaptor.forClass(CustomObjectDeleteCommand.class); + + // test + final Optional toBeResolvedOptional = + service.delete("product-draft-key").toCompletableFuture().join(); + + // assertions + verify(productSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); + assertThat(toBeResolvedOptional).contains(waitingDraft); + final CustomObjectDeleteCommand value = requestArgumentCaptor.getValue(); + assertThat(value.httpRequestIntent().getPath()).contains(sha1Hex(productDraftMock.getKey())); + } + + @Nonnull + private PagedQueryResult getMockPagedQueryResult(@Nonnull final List results) { + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + when(pagedQueryResult.getResults()).thenReturn(results); + return pagedQueryResult; + } } diff --git a/src/test/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImplTest.java b/src/test/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImplTest.java index a59e3f1e82..9506431ab4 100644 --- a/src/test/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImplTest.java +++ b/src/test/java/com/commercetools/sync/services/impl/UnresolvedTransitionsServiceImplTest.java @@ -1,5 +1,17 @@ package com.commercetools.sync.services.impl; +import static io.sphere.sdk.utils.SphereInternalUtils.asSet; +import static java.lang.String.format; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.models.WaitingToBeResolvedTransitions; import com.commercetools.sync.states.StateSyncOptions; import com.commercetools.sync.states.StateSyncOptionsBuilder; @@ -12,295 +24,284 @@ import io.sphere.sdk.queries.PagedQueryResult; import io.sphere.sdk.states.StateDraft; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - -import static io.sphere.sdk.utils.SphereInternalUtils.asSet; -import static java.lang.String.format; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; class UnresolvedTransitionsServiceImplTest { - private UnresolvedTransitionsServiceImpl service; - private StateSyncOptions stateSyncOptions; - private List errorMessages; - private List errorExceptions; - - @BeforeEach - void setUp() { - errorMessages = new ArrayList<>(); - errorExceptions = new ArrayList<>(); - stateSyncOptions = StateSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - errorExceptions.add(exception.getCause()); - }) + private UnresolvedTransitionsServiceImpl service; + private StateSyncOptions stateSyncOptions; + private List errorMessages; + private List errorExceptions; + + @BeforeEach + void setUp() { + errorMessages = new ArrayList<>(); + errorExceptions = new ArrayList<>(); + stateSyncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + errorExceptions.add(exception.getCause()); + }) .build(); - service = new UnresolvedTransitionsServiceImpl(stateSyncOptions); - } - - @Test - void fetch_WithEmptyKeySet_ShouldReturnEmptySet() { - // preparation - final Set keys = new HashSet<>(); - - // test - final Set result = service - .fetch(keys) - .toCompletableFuture() - .join(); - - // assertions - assertThat(result).isEmpty(); - } - - @SuppressWarnings("unchecked") - @Test - void fetch_OnSuccess_ShouldReturnMock() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - final StateDraft stateDraftMock = mock(StateDraft.class); - when(stateDraftMock.getKey()).thenReturn("state-draft-key"); - - final WaitingToBeResolvedTransitions waitingToBeResolved = - new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); - - final PagedQueryResult result = getMockPagedQueryResult(singletonList(customObjectMock)); - when(stateSyncOptions.getCtpClient().execute(any(CustomObjectQuery.class))) - .thenReturn(completedFuture(result)); - - // test - final Set toBeResolvedOptional = service - .fetch(singleton("state-draft-key")) - .toCompletableFuture() - .join(); - - // assertions - assertThat(toBeResolvedOptional).containsOnly(waitingToBeResolved); - } - - @SuppressWarnings("unchecked") - @Test - void fetch_OnSuccess_ShouldRequestHashedKeys() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - final StateDraft stateDraftMock = mock(StateDraft.class); - when(stateDraftMock.getKey()).thenReturn("state-draft-key"); - - final WaitingToBeResolvedTransitions waitingToBeResolved = - new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); - - final PagedQueryResult result = getMockPagedQueryResult(singletonList(customObjectMock)); - when(stateSyncOptions.getCtpClient().execute(any(CustomObjectQuery.class))) - .thenReturn(completedFuture(result)); - final ArgumentCaptor> requestArgumentCaptor = - ArgumentCaptor.forClass(CustomObjectQuery.class); - - // test - final Set setOfSpecialCharKeys = asSet( + service = new UnresolvedTransitionsServiceImpl(stateSyncOptions); + } + + @Test + void fetch_WithEmptyKeySet_ShouldReturnEmptySet() { + // preparation + final Set keys = new HashSet<>(); + + // test + final Set result = + service.fetch(keys).toCompletableFuture().join(); + + // assertions + assertThat(result).isEmpty(); + } + + @SuppressWarnings("unchecked") + @Test + void fetch_OnSuccess_ShouldReturnMock() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + final StateDraft stateDraftMock = mock(StateDraft.class); + when(stateDraftMock.getKey()).thenReturn("state-draft-key"); + + final WaitingToBeResolvedTransitions waitingToBeResolved = + new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); + + final PagedQueryResult result = getMockPagedQueryResult(singletonList(customObjectMock)); + when(stateSyncOptions.getCtpClient().execute(any(CustomObjectQuery.class))) + .thenReturn(completedFuture(result)); + + // test + final Set toBeResolvedOptional = + service.fetch(singleton("state-draft-key")).toCompletableFuture().join(); + + // assertions + assertThat(toBeResolvedOptional).containsOnly(waitingToBeResolved); + } + + @SuppressWarnings("unchecked") + @Test + void fetch_OnSuccess_ShouldRequestHashedKeys() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + final StateDraft stateDraftMock = mock(StateDraft.class); + when(stateDraftMock.getKey()).thenReturn("state-draft-key"); + + final WaitingToBeResolvedTransitions waitingToBeResolved = + new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); + + final PagedQueryResult result = getMockPagedQueryResult(singletonList(customObjectMock)); + when(stateSyncOptions.getCtpClient().execute(any(CustomObjectQuery.class))) + .thenReturn(completedFuture(result)); + final ArgumentCaptor> requestArgumentCaptor = + ArgumentCaptor.forClass(CustomObjectQuery.class); + + // test + final Set setOfSpecialCharKeys = + asSet( "Get a $100 Visa® Reward Card because you’re ordering TV", "product$", "Visa®", "Visa©"); - final Set toBeResolvedOptional = service - .fetch(setOfSpecialCharKeys) - .toCompletableFuture() - .join(); - - // assertions - verify(stateSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); - assertThat(toBeResolvedOptional).containsOnly(waitingToBeResolved); - setOfSpecialCharKeys.forEach(key -> - assertThat(requestArgumentCaptor.getValue().httpRequestIntent().getPath()).contains(sha1Hex(key))); - } - - @Test - void save_OnSuccess_ShouldSaveMock() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - final StateDraft stateDraftMock = mock(StateDraft.class); - when(stateDraftMock.getKey()).thenReturn("state-draft-key"); - - final WaitingToBeResolvedTransitions waitingToBeResolved = - new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); - - when(stateSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(customObjectMock)); - - // test - final Optional result = service - .save(waitingToBeResolved) - .toCompletableFuture() - .join(); - - // assertions - assertThat(result).contains(waitingToBeResolved); - } - - @SuppressWarnings("unchecked") - @Test - void save_OnSuccess_ShouldSaveMockWithSha1HashedKey() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - final StateDraft stateDraftMock = mock(StateDraft.class); - when(stateDraftMock.getKey()).thenReturn("state-draft-key"); - - final WaitingToBeResolvedTransitions waitingToBeResolved = - new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); - - when(stateSyncOptions.getCtpClient().execute(any())).thenReturn(completedFuture(customObjectMock)); - final ArgumentCaptor> requestArgumentCaptor = - ArgumentCaptor.forClass(CustomObjectUpsertCommand.class); - - // test - final Optional result = service - .save(waitingToBeResolved) - .toCompletableFuture() - .join(); - - // assertions - verify(stateSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); - assertThat(result).contains(waitingToBeResolved); - assertThat(requestArgumentCaptor.getValue().getDraft().getKey()) - .isEqualTo(sha1Hex(stateDraftMock.getKey())); - } - - @Test - void save_WithUnsuccessfulMockCtpResponse_ShouldNotSaveMock() { - // preparation - final String stateKey = "state-draft-key"; - final StateDraft stateDraftMock = mock(StateDraft.class); - when(stateDraftMock.getKey()).thenReturn(stateKey); - final WaitingToBeResolvedTransitions waitingToBeResolved = - new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); - - when(stateSyncOptions.getCtpClient().execute(any())) - .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); - - // test - final Optional result = service - .save(waitingToBeResolved) - .toCompletableFuture() - .join(); - - // assertions - assertThat(result).isEmpty(); - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> assertThat(message).contains( - format("Failed to save CustomObject with key: '%s' (hash of state key: '%s').", - sha1Hex(stateKey), stateKey))); - - assertThat(errorExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> - assertThat(exception).isExactlyInstanceOf(BadRequestException.class)); - } - - @Test - void delete_WithUnsuccessfulMockCtpResponse_ShouldReturnProperException() { - // preparation - final StateDraft stateDraftMock = mock(StateDraft.class); - final String key = "state-draft-key"; - when(stateDraftMock.getKey()).thenReturn(key); - when(stateSyncOptions.getCtpClient().execute(any())) - .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); - - // test - final Optional toBeResolvedOptional = service - .delete("state-draft-key").toCompletableFuture().join(); - - // assertions - assertThat(toBeResolvedOptional).isEmpty(); - assertThat(errorMessages).hasSize(1); - assertThat(errorExceptions).hasSize(1); - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> assertThat(message) - .contains(format("Failed to delete CustomObject with key: '%s' (hash of state key: '%s')", + final Set toBeResolvedOptional = + service.fetch(setOfSpecialCharKeys).toCompletableFuture().join(); + + // assertions + verify(stateSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); + assertThat(toBeResolvedOptional).containsOnly(waitingToBeResolved); + setOfSpecialCharKeys.forEach( + key -> + assertThat(requestArgumentCaptor.getValue().httpRequestIntent().getPath()) + .contains(sha1Hex(key))); + } + + @Test + void save_OnSuccess_ShouldSaveMock() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + final StateDraft stateDraftMock = mock(StateDraft.class); + when(stateDraftMock.getKey()).thenReturn("state-draft-key"); + + final WaitingToBeResolvedTransitions waitingToBeResolved = + new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); + + when(stateSyncOptions.getCtpClient().execute(any())) + .thenReturn(completedFuture(customObjectMock)); + + // test + final Optional result = + service.save(waitingToBeResolved).toCompletableFuture().join(); + + // assertions + assertThat(result).contains(waitingToBeResolved); + } + + @SuppressWarnings("unchecked") + @Test + void save_OnSuccess_ShouldSaveMockWithSha1HashedKey() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + final StateDraft stateDraftMock = mock(StateDraft.class); + when(stateDraftMock.getKey()).thenReturn("state-draft-key"); + + final WaitingToBeResolvedTransitions waitingToBeResolved = + new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingToBeResolved); + + when(stateSyncOptions.getCtpClient().execute(any())) + .thenReturn(completedFuture(customObjectMock)); + final ArgumentCaptor> + requestArgumentCaptor = ArgumentCaptor.forClass(CustomObjectUpsertCommand.class); + + // test + final Optional result = + service.save(waitingToBeResolved).toCompletableFuture().join(); + + // assertions + verify(stateSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); + assertThat(result).contains(waitingToBeResolved); + assertThat(requestArgumentCaptor.getValue().getDraft().getKey()) + .isEqualTo(sha1Hex(stateDraftMock.getKey())); + } + + @Test + void save_WithUnsuccessfulMockCtpResponse_ShouldNotSaveMock() { + // preparation + final String stateKey = "state-draft-key"; + final StateDraft stateDraftMock = mock(StateDraft.class); + when(stateDraftMock.getKey()).thenReturn(stateKey); + final WaitingToBeResolvedTransitions waitingToBeResolved = + new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); + + when(stateSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); + + // test + final Optional result = + service.save(waitingToBeResolved).toCompletableFuture().join(); + + // assertions + assertThat(result).isEmpty(); + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .contains( + format( + "Failed to save CustomObject with key: '%s' (hash of state key: '%s').", + sha1Hex(stateKey), stateKey))); + + assertThat(errorExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> assertThat(exception).isExactlyInstanceOf(BadRequestException.class)); + } + + @Test + void delete_WithUnsuccessfulMockCtpResponse_ShouldReturnProperException() { + // preparation + final StateDraft stateDraftMock = mock(StateDraft.class); + final String key = "state-draft-key"; + when(stateDraftMock.getKey()).thenReturn(key); + when(stateSyncOptions.getCtpClient().execute(any())) + .thenReturn(CompletableFutureUtils.failed(new BadRequestException("bad request"))); + + // test + final Optional toBeResolvedOptional = + service.delete("state-draft-key").toCompletableFuture().join(); + + // assertions + assertThat(toBeResolvedOptional).isEmpty(); + assertThat(errorMessages).hasSize(1); + assertThat(errorExceptions).hasSize(1); + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .contains( + format( + "Failed to delete CustomObject with key: '%s' (hash of state key: '%s')", sha1Hex(key), key))); - assertThat(errorExceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(exception -> - assertThat(exception).isExactlyInstanceOf(BadRequestException.class)); - } - - @SuppressWarnings("unchecked") - @Test - void delete_OnSuccess_ShouldRemoveTheResourceObject() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - - final StateDraft stateDraftMock = mock(StateDraft.class); - when(stateDraftMock.getKey()).thenReturn("state-draft-key"); - final WaitingToBeResolvedTransitions waitingDraft = - new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingDraft); - - when(stateSyncOptions.getCtpClient().execute(any(CustomObjectDeleteCommand.class))) - .thenReturn(completedFuture(customObjectMock)); - - // test - final Optional toBeResolvedOptional = service - .delete("state-draft-key").toCompletableFuture().join(); - - // assertions - assertThat(toBeResolvedOptional).contains(waitingDraft); - } - - @SuppressWarnings("unchecked") - @Test - void delete_OnSuccess_ShouldMakeRequestWithSha1HashedKey() { - // preparation - final CustomObject customObjectMock = mock(CustomObject.class); - - final StateDraft stateDraftMock = mock(StateDraft.class); - when(stateDraftMock.getKey()).thenReturn("state-draft-key"); - final WaitingToBeResolvedTransitions waitingDraft = - new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); - when(customObjectMock.getValue()).thenReturn(waitingDraft); - - when(stateSyncOptions.getCtpClient().execute(any(CustomObjectDeleteCommand.class))) - .thenReturn(completedFuture(customObjectMock)); - final ArgumentCaptor> requestArgumentCaptor = - ArgumentCaptor.forClass(CustomObjectDeleteCommand.class); - - // test - final Optional toBeResolvedOptional = service - .delete("state-draft-key").toCompletableFuture().join(); - - // assertions - verify(stateSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); - assertThat(toBeResolvedOptional).contains(waitingDraft); - final CustomObjectDeleteCommand value = requestArgumentCaptor.getValue(); - assertThat(value.httpRequestIntent().getPath()) - .contains(sha1Hex(stateDraftMock.getKey())); - } - - @Nonnull - private PagedQueryResult getMockPagedQueryResult(@Nonnull final List results) { - final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); - when(pagedQueryResult.getResults()).thenReturn(results); - return pagedQueryResult; - } + assertThat(errorExceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + exception -> assertThat(exception).isExactlyInstanceOf(BadRequestException.class)); + } + + @SuppressWarnings("unchecked") + @Test + void delete_OnSuccess_ShouldRemoveTheResourceObject() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + + final StateDraft stateDraftMock = mock(StateDraft.class); + when(stateDraftMock.getKey()).thenReturn("state-draft-key"); + final WaitingToBeResolvedTransitions waitingDraft = + new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingDraft); + + when(stateSyncOptions.getCtpClient().execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(completedFuture(customObjectMock)); + + // test + final Optional toBeResolvedOptional = + service.delete("state-draft-key").toCompletableFuture().join(); + + // assertions + assertThat(toBeResolvedOptional).contains(waitingDraft); + } + + @SuppressWarnings("unchecked") + @Test + void delete_OnSuccess_ShouldMakeRequestWithSha1HashedKey() { + // preparation + final CustomObject customObjectMock = mock(CustomObject.class); + + final StateDraft stateDraftMock = mock(StateDraft.class); + when(stateDraftMock.getKey()).thenReturn("state-draft-key"); + final WaitingToBeResolvedTransitions waitingDraft = + new WaitingToBeResolvedTransitions(stateDraftMock, singleton("test-ref")); + when(customObjectMock.getValue()).thenReturn(waitingDraft); + + when(stateSyncOptions.getCtpClient().execute(any(CustomObjectDeleteCommand.class))) + .thenReturn(completedFuture(customObjectMock)); + final ArgumentCaptor> + requestArgumentCaptor = ArgumentCaptor.forClass(CustomObjectDeleteCommand.class); + + // test + final Optional toBeResolvedOptional = + service.delete("state-draft-key").toCompletableFuture().join(); + + // assertions + verify(stateSyncOptions.getCtpClient()).execute(requestArgumentCaptor.capture()); + assertThat(toBeResolvedOptional).contains(waitingDraft); + final CustomObjectDeleteCommand value = + requestArgumentCaptor.getValue(); + assertThat(value.httpRequestIntent().getPath()).contains(sha1Hex(stateDraftMock.getKey())); + } + + @Nonnull + private PagedQueryResult getMockPagedQueryResult(@Nonnull final List results) { + final PagedQueryResult pagedQueryResult = mock(PagedQueryResult.class); + when(pagedQueryResult.getResults()).thenReturn(results); + return pagedQueryResult; + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptionsBuilderTest.java index 3807d2f51f..ff7bc22865 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/ShoppingListSyncOptionsBuilderTest.java @@ -1,5 +1,16 @@ package com.commercetools.sync.shoppinglists; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -10,304 +21,290 @@ import io.sphere.sdk.shoppinglists.ShoppingListDraft; import io.sphere.sdk.shoppinglists.ShoppingListDraftBuilder; import io.sphere.sdk.shoppinglists.commands.updateactions.ChangeName; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ShoppingListSyncOptionsBuilderTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private ShoppingListSyncOptionsBuilder shoppingListSyncOptionsBuilder = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT); - - @Test - void of_WithClient_ShouldCreateShoppingListSyncOptionsBuilder() { - assertThat(shoppingListSyncOptionsBuilder).isNotNull(); - } - - @Test - void build_WithClient_ShouldBuildSyncOptions() { - final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); - assertThat(shoppingListSyncOptions).isNotNull(); - assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNull(); - assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNull(); - assertThat(shoppingListSyncOptions.getErrorCallback()).isNull(); - assertThat(shoppingListSyncOptions.getWarningCallback()).isNull(); - assertThat(shoppingListSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); - assertThat(shoppingListSyncOptions.getBatchSize()).isEqualTo(ShoppingListSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } - - @Test - void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { - final TriFunction>, ShoppingListDraft, ShoppingList, - List>> beforeUpdateCallback = - (updateActions, newShoppingList, oldShoppingList) -> emptyList(); - - shoppingListSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - - final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); - assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNotNull(); - } - - @Test - void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { - shoppingListSyncOptionsBuilder.beforeCreateCallback((newShoppingList) -> null); - - final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); - assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNotNull(); - } - - @Test - void errorCallBack_WithCallBack_ShouldSetCallBack() { - final QuadConsumer, - Optional, List>> mockErrorCallBack - = (syncException, draft, shoppingList, updateActions) -> { - }; - shoppingListSyncOptionsBuilder.errorCallback(mockErrorCallBack); - - final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); - assertThat(shoppingListSyncOptions.getErrorCallback()).isNotNull(); - } - - @Test - void warningCallBack_WithCallBack_ShouldSetCallBack() { - final TriConsumer, Optional> mockWarningCallBack - = (syncException, draft, shoppingList) -> { }; - shoppingListSyncOptionsBuilder.warningCallback(mockWarningCallBack); - - final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); - assertThat(shoppingListSyncOptions.getWarningCallback()).isNotNull(); - } - - @Test - void getThis_ShouldReturnCorrectInstance() { - final ShoppingListSyncOptionsBuilder instance = shoppingListSyncOptionsBuilder.getThis(); - assertThat(instance).isNotNull(); - assertThat(instance).isInstanceOf(ShoppingListSyncOptionsBuilder.class); - assertThat(instance).isEqualTo(shoppingListSyncOptionsBuilder); - } - - @Test - void shoppingListSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(30) - .beforeCreateCallback((newShoppingList) -> null) - .beforeUpdateCallback( - (updateActions, newShoppingList, oldShoppingList) -> emptyList()) - .build(); - - assertThat(shoppingListSyncOptions).isNotNull(); - } - - @Test - void batchSize_WithPositiveValue_ShouldSetBatchSize() { - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(10) - .build(); - - assertThat(shoppingListSyncOptions.getBatchSize()).isEqualTo(10); - } - - @Test - void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final ShoppingListSyncOptions shoppingListSyncOptionsWithZeroBatchSize = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(0) - .build(); - - assertThat(shoppingListSyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(ShoppingListSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - - final ShoppingListSyncOptions shoppingListSyncOptionsWithNegativeBatchSize = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(-100) - .build(); - - assertThat(shoppingListSyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(ShoppingListSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } - - @Test - void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).build(); - - assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - - final List> filteredList = - shoppingListSyncOptions.applyBeforeUpdateCallback( - updateActions, mock(ShoppingListDraft.class), mock(ShoppingList.class)); - - assertThat(filteredList).isSameAs(updateActions); - } - - @Test - void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { - final TriFunction>, ShoppingListDraft, - ShoppingList, List>> beforeUpdateCallback = - (updateActions, newShoppingList, oldShoppingList) -> null; - - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder - .of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = - shoppingListSyncOptions - .applyBeforeUpdateCallback(updateActions, mock(ShoppingListDraft.class), mock(ShoppingList.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - private interface MockTriFunction extends - TriFunction>, ShoppingListDraft, - ShoppingList, List>> { - } - - @Test - void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); - - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = emptyList(); - final List> filteredList = - shoppingListSyncOptions - .applyBeforeUpdateCallback(updateActions, mock(ShoppingListDraft.class), mock(ShoppingList.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - @Test - void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { - final TriFunction>, ShoppingListDraft, - ShoppingList, List>> beforeUpdateCallback = - (updateActions, newShoppingList, oldShoppingList) -> emptyList(); - - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder - .of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = - shoppingListSyncOptions - .applyBeforeUpdateCallback(updateActions, mock(ShoppingListDraft.class), mock(ShoppingList.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { - final Function draftFunction = - shoppingListDraft -> - ShoppingListDraftBuilder.of(shoppingListDraft) - .key(format("%s_filteredKey", shoppingListDraft.getKey())) - .build(); - - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private ShoppingListSyncOptionsBuilder shoppingListSyncOptionsBuilder = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateShoppingListSyncOptionsBuilder() { + assertThat(shoppingListSyncOptionsBuilder).isNotNull(); + } + + @Test + void build_WithClient_ShouldBuildSyncOptions() { + final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); + assertThat(shoppingListSyncOptions).isNotNull(); + assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNull(); + assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNull(); + assertThat(shoppingListSyncOptions.getErrorCallback()).isNull(); + assertThat(shoppingListSyncOptions.getWarningCallback()).isNull(); + assertThat(shoppingListSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); + assertThat(shoppingListSyncOptions.getBatchSize()) + .isEqualTo(ShoppingListSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + + @Test + void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { + final TriFunction< + List>, + ShoppingListDraft, + ShoppingList, + List>> + beforeUpdateCallback = (updateActions, newShoppingList, oldShoppingList) -> emptyList(); + + shoppingListSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); + + final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); + assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNotNull(); + } + + @Test + void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { + shoppingListSyncOptionsBuilder.beforeCreateCallback((newShoppingList) -> null); + + final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); + assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNotNull(); + } + + @Test + void errorCallBack_WithCallBack_ShouldSetCallBack() { + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + mockErrorCallBack = (syncException, draft, shoppingList, updateActions) -> {}; + shoppingListSyncOptionsBuilder.errorCallback(mockErrorCallBack); + + final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); + assertThat(shoppingListSyncOptions.getErrorCallback()).isNotNull(); + } + + @Test + void warningCallBack_WithCallBack_ShouldSetCallBack() { + final TriConsumer, Optional> + mockWarningCallBack = (syncException, draft, shoppingList) -> {}; + shoppingListSyncOptionsBuilder.warningCallback(mockWarningCallBack); + + final ShoppingListSyncOptions shoppingListSyncOptions = shoppingListSyncOptionsBuilder.build(); + assertThat(shoppingListSyncOptions.getWarningCallback()).isNotNull(); + } + + @Test + void getThis_ShouldReturnCorrectInstance() { + final ShoppingListSyncOptionsBuilder instance = shoppingListSyncOptionsBuilder.getThis(); + assertThat(instance).isNotNull(); + assertThat(instance).isInstanceOf(ShoppingListSyncOptionsBuilder.class); + assertThat(instance).isEqualTo(shoppingListSyncOptionsBuilder); + } + + @Test + void shoppingListSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) + .batchSize(30) + .beforeCreateCallback((newShoppingList) -> null) + .beforeUpdateCallback((updateActions, newShoppingList, oldShoppingList) -> emptyList()) + .build(); + + assertThat(shoppingListSyncOptions).isNotNull(); + } + + @Test + void batchSize_WithPositiveValue_ShouldSetBatchSize() { + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); + + assertThat(shoppingListSyncOptions.getBatchSize()).isEqualTo(10); + } + + @Test + void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final ShoppingListSyncOptions shoppingListSyncOptionsWithZeroBatchSize = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); + + assertThat(shoppingListSyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(ShoppingListSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + + final ShoppingListSyncOptions shoppingListSyncOptionsWithNegativeBatchSize = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); + + assertThat(shoppingListSyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(ShoppingListSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + + @Test + void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).build(); + + assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNull(); + + final List> updateActions = + singletonList(ChangeName.of(ofEnglish("name"))); + + final List> filteredList = + shoppingListSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ShoppingListDraft.class), mock(ShoppingList.class)); + + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { + final TriFunction< + List>, + ShoppingListDraft, + ShoppingList, + List>> + beforeUpdateCallback = (updateActions, newShoppingList, oldShoppingList) -> null; + + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + + assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + shoppingListSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ShoppingListDraft.class), mock(ShoppingList.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + private interface MockTriFunction + extends TriFunction< + List>, + ShoppingListDraft, + ShoppingList, + List>> {} + + @Test + void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); + + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + + assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = emptyList(); + final List> filteredList = + shoppingListSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ShoppingListDraft.class), mock(ShoppingList.class)); + + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { + final TriFunction< + List>, + ShoppingListDraft, + ShoppingList, + List>> + beforeUpdateCallback = (updateActions, newShoppingList, oldShoppingList) -> emptyList(); + + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) + .beforeUpdateCallback(beforeUpdateCallback) + .build(); + + assertThat(shoppingListSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = + singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + shoppingListSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(ShoppingListDraft.class), mock(ShoppingList.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { + final Function draftFunction = + shoppingListDraft -> + ShoppingListDraftBuilder.of(shoppingListDraft) + .key(format("%s_filteredKey", shoppingListDraft.getKey())) + .build(); - assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNotNull(); + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); - final ShoppingListDraft resourceDraft = mock(ShoppingListDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); + assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNotNull(); + final ShoppingListDraft resourceDraft = mock(ShoppingListDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); - final Optional filteredDraft = shoppingListSyncOptions - .applyBeforeCreateCallback(resourceDraft); + final Optional filteredDraft = + shoppingListSyncOptions.applyBeforeCreateCallback(resourceDraft); - assertThat(filteredDraft).hasValueSatisfying(shoppingListDraft -> - assertThat(shoppingListDraft.getKey()).isEqualTo("myKey_filteredKey")); - } + assertThat(filteredDraft) + .hasValueSatisfying( + shoppingListDraft -> + assertThat(shoppingListDraft.getKey()).isEqualTo("myKey_filteredKey")); + } - @Test - void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final ShoppingListSyncOptions shoppingListSyncOptions = ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).build(); - assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNull(); + @Test + void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNull(); - final ShoppingListDraft resourceDraft = mock(ShoppingListDraft.class); - final Optional filteredDraft = shoppingListSyncOptions.applyBeforeCreateCallback( - resourceDraft); + final ShoppingListDraft resourceDraft = mock(ShoppingListDraft.class); + final Optional filteredDraft = + shoppingListSyncOptions.applyBeforeCreateCallback(resourceDraft); - assertThat(filteredDraft).containsSame(resourceDraft); - } + assertThat(filteredDraft).containsSame(resourceDraft); + } - @Test - void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { - final Function draftFunction = shoppingListDraft -> null; - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); + @Test + void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { + final Function draftFunction = shoppingListDraft -> null; + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); - assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNotNull(); + assertThat(shoppingListSyncOptions.getBeforeCreateCallback()).isNotNull(); - final ShoppingListDraft resourceDraft = mock(ShoppingListDraft.class); - final Optional filteredDraft = - shoppingListSyncOptions.applyBeforeCreateCallback(resourceDraft); + final ShoppingListDraft resourceDraft = mock(ShoppingListDraft.class); + final Optional filteredDraft = + shoppingListSyncOptions.applyBeforeCreateCallback(resourceDraft); - assertThat(filteredDraft).isEmpty(); - } + assertThat(filteredDraft).isEmpty(); + } - @Test - void cacheSize_WithPositiveValue_ShouldSetCacheSize() { - final ShoppingListSyncOptions shoppingListSyncOptions = - ShoppingListSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(10) - .build(); + @Test + void cacheSize_WithPositiveValue_ShouldSetCacheSize() { + final ShoppingListSyncOptions shoppingListSyncOptions = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); - assertThat(shoppingListSyncOptions.getCacheSize()).isEqualTo(10); - } + assertThat(shoppingListSyncOptions.getCacheSize()).isEqualTo(10); + } - @Test - void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final ShoppingListSyncOptions shoppingListSyncOptionsWithZeroCacheSize = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(0) - .build(); + @Test + void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final ShoppingListSyncOptions shoppingListSyncOptionsWithZeroCacheSize = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); - assertThat(shoppingListSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); + assertThat(shoppingListSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - final ShoppingListSyncOptions shoppingListSyncOptionsWithNegativeCacheSize = - ShoppingListSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(-100) - .build(); + final ShoppingListSyncOptions shoppingListSyncOptionsWithNegativeCacheSize = + ShoppingListSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); - assertThat(shoppingListSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } + assertThat(shoppingListSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/ShoppingListSyncTest.java b/src/test/java/com/commercetools/sync/shoppinglists/ShoppingListSyncTest.java index 21f647b325..a7fb9ac72b 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/ShoppingListSyncTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/ShoppingListSyncTest.java @@ -1,5 +1,30 @@ package com.commercetools.sync.shoppinglists; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_IS_NULL; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_KEY_NOT_SET; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_NAME_NOT_SET; +import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static org.assertj.core.api.Assertions.as; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.STRING; +import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics; import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.services.CustomerService; @@ -24,700 +49,690 @@ import io.sphere.sdk.shoppinglists.TextLineItemDraftBuilder; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletionException; - -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_IS_NULL; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_KEY_NOT_SET; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_NAME_NOT_SET; -import static io.sphere.sdk.utils.CompletableFutureUtils.exceptionallyCompletedFuture; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.CompletableFuture.supplyAsync; -import static org.assertj.core.api.Assertions.as; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.STRING; -import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class ShoppingListSyncTest { - private ShoppingListSyncOptions syncOptions; - private List errorMessages; - private List exceptions; - - @BeforeEach - void setup() { - errorMessages = new ArrayList<>(); - exceptions = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); - - syncOptions = ShoppingListSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception); - }) + private ShoppingListSyncOptions syncOptions; + private List errorMessages; + private List exceptions; + + @BeforeEach + void setup() { + errorMessages = new ArrayList<>(); + exceptions = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); + + syncOptions = + ShoppingListSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception); + }) + .build(); + } + + @Test + void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions); + + // test + ShoppingListSyncStatistics statistics = + shoppingListSync.sync(singletonList(null)).toCompletableFuture().join(); + + AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo(SHOPPING_LIST_DRAFT_IS_NULL); + } + + @Test + void sync_WithNullKeyDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shopping-list-name")).build(); + ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions); + + // test + ShoppingListSyncStatistics statistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); + } + + @Test + void sync_WithEmptyKeyDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shopping-list-name")) + .key("") + .build(); + ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions); + + // test + ShoppingListSyncStatistics statistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); + } + + @Test + void sync_WithoutName_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.of()).key("shopping-list-key").build(); + ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions); + + // test + ShoppingListSyncStatistics statistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo(format(SHOPPING_LIST_DRAFT_NAME_NOT_SET, shoppingListDraft.getKey())); + } + + @Test + void sync_WithExceptionOnCachingKeysToIds_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final TypeService typeService = mock(TypeService.class); + final CustomerService customerService = mock(CustomerService.class); + + when(typeService.cacheKeysToIds(any())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + when(customerService.cacheKeysToIds(any())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + syncOptions, mock(ShoppingListService.class), customerService, typeService); + + ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shopping-list-name")) + .key("shopping-list-key") + .customer(ResourceIdentifier.ofKey("customer-key")) + .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) + .build(); + + // test + ShoppingListSyncStatistics statistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo("Failed to build a cache of keys to ids."); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasCauseExactlyInstanceOf(CompletionException.class) + .hasRootCauseExactlyInstanceOf(SphereException.class); + } + + @Test + void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + final ShoppingListService shoppingListService = mock(ShoppingListService.class); + + when(shoppingListService.fetchMatchingShoppingListsByKeys(singleton("shopping-list-key"))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + syncOptions, shoppingListService, mock(CustomerService.class), mock(TypeService.class)); + + ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shopping-list-name")) + .key("shopping-list-key") + .customer(ResourceIdentifier.ofKey("customer-key")) + .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) + .build(); + // test + final ShoppingListSyncStatistics statistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .isEqualTo("Failed to fetch existing shopping lists with keys: '[shopping-list-key]'."); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasCauseExactlyInstanceOf(CompletionException.class) + .hasRootCauseExactlyInstanceOf(SphereException.class); + } + + @Test + void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallbackAndIncrementCreated() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(singleton("shoppingListKey"))) + .thenReturn(completedFuture(new HashSet<>(singletonList(mockShoppingList)))); + + when(mockShoppingListService.createShoppingList(any())) + .thenReturn(completedFuture(Optional.of(mockShoppingList))); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("NAME")) + .key("shoppingListKey") + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 1, 0, 0); + + verify(spySyncOptions).applyBeforeCreateCallback(shoppingListDraft); + verify(spySyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void sync_FailedOnCreation_ShouldCallBeforeCreateCallbackAndIncrementFailed() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(singleton("shoppingListKey"))) + .thenReturn(completedFuture(new HashSet<>(singletonList(mockShoppingList)))); + + // simulate an error during create, service will return an empty optional. + when(mockShoppingListService.createShoppingList(any())) + .thenReturn(completedFuture(Optional.empty())); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("NAME")) + .key("shoppingListKey") + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); + + verify(spySyncOptions).applyBeforeCreateCallback(shoppingListDraft); + verify(spySyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); + + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockShoppingList))); + + when(mockShoppingListService.updateShoppingList(any(), anyList())) + .thenReturn(completedFuture(mockShoppingList)); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("NAME")) + .key("shoppingListKey") + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); + + verify(spySyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spySyncOptions, never()).applyBeforeCreateCallback(shoppingListDraft); + } + + @Test + void sync_WithUnchangedShoppingListDraftAndUpdatedLineItemDraft_ShouldIncrementUpdated() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + + final LineItem mockLineItem = mock(LineItem.class); + when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); + when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("shoppingListName")); + + final ProductVariant mockProductVariant = mock(ProductVariant.class); + when(mockProductVariant.getSku()).thenReturn("dummy-sku"); + when(mockLineItem.getVariant()).thenReturn(mockProductVariant); + when(mockLineItem.getQuantity()).thenReturn(10L); + + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockShoppingList))); + + when(mockShoppingListService.updateShoppingList(any(), anyList())) + .thenReturn(completedFuture(mockShoppingList)); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final List lineItemDrafts = + singletonList(LineItemDraftBuilder.ofSku("dummy-sku", 5L).build()); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) + .key("shoppingListKey") + .lineItems(lineItemDrafts) + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); + + verify(spySyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spySyncOptions, never()).applyBeforeCreateCallback(shoppingListDraft); + } + + @Test + void sync_WithUnchangedShoppingListDraftAndUpdatedTextLineItemDraft_ShouldIncrementUpdated() { + // preparation + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); + when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("shoppingListName")); + + final TextLineItem mockTextLineItem = mock(TextLineItem.class); + when(mockTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("textLineItemName")); + when(mockTextLineItem.getQuantity()).thenReturn(10L); + + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockShoppingList))); + when(mockShoppingListService.updateShoppingList(any(), anyList())) + .thenReturn(completedFuture(mockShoppingList)); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final List textLineItemDrafts = + singletonList( + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("textLineItemName"), 5L).build()); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) + .key("shoppingListKey") + .textLineItems(textLineItemDrafts) + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); + + verify(spySyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spySyncOptions, never()).applyBeforeCreateCallback(shoppingListDraft); + } + + @Test + void sync_WithoutUpdateActions_ShouldNotIncrementUpdated() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); + when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("shoppingListName")); + when(mockShoppingList.getDescription()) + .thenReturn(LocalizedString.ofEnglish("shoppingListDesc")); + when(mockShoppingList.getSlug()).thenReturn(LocalizedString.ofEnglish("shoppingListSlug")); + when(mockShoppingList.getAnonymousId()).thenReturn("shoppingListAnonymousId"); + when(mockShoppingList.getDeleteDaysAfterLastModification()).thenReturn(360); + + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockShoppingList))); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) + .key("shoppingListKey") + .description(mockShoppingList.getDescription()) + .slug(mockShoppingList.getSlug()) + .anonymousId(mockShoppingList.getAnonymousId()) + .deleteDaysAfterLastModification(mockShoppingList.getDeleteDaysAfterLastModification()) + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 0); + + verify(spySyncOptions) + .applyBeforeUpdateCallback(emptyList(), shoppingListDraft, mockShoppingList); + verify(spySyncOptions, never()).applyBeforeCreateCallback(shoppingListDraft); + } + + @Test + void sync_WithBadRequestException_ShouldFailToUpdateAndIncreaseFailedCounter() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); + + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockShoppingList))); + + when(mockShoppingListService.updateShoppingList(any(), anyList())) + .thenReturn(exceptionallyCompletedFuture(new BadRequestException("Invalid request"))); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) + .key("shoppingListKey") + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages).hasSize(1).singleElement(as(STRING)).contains("Invalid request"); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasRootCauseExactlyInstanceOf(BadRequestException.class); + } + + @Test + void sync_WithConcurrentModificationException_ShouldRetryToUpdateNewCustomerWithSuccess() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("shoppingListName")); + when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); + when(mockShoppingList.getDescription()) + .thenReturn(LocalizedString.ofEnglish("shoppingListDesc")); + when(mockShoppingList.getSlug()).thenReturn(LocalizedString.ofEnglish("shoppingListSlug")); + when(mockShoppingList.getAnonymousId()).thenReturn("shoppingListAnonymousId"); + when(mockShoppingList.getDeleteDaysAfterLastModification()).thenReturn(360); + + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockShoppingList))); + + when(mockShoppingListService.updateShoppingList(any(), anyList())) + .thenReturn( + exceptionallyCompletedFuture( + new SphereException(new ConcurrentModificationException()))) + .thenReturn(completedFuture(mockShoppingList)); + + when(mockShoppingListService.fetchShoppingList("shoppingListKey")) + .thenReturn(completedFuture(Optional.of(mockShoppingList))); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) + .key("shoppingListKey") + .description(LocalizedString.ofEnglish("newShoppingListDesc")) + .slug(mockShoppingList.getSlug()) + .anonymousId(mockShoppingList.getAnonymousId()) + .deleteDaysAfterLastModification(mockShoppingList.getDeleteDaysAfterLastModification()) .build(); - } - - @Test - void sync_WithNullDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //preparation - ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions); - - //test - ShoppingListSyncStatistics statistics = shoppingListSync - .sync(singletonList(null)) - .toCompletableFuture() - .join(); - - AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); - - //assertions - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo(SHOPPING_LIST_DRAFT_IS_NULL); - } - - @Test - void sync_WithNullKeyDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //preparation - ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shopping-list-name")).build(); - ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions); - - //test - ShoppingListSyncStatistics statistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); - - //assertions - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); - } - - @Test - void sync_WithEmptyKeyDraft_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //preparation - ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shopping-list-name")).key("").build(); - ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions); - - //test - ShoppingListSyncStatistics statistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); - - //assertions - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); - } - - @Test - void sync_WithoutName_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //preparation - ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.of()).key("shopping-list-key").build(); - ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions); - - //test - ShoppingListSyncStatistics statistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); - - //assertions - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo(format(SHOPPING_LIST_DRAFT_NAME_NOT_SET, shoppingListDraft.getKey())); - } - - @Test - void sync_WithExceptionOnCachingKeysToIds_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - //preparation - final TypeService typeService = mock(TypeService.class); - final CustomerService customerService = mock(CustomerService.class); - - when(typeService.cacheKeysToIds(any())) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - when(customerService.cacheKeysToIds(any())) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - - final ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions, mock(ShoppingListService.class), - customerService , typeService); - - ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("shopping-list-name")) - .key("shopping-list-key") - .customer(ResourceIdentifier.ofKey("customer-key")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) - .build(); - - //test - ShoppingListSyncStatistics statistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); - - //assertions - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo("Failed to build a cache of keys to ids."); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasCauseExactlyInstanceOf(CompletionException.class) - .hasRootCauseExactlyInstanceOf(SphereException.class); - } - - @Test - void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - final ShoppingListService shoppingListService = mock(ShoppingListService.class); - - when(shoppingListService.fetchMatchingShoppingListsByKeys(singleton("shopping-list-key"))) - .thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - final ShoppingListSync shoppingListSync = new ShoppingListSync(syncOptions, shoppingListService, - mock(CustomerService.class), mock(TypeService.class)); - - ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shopping-list-name")) - .key("shopping-list-key") - .customer(ResourceIdentifier.ofKey("customer-key")) - .custom(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())) - .build(); - // test - final ShoppingListSyncStatistics statistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - - // assertions - AssertionsForStatistics.assertThat(statistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .isEqualTo("Failed to fetch existing shopping lists with keys: '[shopping-list-key]'."); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasCauseExactlyInstanceOf(CompletionException.class) - .hasRootCauseExactlyInstanceOf(SphereException.class); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallbackAndIncrementCreated() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(singleton("shoppingListKey"))) - .thenReturn(completedFuture(new HashSet<>(singletonList(mockShoppingList)))); - - when(mockShoppingListService.createShoppingList(any())) - .thenReturn(completedFuture(Optional.of(mockShoppingList))); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class) , mock(TypeService.class)); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("NAME")) - .key("shoppingListKey") - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 1, 0, 0); - - verify(spySyncOptions).applyBeforeCreateCallback(shoppingListDraft); - verify(spySyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - @Test - void sync_FailedOnCreation_ShouldCallBeforeCreateCallbackAndIncrementFailed() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(singleton("shoppingListKey"))) - .thenReturn(completedFuture(new HashSet<>(singletonList(mockShoppingList)))); - - // simulate an error during create, service will return an empty optional. - when(mockShoppingListService.createShoppingList(any())) - .thenReturn(completedFuture(Optional.empty())); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class), mock(TypeService.class)); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("NAME")) - .key("shoppingListKey") - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); - - verify(spySyncOptions).applyBeforeCreateCallback(shoppingListDraft); - verify(spySyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } - - @Test - void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); - - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockShoppingList))); - - when(mockShoppingListService.updateShoppingList(any(), anyList())) - .thenReturn(completedFuture(mockShoppingList)); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class) , mock(TypeService.class)); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("NAME")) - .key("shoppingListKey") - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); - - verify(spySyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spySyncOptions, never()).applyBeforeCreateCallback(shoppingListDraft); - } - - @Test - void sync_WithUnchangedShoppingListDraftAndUpdatedLineItemDraft_ShouldIncrementUpdated() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - - final LineItem mockLineItem = mock(LineItem.class); - when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); - when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("shoppingListName")); - - final ProductVariant mockProductVariant = mock(ProductVariant.class); - when(mockProductVariant.getSku()).thenReturn("dummy-sku"); - when(mockLineItem.getVariant()).thenReturn(mockProductVariant); - when(mockLineItem.getQuantity()).thenReturn(10L); - - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockShoppingList))); - - when(mockShoppingListService.updateShoppingList(any(), anyList())) - .thenReturn(completedFuture(mockShoppingList)); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class), mock(TypeService.class)); - - final List lineItemDrafts = singletonList( - LineItemDraftBuilder.ofSku("dummy-sku", 5L).build()); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) - .key("shoppingListKey") - .lineItems(lineItemDrafts) - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); - - verify(spySyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spySyncOptions, never()).applyBeforeCreateCallback(shoppingListDraft); - } - - @Test - void sync_WithUnchangedShoppingListDraftAndUpdatedTextLineItemDraft_ShouldIncrementUpdated() { - // preparation - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); - when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("shoppingListName")); - - final TextLineItem mockTextLineItem = mock(TextLineItem.class); - when(mockTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("textLineItemName")); - when(mockTextLineItem.getQuantity()).thenReturn(10L); - - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockShoppingList))); - when(mockShoppingListService.updateShoppingList(any(), anyList())) - .thenReturn(completedFuture(mockShoppingList)); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class), mock(TypeService.class)); - - final List textLineItemDrafts = singletonList( - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("textLineItemName"), 5L).build()); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) - .key("shoppingListKey") - .textLineItems(textLineItemDrafts) - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); - - verify(spySyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spySyncOptions, never()).applyBeforeCreateCallback(shoppingListDraft); - } - - @Test - void sync_WithoutUpdateActions_ShouldNotIncrementUpdated() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); - when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("shoppingListName")); - when(mockShoppingList.getDescription()).thenReturn(LocalizedString.ofEnglish("shoppingListDesc")); - when(mockShoppingList.getSlug()).thenReturn(LocalizedString.ofEnglish("shoppingListSlug")); - when(mockShoppingList.getAnonymousId()).thenReturn("shoppingListAnonymousId"); - when(mockShoppingList.getDeleteDaysAfterLastModification()).thenReturn(360); - - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockShoppingList))); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class), mock(TypeService.class)); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("shoppingListName")) - .key("shoppingListKey") - .description(mockShoppingList.getDescription()) - .slug(mockShoppingList.getSlug()) - .anonymousId(mockShoppingList.getAnonymousId()) - .deleteDaysAfterLastModification(mockShoppingList.getDeleteDaysAfterLastModification()) - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 0); - - verify(spySyncOptions).applyBeforeUpdateCallback(emptyList(), shoppingListDraft, mockShoppingList); - verify(spySyncOptions, never()).applyBeforeCreateCallback(shoppingListDraft); - } - - @Test - void sync_WithBadRequestException_ShouldFailToUpdateAndIncreaseFailedCounter() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); - - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockShoppingList))); - - when(mockShoppingListService.updateShoppingList(any(), anyList())) - .thenReturn(exceptionallyCompletedFuture(new BadRequestException("Invalid request"))); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class), mock(TypeService.class)); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) - .key("shoppingListKey") - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .contains("Invalid request"); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasRootCauseExactlyInstanceOf(BadRequestException.class); - } - - @Test - void sync_WithConcurrentModificationException_ShouldRetryToUpdateNewCustomerWithSuccess() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("shoppingListName")); - when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); - when(mockShoppingList.getDescription()).thenReturn(LocalizedString.ofEnglish("shoppingListDesc")); - when(mockShoppingList.getSlug()).thenReturn(LocalizedString.ofEnglish("shoppingListSlug")); - when(mockShoppingList.getAnonymousId()).thenReturn("shoppingListAnonymousId"); - when(mockShoppingList.getDeleteDaysAfterLastModification()).thenReturn(360); - - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockShoppingList))); - - when(mockShoppingListService.updateShoppingList(any(), anyList())) - .thenReturn(exceptionallyCompletedFuture(new SphereException(new ConcurrentModificationException()))) - .thenReturn(completedFuture(mockShoppingList)); - - when(mockShoppingListService.fetchShoppingList("shoppingListKey")) - .thenReturn(completedFuture(Optional.of(mockShoppingList))); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class), mock(TypeService.class)); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("shoppingListName")) - .key("shoppingListKey") - .description(LocalizedString.ofEnglish("newShoppingListDesc")) - .slug(mockShoppingList.getSlug()) - .anonymousId(mockShoppingList.getAnonymousId()) - .deleteDaysAfterLastModification(mockShoppingList.getDeleteDaysAfterLastModification()) - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); - } - - @Test - void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); - - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockShoppingList))); - - when(mockShoppingListService.updateShoppingList(any(), anyList())) - .thenReturn(exceptionallyCompletedFuture(new SphereException(new ConcurrentModificationException()))) - .thenReturn(completedFuture(mockShoppingList)); - - when(mockShoppingListService.fetchShoppingList("shoppingListKey")) - .thenReturn(exceptionallyCompletedFuture(new SphereException())); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class), mock(TypeService.class)); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("shoppingListName")) - .key("shoppingListKey") - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .contains("Failed to fetch from CTP while retrying after concurrency modification."); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasRootCauseExactlyInstanceOf(SphereException.class); - } - - @Test - void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); - - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockShoppingList))); - - when(mockShoppingListService.updateShoppingList(any(), anyList())) - .thenReturn(exceptionallyCompletedFuture(new SphereException(new ConcurrentModificationException()))) - .thenReturn(completedFuture(mockShoppingList)); - - when(mockShoppingListService.fetchShoppingList("shoppingListKey")) - .thenReturn(completedFuture(Optional.empty())); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class), mock(TypeService.class)); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("shoppingListName")) - .key("shoppingListKey") - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .contains("Not found when attempting to fetch while retrying after concurrency modification."); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class) - .hasNoCause(); - } - - @Test - void sync_WithExceptionOnReferenceResolution_ShouldFailToUpdateAndIncreaseFailedCounter() { - // preparation - final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); - - when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) - .thenReturn(completedFuture(singleton(mockShoppingList))); - - final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); - final TypeService typeService = mock(TypeService.class); - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFutureUtils.failed(new SphereException("CTP error on fetch"))); - - final ShoppingListSync shoppingListSync = new ShoppingListSync(spySyncOptions, mockShoppingListService, - mock(CustomerService.class), typeService); - - final ShoppingListDraft shoppingListDraft = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) - .key("shoppingListKey") - .build(); - - //test - final ShoppingListSyncStatistics shoppingListSyncStatistics = shoppingListSync - .sync(singletonList(shoppingListDraft)) - .toCompletableFuture() - .join(); - - // assertions - AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); - - assertThat(errorMessages) - .hasSize(1) - .singleElement(as(STRING)) - .contains("Failed to process the ShoppingListDraft with key:'shoppingListKey'"); - - assertThat(exceptions) - .hasSize(1) - .singleElement(as(THROWABLE)) - .isExactlyInstanceOf(SyncException.class); - } + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 1, 0); + } + + @Test + void sync_WithConcurrentModificationExceptionAndFailedFetch_ShouldFailToReFetchAndUpdate() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); + + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockShoppingList))); + + when(mockShoppingListService.updateShoppingList(any(), anyList())) + .thenReturn( + exceptionallyCompletedFuture( + new SphereException(new ConcurrentModificationException()))) + .thenReturn(completedFuture(mockShoppingList)); + + when(mockShoppingListService.fetchShoppingList("shoppingListKey")) + .thenReturn(exceptionallyCompletedFuture(new SphereException())); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) + .key("shoppingListKey") + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .contains("Failed to fetch from CTP while retrying after concurrency modification."); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasRootCauseExactlyInstanceOf(SphereException.class); + } + + @Test + void sync_WithConcurrentModificationExceptionAndUnexpectedDelete_ShouldFailToReFetchAndUpdate() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); + + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockShoppingList))); + + when(mockShoppingListService.updateShoppingList(any(), anyList())) + .thenReturn( + exceptionallyCompletedFuture( + new SphereException(new ConcurrentModificationException()))) + .thenReturn(completedFuture(mockShoppingList)); + + when(mockShoppingListService.fetchShoppingList("shoppingListKey")) + .thenReturn(completedFuture(Optional.empty())); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, + mockShoppingListService, + mock(CustomerService.class), + mock(TypeService.class)); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) + .key("shoppingListKey") + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .contains( + "Not found when attempting to fetch while retrying after concurrency modification."); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class) + .hasNoCause(); + } + + @Test + void sync_WithExceptionOnReferenceResolution_ShouldFailToUpdateAndIncreaseFailedCounter() { + // preparation + final ShoppingListService mockShoppingListService = mock(ShoppingListService.class); + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getKey()).thenReturn("shoppingListKey"); + + when(mockShoppingListService.fetchMatchingShoppingListsByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockShoppingList))); + + final ShoppingListSyncOptions spySyncOptions = spy(syncOptions); + final TypeService typeService = mock(TypeService.class); + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFutureUtils.failed(new SphereException("CTP error on fetch"))); + + final ShoppingListSync shoppingListSync = + new ShoppingListSync( + spySyncOptions, mockShoppingListService, mock(CustomerService.class), typeService); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppingListName")) + .key("shoppingListKey") + .build(); + + // test + final ShoppingListSyncStatistics shoppingListSyncStatistics = + shoppingListSync.sync(singletonList(shoppingListDraft)).toCompletableFuture().join(); + + // assertions + AssertionsForStatistics.assertThat(shoppingListSyncStatistics).hasValues(1, 0, 0, 1); + + assertThat(errorMessages) + .hasSize(1) + .singleElement(as(STRING)) + .contains("Failed to process the ShoppingListDraft with key:'shoppingListKey'"); + + assertThat(exceptions) + .hasSize(1) + .singleElement(as(THROWABLE)) + .isExactlyInstanceOf(SyncException.class); + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/helpers/LineItemReferenceResolverTest.java b/src/test/java/com/commercetools/sync/shoppinglists/helpers/LineItemReferenceResolverTest.java index 2dc335cd50..257c0c82a5 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/helpers/LineItemReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/helpers/LineItemReferenceResolverTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.shoppinglists.helpers; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; +import static com.commercetools.sync.shoppinglists.helpers.LineItemReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.services.TypeService; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; @@ -10,139 +20,117 @@ import io.sphere.sdk.shoppinglists.LineItemDraftBuilder; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.Optional; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; -import static com.commercetools.sync.shoppinglists.helpers.LineItemReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class LineItemReferenceResolverTest { - private TypeService typeService; - - private LineItemReferenceResolver referenceResolver; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - typeService = getMockTypeService(); - - final ShoppingListSyncOptions syncOptions = ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new LineItemReferenceResolver(syncOptions, typeService); - } - - @Test - void resolveReferences_WithCustomTypeId_ShouldNotResolveCustomTypeReferenceWithKey() { - final String customTypeId = "customTypeId"; - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeIdAndJson(customTypeId, new HashMap<>()); - - final LineItemDraft lineItemDraft = - LineItemDraftBuilder.ofSku("dummy-sku", 10L) - .custom(customFieldsDraft) - .build(); - - final LineItemDraft resolvedDraft = referenceResolver - .resolveReferences(lineItemDraft) - .toCompletableFuture() - .join(); - - assertThat(resolvedDraft.getCustom()).isNotNull(); - assertThat(resolvedDraft.getCustom()).isEqualTo(customFieldsDraft); - } - - @Test - void resolveReferences_WithNonNullKeyOnCustomTypeResId_ShouldResolveCustomTypeReference() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("customTypeKey", new HashMap<>()); - - final LineItemDraft lineItemDraft = - LineItemDraftBuilder.ofSku("dummy-sku", 10L) - .custom(customFieldsDraft) - .build(); - - final LineItemDraft resolvedDraft = referenceResolver - .resolveReferences(lineItemDraft) - .toCompletableFuture() - .join(); - - assertThat(resolvedDraft.getCustom()).isNotNull(); - assertThat(resolvedDraft.getCustom().getType().getId()).isEqualTo("typeId"); - } - - @Test - void resolveReferences_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFutureUtils.failed(new SphereException("CTP error on fetch"))); - - final String customTypeKey = "customTypeKey"; - final CustomFieldsDraft customFieldsDraft = - CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); - - final LineItemDraft lineItemDraft = - LineItemDraftBuilder.ofSku("dummy-sku", 10L) - .custom(customFieldsDraft) - .build(); - - assertThat(referenceResolver.resolveReferences(lineItemDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveReferences_WithNonExistentCustomType_ShouldCompleteExceptionally() { - final String customTypeKey = "customTypeKey"; - final CustomFieldsDraft customFieldsDraft = - CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); - - final LineItemDraft lineItemDraft = - LineItemDraftBuilder.ofSku("dummy-sku", 10L) - .custom(customFieldsDraft) - .build(); - - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final String expectedExceptionMessage = format(FAILED_TO_RESOLVE_CUSTOM_TYPE, lineItemDraft.getSku()); - - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); - - assertThat(referenceResolver.resolveReferences(lineItemDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveReferences_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("", new HashMap<>()); - - final LineItemDraft lineItemDraft = - LineItemDraftBuilder.ofSku("dummy-sku", 10L) - .custom(customFieldsDraft) - .build(); - - assertThat(referenceResolver.resolveReferences(lineItemDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on LineItemDraft" - + " with SKU: 'dummy-sku'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - - } + private TypeService typeService; + + private LineItemReferenceResolver referenceResolver; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + typeService = getMockTypeService(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = new LineItemReferenceResolver(syncOptions, typeService); + } + + @Test + void resolveReferences_WithCustomTypeId_ShouldNotResolveCustomTypeReferenceWithKey() { + final String customTypeId = "customTypeId"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson(customTypeId, new HashMap<>()); + + final LineItemDraft lineItemDraft = + LineItemDraftBuilder.ofSku("dummy-sku", 10L).custom(customFieldsDraft).build(); + + final LineItemDraft resolvedDraft = + referenceResolver.resolveReferences(lineItemDraft).toCompletableFuture().join(); + + assertThat(resolvedDraft.getCustom()).isNotNull(); + assertThat(resolvedDraft.getCustom()).isEqualTo(customFieldsDraft); + } + + @Test + void resolveReferences_WithNonNullKeyOnCustomTypeResId_ShouldResolveCustomTypeReference() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("customTypeKey", new HashMap<>()); + + final LineItemDraft lineItemDraft = + LineItemDraftBuilder.ofSku("dummy-sku", 10L).custom(customFieldsDraft).build(); + + final LineItemDraft resolvedDraft = + referenceResolver.resolveReferences(lineItemDraft).toCompletableFuture().join(); + + assertThat(resolvedDraft.getCustom()).isNotNull(); + assertThat(resolvedDraft.getCustom().getType().getId()).isEqualTo("typeId"); + } + + @Test + void resolveReferences_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFutureUtils.failed(new SphereException("CTP error on fetch"))); + + final String customTypeKey = "customTypeKey"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); + + final LineItemDraft lineItemDraft = + LineItemDraftBuilder.ofSku("dummy-sku", 10L).custom(customFieldsDraft).build(); + + assertThat(referenceResolver.resolveReferences(lineItemDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveReferences_WithNonExistentCustomType_ShouldCompleteExceptionally() { + final String customTypeKey = "customTypeKey"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); + + final LineItemDraft lineItemDraft = + LineItemDraftBuilder.ofSku("dummy-sku", 10L).custom(customFieldsDraft).build(); + + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final String expectedExceptionMessage = + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, lineItemDraft.getSku()); + + final String expectedMessageWithCause = + format( + "%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); + + assertThat(referenceResolver.resolveReferences(lineItemDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void resolveReferences_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("", new HashMap<>()); + + final LineItemDraft lineItemDraft = + LineItemDraftBuilder.ofSku("dummy-sku", 10L).custom(customFieldsDraft).build(); + + assertThat(referenceResolver.resolveReferences(lineItemDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on LineItemDraft" + + " with SKU: 'dummy-sku'. Reason: %s", + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListBatchValidatorTest.java b/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListBatchValidatorTest.java index 3cf43cb1f7..8403110a4e 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListBatchValidatorTest.java @@ -1,5 +1,21 @@ package com.commercetools.sync.shoppinglists.helpers; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.LINE_ITEM_DRAFT_IS_NULL; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.LINE_ITEM_DRAFT_SKU_NOT_SET; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_IS_NULL; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_KEY_NOT_SET; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_NAME_NOT_SET; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.TEXT_LINE_ITEM_DRAFT_IS_NULL; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET; +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptionsBuilder; import io.sphere.sdk.client.SphereClient; @@ -12,303 +28,303 @@ import io.sphere.sdk.shoppinglists.TextLineItemDraft; import io.sphere.sdk.shoppinglists.TextLineItemDraftBuilder; import io.sphere.sdk.types.CustomFieldsDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; - -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.LINE_ITEM_DRAFT_IS_NULL; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.LINE_ITEM_DRAFT_SKU_NOT_SET; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_IS_NULL; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_KEY_NOT_SET; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.SHOPPING_LIST_DRAFT_NAME_NOT_SET; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.TEXT_LINE_ITEM_DRAFT_IS_NULL; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListBatchValidator.TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET; -import static java.lang.String.format; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ShoppingListBatchValidatorTest { - private ShoppingListSyncOptions syncOptions; - private ShoppingListSyncStatistics syncStatistics; - private List errorCallBackMessages; + private ShoppingListSyncOptions syncOptions; + private ShoppingListSyncStatistics syncStatistics; + private List errorCallBackMessages; - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = ShoppingListSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorCallBackMessages.add(exception.getMessage())) + syncOptions = + ShoppingListSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallBackMessages.add(exception.getMessage())) .build(); - syncStatistics = mock(ShoppingListSyncStatistics.class); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(emptyList()); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullShoppingListDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(singletonList(null)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(SHOPPING_LIST_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithShoppingListDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithShoppingListDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - when(shoppingListDraft.getKey()).thenReturn(EMPTY); - when(shoppingListDraft.getName()).thenReturn(LocalizedString.empty()); - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithShoppingListDraftWithNullName_ShouldHaveValidationErrorAndEmptyResult() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(SHOPPING_LIST_DRAFT_NAME_NOT_SET, shoppingListDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithShoppingListDraftWithEmptyName_ShouldHaveValidationErrorAndEmptyResult() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); - when(shoppingListDraft.getName()).thenReturn(LocalizedString.of()); - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(SHOPPING_LIST_DRAFT_NAME_NOT_SET, shoppingListDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithLineItemDraftIsNull_ShouldHaveValidationErrorAndEmptyResult() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); - when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); - when(shoppingListDraft.getLineItems()).thenReturn(singletonList(null)); - - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(LINE_ITEM_DRAFT_IS_NULL, 0, shoppingListDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithLineItemDraftWithNullSku_ShouldHaveValidationErrorAndEmptyResult() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); - when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); - when(shoppingListDraft.getLineItems()).thenReturn(singletonList(LineItemDraftBuilder.ofSku("", - 1L).build())); - - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(LINE_ITEM_DRAFT_SKU_NOT_SET, 0, shoppingListDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithTextLineItemDraftIsNull_ShouldHaveValidationErrorAndEmptyResult() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); - when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); - when(shoppingListDraft.getLineItems()).thenReturn(singletonList(LineItemDraftBuilder.ofSku("123", - 1L).build())); - when(shoppingListDraft.getTextLineItems()).thenReturn(singletonList(null)); - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(TEXT_LINE_ITEM_DRAFT_IS_NULL, 0, shoppingListDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithLineItemAndTextLineItemError_ShouldHaveOneCallback() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); - when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); - when(shoppingListDraft.getLineItems()).thenReturn(singletonList(LineItemDraftBuilder.ofSku("", - 1L).build())); - when(shoppingListDraft.getTextLineItems()).thenReturn(singletonList(null)); - - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format("%s,%s", format(LINE_ITEM_DRAFT_SKU_NOT_SET, 0, shoppingListDraft.getKey()), - format(TEXT_LINE_ITEM_DRAFT_IS_NULL,0, shoppingListDraft.getKey()))); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithTextLineItemDraftWithEmptyName_ShouldHaveValidationErrorAndEmptyResult() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); - when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); - when(shoppingListDraft.getLineItems()).thenReturn(singletonList(LineItemDraftBuilder.ofSku("123", - 1L).build())); - when(shoppingListDraft.getTextLineItems()).thenReturn( - singletonList(TextLineItemDraftBuilder.of(LocalizedString.empty(), - 1L).build())); - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET, 0, shoppingListDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithTextLineItemDraftWithNullName_ShouldHaveValidationErrorAndEmptyResult() { - final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); - when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); - when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); - when(shoppingListDraft.getLineItems()).thenReturn(singletonList(LineItemDraftBuilder.ofSku("123", - 1L).build())); - when(shoppingListDraft.getTextLineItems()).thenReturn( - singletonList(TextLineItemDraftBuilder.of(null, - 1L).build())); - final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET, 0, shoppingListDraft.getKey())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { - final ShoppingListDraft validShoppingListDraft = mock(ShoppingListDraft.class); - when(validShoppingListDraft.getKey()).thenReturn("validDraftKey"); - when(validShoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("name")); - when(validShoppingListDraft.getCustom()) - .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())); - LineItemDraft lineItem = mock(LineItemDraft.class); - when(lineItem.getCustom()).thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("lineItemTypeKey", - emptyMap())); - when(lineItem.getSku()).thenReturn("validSku"); - when(validShoppingListDraft.getLineItems()).thenReturn(singletonList(lineItem)); - TextLineItemDraft textLineItem = mock(TextLineItemDraft.class); - when(textLineItem.getCustom()).thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("textLineItemTypeKey", - emptyMap())); - when(textLineItem.getName()).thenReturn(LocalizedString.ofEnglish("validName")); - when(validShoppingListDraft.getTextLineItems()).thenReturn(singletonList(textLineItem)); - Customer customer = mock(Customer.class); - when(customer.getKey()).thenReturn("customerKey"); - final ResourceIdentifier customerResourceIdentifier = ResourceIdentifier.ofKey(customer.getKey()); - when(validShoppingListDraft.getCustomer()).thenReturn(customerResourceIdentifier); - - final ShoppingListDraft validShoppingListDraftWithoutReferences = mock(ShoppingListDraft.class); - when(validShoppingListDraftWithoutReferences.getKey()).thenReturn("validDraftKey"); - when(validShoppingListDraftWithoutReferences.getName()).thenReturn(LocalizedString.ofEnglish("name")); - when(validShoppingListDraftWithoutReferences.getLineItems()).thenReturn(null); - when(validShoppingListDraftWithoutReferences.getTextLineItems()).thenReturn(null); - - final ShoppingListDraft invalidShoppingListDraft = mock(ShoppingListDraft.class); - - final ShoppingListBatchValidator shoppingListBatchValidator = - new ShoppingListBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, ShoppingListBatchValidator.ReferencedKeys> pair - = shoppingListBatchValidator.validateAndCollectReferencedKeys( - Arrays.asList(validShoppingListDraft, invalidShoppingListDraft, validShoppingListDraftWithoutReferences)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, invalidShoppingListDraft.getName())); - assertThat(pair.getLeft()) - .containsExactlyInAnyOrder(validShoppingListDraft, validShoppingListDraftWithoutReferences); - assertThat(pair.getRight().getTypeKeys()) - .containsExactlyInAnyOrder("typeKey", "lineItemTypeKey", "textLineItemTypeKey"); - assertThat(pair.getRight().getCustomerKeys()) - .containsExactlyInAnyOrder("customerKey"); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyKeys_ShouldNotCollectKeys() { - final ShoppingListDraft validShoppingListDraft = mock(ShoppingListDraft.class); - when(validShoppingListDraft.getKey()).thenReturn("validDraftKey"); - when(validShoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("name")); - when(validShoppingListDraft.getCustom()) - .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson(EMPTY, emptyMap())); - LineItemDraft lineItem = mock(LineItemDraft.class); - when(lineItem.getCustom()).thenReturn(CustomFieldsDraft.ofTypeKeyAndJson(EMPTY, - emptyMap())); - when(lineItem.getSku()).thenReturn("validSku"); - when(validShoppingListDraft.getLineItems()).thenReturn(singletonList(lineItem)); - TextLineItemDraft textLineItem = mock(TextLineItemDraft.class); - when(textLineItem.getCustom()).thenReturn(CustomFieldsDraft.ofTypeKeyAndJson(EMPTY, - emptyMap())); - when(textLineItem.getName()).thenReturn(LocalizedString.ofEnglish("validName")); - when(validShoppingListDraft.getTextLineItems()).thenReturn(singletonList(textLineItem)); - final ResourceIdentifier customerResourceIdentifier = ResourceIdentifier.ofKey(EMPTY); - when(validShoppingListDraft.getCustomer()).thenReturn(customerResourceIdentifier); - - final ShoppingListBatchValidator shoppingListBatchValidator = - new ShoppingListBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, ShoppingListBatchValidator.ReferencedKeys> pair - = shoppingListBatchValidator.validateAndCollectReferencedKeys( + syncStatistics = mock(ShoppingListSyncStatistics.class); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(emptyList()); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullShoppingListDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(singletonList(null)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(SHOPPING_LIST_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithShoppingListDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithShoppingListDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + when(shoppingListDraft.getKey()).thenReturn(EMPTY); + when(shoppingListDraft.getName()).thenReturn(LocalizedString.empty()); + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, shoppingListDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithShoppingListDraftWithNullName_ShouldHaveValidationErrorAndEmptyResult() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(SHOPPING_LIST_DRAFT_NAME_NOT_SET, shoppingListDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithShoppingListDraftWithEmptyName_ShouldHaveValidationErrorAndEmptyResult() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); + when(shoppingListDraft.getName()).thenReturn(LocalizedString.of()); + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(SHOPPING_LIST_DRAFT_NAME_NOT_SET, shoppingListDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithLineItemDraftIsNull_ShouldHaveValidationErrorAndEmptyResult() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); + when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); + when(shoppingListDraft.getLineItems()).thenReturn(singletonList(null)); + + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(LINE_ITEM_DRAFT_IS_NULL, 0, shoppingListDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithLineItemDraftWithNullSku_ShouldHaveValidationErrorAndEmptyResult() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); + when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); + when(shoppingListDraft.getLineItems()) + .thenReturn(singletonList(LineItemDraftBuilder.ofSku("", 1L).build())); + + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(LINE_ITEM_DRAFT_SKU_NOT_SET, 0, shoppingListDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithTextLineItemDraftIsNull_ShouldHaveValidationErrorAndEmptyResult() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); + when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); + when(shoppingListDraft.getLineItems()) + .thenReturn(singletonList(LineItemDraftBuilder.ofSku("123", 1L).build())); + when(shoppingListDraft.getTextLineItems()).thenReturn(singletonList(null)); + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(TEXT_LINE_ITEM_DRAFT_IS_NULL, 0, shoppingListDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithLineItemAndTextLineItemError_ShouldHaveOneCallback() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); + when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); + when(shoppingListDraft.getLineItems()) + .thenReturn(singletonList(LineItemDraftBuilder.ofSku("", 1L).build())); + when(shoppingListDraft.getTextLineItems()).thenReturn(singletonList(null)); + + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo( + format( + "%s,%s", + format(LINE_ITEM_DRAFT_SKU_NOT_SET, 0, shoppingListDraft.getKey()), + format(TEXT_LINE_ITEM_DRAFT_IS_NULL, 0, shoppingListDraft.getKey()))); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithTextLineItemDraftWithEmptyName_ShouldHaveValidationErrorAndEmptyResult() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); + when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); + when(shoppingListDraft.getLineItems()) + .thenReturn(singletonList(LineItemDraftBuilder.ofSku("123", 1L).build())); + when(shoppingListDraft.getTextLineItems()) + .thenReturn( + singletonList(TextLineItemDraftBuilder.of(LocalizedString.empty(), 1L).build())); + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET, 0, shoppingListDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithTextLineItemDraftWithNullName_ShouldHaveValidationErrorAndEmptyResult() { + final ShoppingListDraft shoppingListDraft = mock(ShoppingListDraft.class); + when(shoppingListDraft.getKey()).thenReturn("validDraftKey"); + when(shoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("validDraftName")); + when(shoppingListDraft.getLineItems()) + .thenReturn(singletonList(LineItemDraftBuilder.ofSku("123", 1L).build())); + when(shoppingListDraft.getTextLineItems()) + .thenReturn(singletonList(TextLineItemDraftBuilder.of(null, 1L).build())); + final Set validDrafts = getValidDrafts(singletonList(shoppingListDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(TEXT_LINE_ITEM_DRAFT_NAME_NOT_SET, 0, shoppingListDraft.getKey())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { + final ShoppingListDraft validShoppingListDraft = mock(ShoppingListDraft.class); + when(validShoppingListDraft.getKey()).thenReturn("validDraftKey"); + when(validShoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("name")); + when(validShoppingListDraft.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("typeKey", emptyMap())); + LineItemDraft lineItem = mock(LineItemDraft.class); + when(lineItem.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("lineItemTypeKey", emptyMap())); + when(lineItem.getSku()).thenReturn("validSku"); + when(validShoppingListDraft.getLineItems()).thenReturn(singletonList(lineItem)); + TextLineItemDraft textLineItem = mock(TextLineItemDraft.class); + when(textLineItem.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson("textLineItemTypeKey", emptyMap())); + when(textLineItem.getName()).thenReturn(LocalizedString.ofEnglish("validName")); + when(validShoppingListDraft.getTextLineItems()).thenReturn(singletonList(textLineItem)); + Customer customer = mock(Customer.class); + when(customer.getKey()).thenReturn("customerKey"); + final ResourceIdentifier customerResourceIdentifier = + ResourceIdentifier.ofKey(customer.getKey()); + when(validShoppingListDraft.getCustomer()).thenReturn(customerResourceIdentifier); + + final ShoppingListDraft validShoppingListDraftWithoutReferences = mock(ShoppingListDraft.class); + when(validShoppingListDraftWithoutReferences.getKey()).thenReturn("validDraftKey"); + when(validShoppingListDraftWithoutReferences.getName()) + .thenReturn(LocalizedString.ofEnglish("name")); + when(validShoppingListDraftWithoutReferences.getLineItems()).thenReturn(null); + when(validShoppingListDraftWithoutReferences.getTextLineItems()).thenReturn(null); + + final ShoppingListDraft invalidShoppingListDraft = mock(ShoppingListDraft.class); + + final ShoppingListBatchValidator shoppingListBatchValidator = + new ShoppingListBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, ShoppingListBatchValidator.ReferencedKeys> pair = + shoppingListBatchValidator.validateAndCollectReferencedKeys( + Arrays.asList( + validShoppingListDraft, + invalidShoppingListDraft, + validShoppingListDraftWithoutReferences)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(SHOPPING_LIST_DRAFT_KEY_NOT_SET, invalidShoppingListDraft.getName())); + assertThat(pair.getLeft()) + .containsExactlyInAnyOrder(validShoppingListDraft, validShoppingListDraftWithoutReferences); + assertThat(pair.getRight().getTypeKeys()) + .containsExactlyInAnyOrder("typeKey", "lineItemTypeKey", "textLineItemTypeKey"); + assertThat(pair.getRight().getCustomerKeys()).containsExactlyInAnyOrder("customerKey"); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyKeys_ShouldNotCollectKeys() { + final ShoppingListDraft validShoppingListDraft = mock(ShoppingListDraft.class); + when(validShoppingListDraft.getKey()).thenReturn("validDraftKey"); + when(validShoppingListDraft.getName()).thenReturn(LocalizedString.ofEnglish("name")); + when(validShoppingListDraft.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson(EMPTY, emptyMap())); + LineItemDraft lineItem = mock(LineItemDraft.class); + when(lineItem.getCustom()).thenReturn(CustomFieldsDraft.ofTypeKeyAndJson(EMPTY, emptyMap())); + when(lineItem.getSku()).thenReturn("validSku"); + when(validShoppingListDraft.getLineItems()).thenReturn(singletonList(lineItem)); + TextLineItemDraft textLineItem = mock(TextLineItemDraft.class); + when(textLineItem.getCustom()) + .thenReturn(CustomFieldsDraft.ofTypeKeyAndJson(EMPTY, emptyMap())); + when(textLineItem.getName()).thenReturn(LocalizedString.ofEnglish("validName")); + when(validShoppingListDraft.getTextLineItems()).thenReturn(singletonList(textLineItem)); + final ResourceIdentifier customerResourceIdentifier = ResourceIdentifier.ofKey(EMPTY); + when(validShoppingListDraft.getCustomer()).thenReturn(customerResourceIdentifier); + + final ShoppingListBatchValidator shoppingListBatchValidator = + new ShoppingListBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, ShoppingListBatchValidator.ReferencedKeys> pair = + shoppingListBatchValidator.validateAndCollectReferencedKeys( Arrays.asList(validShoppingListDraft)); - assertThat(pair.getLeft()).contains(validShoppingListDraft); - assertThat(pair.getRight().getCustomerKeys()).isEmpty(); - assertThat(pair.getRight().getTypeKeys()).isEmpty(); - assertThat(errorCallBackMessages).hasSize(0); - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List shoppingListDrafts) { - final ShoppingListBatchValidator shoppingListBatchValidator = - new ShoppingListBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, ShoppingListBatchValidator.ReferencedKeys> pair = - shoppingListBatchValidator.validateAndCollectReferencedKeys(shoppingListDrafts); - return pair.getLeft(); - } + assertThat(pair.getLeft()).contains(validShoppingListDraft); + assertThat(pair.getRight().getCustomerKeys()).isEmpty(); + assertThat(pair.getRight().getTypeKeys()).isEmpty(); + assertThat(errorCallBackMessages).hasSize(0); + } + + @Nonnull + private Set getValidDrafts( + @Nonnull final List shoppingListDrafts) { + final ShoppingListBatchValidator shoppingListBatchValidator = + new ShoppingListBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, ShoppingListBatchValidator.ReferencedKeys> pair = + shoppingListBatchValidator.validateAndCollectReferencedKeys(shoppingListDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolverTest.java b/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolverTest.java index 06a816f05e..133d508ec3 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListReferenceResolverTest.java @@ -1,5 +1,21 @@ package com.commercetools.sync.shoppinglists.helpers; +import static com.commercetools.sync.commons.MockUtils.getMockCustomerService; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListReferenceResolver.CUSTOMER_DOES_NOT_EXIST; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListReferenceResolver.FAILED_TO_RESOLVE_CUSTOMER_REFERENCE; +import static com.commercetools.sync.shoppinglists.helpers.ShoppingListReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.services.CustomerService; import com.commercetools.sync.services.TypeService; @@ -18,9 +34,6 @@ import io.sphere.sdk.shoppinglists.TextLineItemDraftBuilder; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.time.ZonedDateTime; import java.util.HashMap; import java.util.Locale; @@ -28,307 +41,290 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import static com.commercetools.sync.commons.MockUtils.getMockCustomerService; -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListReferenceResolver.CUSTOMER_DOES_NOT_EXIST; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListReferenceResolver.FAILED_TO_RESOLVE_CUSTOMER_REFERENCE; -import static com.commercetools.sync.shoppinglists.helpers.ShoppingListReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ShoppingListReferenceResolverTest { - private TypeService typeService; - private CustomerService customerService; - - private ShoppingListReferenceResolver referenceResolver; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - typeService = getMockTypeService(); - customerService = getMockCustomerService(); - - final ShoppingListSyncOptions syncOptions = ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new ShoppingListReferenceResolver(syncOptions, customerService, typeService); - } - - @Test - void resolveCustomTypeReference_WithNonNullIdOnCustomTypeResId_ShouldResolveCustomTypeReference() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("customTypeKey", new HashMap<>()); - - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .custom(customFieldsDraft) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - final ShoppingListDraftBuilder resolvedDraft = referenceResolver - .resolveCustomTypeReference(draftBuilder) - .toCompletableFuture().join(); - - assertThat(resolvedDraft.getCustom()).isNotNull(); - assertThat(resolvedDraft.getCustom().getType().getId()).isEqualTo("typeId"); - - } - - @Test - void resolveCustomTypeReference_WithCustomTypeId_ShouldNotResolveCustomTypeReferenceWithKey() { - final String customTypeId = "customTypeId"; - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeIdAndJson(customTypeId, new HashMap<>()); - - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .custom(customFieldsDraft) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - final ShoppingListDraftBuilder resolvedDraft = referenceResolver - .resolveCustomTypeReference(draftBuilder) - .toCompletableFuture().join(); - - assertThat(resolvedDraft.getCustom()).isNotNull(); - assertThat(resolvedDraft.getCustom()).isEqualTo(customFieldsDraft); - } - - @Test - void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFutureUtils.failed(new SphereException("CTP error on fetch"))); - - final String customTypeKey = "customTypeKey"; - final CustomFieldsDraft customFieldsDraft = - CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .custom(customFieldsDraft) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - final CompletionStage resolvedDraftCompletionStage = referenceResolver - .resolveCustomTypeReference(draftBuilder); - - assertThat(resolvedDraftCompletionStage) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { - final String customTypeKey = "customTypeKey"; - final CustomFieldsDraft customFieldsDraft = - CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); - - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .custom(customFieldsDraft) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - // test - final CompletionStage resolvedDraftCompletionStage = referenceResolver - .resolveCustomTypeReference(draftBuilder); - - // assertion - final String expectedExceptionMessage = format(FAILED_TO_RESOLVE_CUSTOM_TYPE, - draftBuilder.getKey()); - - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); - ; - assertThat(resolvedDraftCompletionStage) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("", new HashMap<>()); - - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .custom(customFieldsDraft) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - assertThat(referenceResolver.resolveCustomTypeReference(draftBuilder)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on ShoppingListDraft" - + " with key:'null'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomerReference_WithCustomerKey_ShouldResolveCustomerReference() { - when(customerService.fetchCachedCustomerId("customerKey")) - .thenReturn(CompletableFuture.completedFuture(Optional.of("customerId"))); - - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .customer(ResourceIdentifier.ofKey("customerKey")) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - final ShoppingListDraftBuilder resolvedDraft = referenceResolver - .resolveCustomerReference(draftBuilder) - .toCompletableFuture() - .join(); - - assertThat(resolvedDraft.getCustomer()).isNotNull(); - assertThat(resolvedDraft.getCustomer().getId()).isEqualTo("customerId"); - } - - @Test - void resolveCustomerReference_WithNullCustomerReference_ShouldNotResolveCustomerReference() { - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .customer((ResourceIdentifier) null) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - final ShoppingListDraftBuilder referencesResolvedDraft = - referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture().join(); - - assertThat(referencesResolvedDraft.getCustom()).isNull(); - } - - @Test - void resolveCustomerReference_WithExceptionOnCustomerGroupFetch_ShouldNotResolveReference() { - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .customer(ResourceIdentifier.ofKey("anyKey")) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(customerService.fetchCachedCustomerId(anyString())).thenReturn(futureThrowingSphereException); - - assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveCustomerReference_WithNullCustomerKey_ShouldNotResolveCustomerReference() { - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .customer(ResourceIdentifier.ofKey(null)) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, draftBuilder.getKey(), + private TypeService typeService; + private CustomerService customerService; + + private ShoppingListReferenceResolver referenceResolver; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + typeService = getMockTypeService(); + customerService = getMockCustomerService(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = + new ShoppingListReferenceResolver(syncOptions, customerService, typeService); + } + + @Test + void + resolveCustomTypeReference_WithNonNullIdOnCustomTypeResId_ShouldResolveCustomTypeReference() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("customTypeKey", new HashMap<>()); + + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .custom(customFieldsDraft) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + final ShoppingListDraftBuilder resolvedDraft = + referenceResolver.resolveCustomTypeReference(draftBuilder).toCompletableFuture().join(); + + assertThat(resolvedDraft.getCustom()).isNotNull(); + assertThat(resolvedDraft.getCustom().getType().getId()).isEqualTo("typeId"); + } + + @Test + void resolveCustomTypeReference_WithCustomTypeId_ShouldNotResolveCustomTypeReferenceWithKey() { + final String customTypeId = "customTypeId"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson(customTypeId, new HashMap<>()); + + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .custom(customFieldsDraft) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + final ShoppingListDraftBuilder resolvedDraft = + referenceResolver.resolveCustomTypeReference(draftBuilder).toCompletableFuture().join(); + + assertThat(resolvedDraft.getCustom()).isNotNull(); + assertThat(resolvedDraft.getCustom()).isEqualTo(customFieldsDraft); + } + + @Test + void resolveCustomTypeReference_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFutureUtils.failed(new SphereException("CTP error on fetch"))); + + final String customTypeKey = "customTypeKey"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .custom(customFieldsDraft) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + final CompletionStage resolvedDraftCompletionStage = + referenceResolver.resolveCustomTypeReference(draftBuilder); + + assertThat(resolvedDraftCompletionStage) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveCustomTypeReference_WithNonExistentCustomType_ShouldCompleteExceptionally() { + final String customTypeKey = "customTypeKey"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); + + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .custom(customFieldsDraft) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + // test + final CompletionStage resolvedDraftCompletionStage = + referenceResolver.resolveCustomTypeReference(draftBuilder); + + // assertion + final String expectedExceptionMessage = + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, draftBuilder.getKey()); + + final String expectedMessageWithCause = + format( + "%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); + ; + assertThat(resolvedDraftCompletionStage) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void resolveCustomTypeReference_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("", new HashMap<>()); + + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .custom(customFieldsDraft) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + assertThat(referenceResolver.resolveCustomTypeReference(draftBuilder)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on ShoppingListDraft" + + " with key:'null'. Reason: %s", BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomerReference_WithEmptyCustomerKey_ShouldNotResolveCustomerReference() { - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .customer(ResourceIdentifier.ofKey(" ")) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, draftBuilder.getKey(), + } + + @Test + void resolveCustomerReference_WithCustomerKey_ShouldResolveCustomerReference() { + when(customerService.fetchCachedCustomerId("customerKey")) + .thenReturn(CompletableFuture.completedFuture(Optional.of("customerId"))); + + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .customer(ResourceIdentifier.ofKey("customerKey")) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + final ShoppingListDraftBuilder resolvedDraft = + referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture().join(); + + assertThat(resolvedDraft.getCustomer()).isNotNull(); + assertThat(resolvedDraft.getCustomer().getId()).isEqualTo("customerId"); + } + + @Test + void resolveCustomerReference_WithNullCustomerReference_ShouldNotResolveCustomerReference() { + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .customer((ResourceIdentifier) null) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + final ShoppingListDraftBuilder referencesResolvedDraft = + referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture().join(); + + assertThat(referencesResolvedDraft.getCustom()).isNull(); + } + + @Test + void resolveCustomerReference_WithExceptionOnCustomerGroupFetch_ShouldNotResolveReference() { + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .customer(ResourceIdentifier.ofKey("anyKey")) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + final CompletableFuture> futureThrowingSphereException = + new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(customerService.fetchCachedCustomerId(anyString())) + .thenReturn(futureThrowingSphereException); + + assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveCustomerReference_WithNullCustomerKey_ShouldNotResolveCustomerReference() { + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .customer(ResourceIdentifier.ofKey(null)) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, + draftBuilder.getKey(), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - } - - @Test - void resolveCustomerReference_WithNonExistingCustomerKey_ShouldNotResolveCustomerReference() { - when(customerService.fetchCachedCustomerId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final String customerKey = "non-existing-customer-key"; - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .customer(ResourceIdentifier.ofKey(customerKey)) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format(ShoppingListReferenceResolver.FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, - draftBuilder.getKey(), format(CUSTOMER_DOES_NOT_EXIST, customerKey))); - } - - @Test - void resolveCustomerReference_WithIdOnCustomerReference_ShouldNotResolveReference() { - final ShoppingListDraftBuilder draftBuilder = - ShoppingListDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "NAME")) - .customer(ResourceIdentifier.ofId("existingId")) - .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); - - assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> + } + + @Test + void resolveCustomerReference_WithEmptyCustomerKey_ShouldNotResolveCustomerReference() { + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .customer(ResourceIdentifier.ofKey(" ")) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, + draftBuilder.getKey(), + BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); + } + + @Test + void resolveCustomerReference_WithNonExistingCustomerKey_ShouldNotResolveCustomerReference() { + when(customerService.fetchCachedCustomerId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final String customerKey = "non-existing-customer-key"; + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .customer(ResourceIdentifier.ofKey(customerKey)) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + ShoppingListReferenceResolver.FAILED_TO_RESOLVE_CUSTOMER_REFERENCE, + draftBuilder.getKey(), + format(CUSTOMER_DOES_NOT_EXIST, customerKey))); + } + + @Test + void resolveCustomerReference_WithIdOnCustomerReference_ShouldNotResolveReference() { + final ShoppingListDraftBuilder draftBuilder = + ShoppingListDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "NAME")) + .customer(ResourceIdentifier.ofId("existingId")) + .description(LocalizedString.of(Locale.ENGLISH, "DESCRIPTION")); + + assertThat(referenceResolver.resolveCustomerReference(draftBuilder).toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching( + resolvedDraft -> Objects.equals(resolvedDraft.getCustomer(), draftBuilder.getCustomer())); - } + } - @Test - void resolveReferences_WithoutReferences_ShouldNotResolveReferences() { - final ShoppingListDraft shoppingListDraft = ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("name")) + @Test + void resolveReferences_WithoutReferences_ShouldNotResolveReferences() { + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) .key("shoppingList-key") .build(); - final ShoppingListDraft resolvedDraft = referenceResolver - .resolveReferences(shoppingListDraft) - .toCompletableFuture() - .join(); - - assertThat(resolvedDraft).isEqualTo(shoppingListDraft); - } - - @Test - void resolveReferences_WithAllValidFieldsAndReferences_ShouldResolveReferences() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("typeKey", new HashMap<>()); - - final ZonedDateTime addedAt = ZonedDateTime.now(); - final LineItemDraft lineItemDraft = - LineItemDraftBuilder.ofSku("variant-sku", 20L) - .custom(customFieldsDraft) - .addedAt(addedAt) - .build(); - - final TextLineItemDraft textLineItemDraft = - TextLineItemDraftBuilder - .of(LocalizedString.ofEnglish("textLineItemName"), 10L) - .description(LocalizedString.ofEnglish("desc")) - .custom(customFieldsDraft) - .addedAt(addedAt) - .build(); - - final ShoppingListDraft shoppingListDraft = ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("name")) + final ShoppingListDraft resolvedDraft = + referenceResolver.resolveReferences(shoppingListDraft).toCompletableFuture().join(); + + assertThat(resolvedDraft).isEqualTo(shoppingListDraft); + } + + @Test + void resolveReferences_WithAllValidFieldsAndReferences_ShouldResolveReferences() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("typeKey", new HashMap<>()); + + final ZonedDateTime addedAt = ZonedDateTime.now(); + final LineItemDraft lineItemDraft = + LineItemDraftBuilder.ofSku("variant-sku", 20L) + .custom(customFieldsDraft) + .addedAt(addedAt) + .build(); + + final TextLineItemDraft textLineItemDraft = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("textLineItemName"), 10L) + .description(LocalizedString.ofEnglish("desc")) + .custom(customFieldsDraft) + .addedAt(addedAt) + .build(); + + final ShoppingListDraft shoppingListDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) .key("shoppingList-key") .description(LocalizedString.ofEnglish("desc")) .slug(LocalizedString.ofEnglish("slug")) @@ -340,13 +336,11 @@ void resolveReferences_WithAllValidFieldsAndReferences_ShouldResolveReferences() .customer(ResourceIdentifier.ofKey("customerKey")) .build(); - final ShoppingListDraft resolvedDraft = referenceResolver - .resolveReferences(shoppingListDraft) - .toCompletableFuture() - .join(); + final ShoppingListDraft resolvedDraft = + referenceResolver.resolveReferences(shoppingListDraft).toCompletableFuture().join(); - final ShoppingListDraft expectedDraft = ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("name")) + final ShoppingListDraft expectedDraft = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) .key("shoppingList-key") .description(LocalizedString.ofEnglish("desc")) .slug(LocalizedString.ofEnglish("slug")) @@ -354,19 +348,21 @@ void resolveReferences_WithAllValidFieldsAndReferences_ShouldResolveReferences() .anonymousId("anonymousId") .custom(CustomFieldsDraft.ofTypeIdAndJson("typeId", new HashMap<>())) .customer(Customer.referenceOfId("customerId").toResourceIdentifier()) - .lineItems(singletonList(LineItemDraftBuilder - .ofSku("variant-sku", 20L) - .custom(CustomFieldsDraft.ofTypeIdAndJson("typeId", new HashMap<>())) - .addedAt(addedAt) - .build())) - .textLineItems(singletonList(TextLineItemDraftBuilder - .of(LocalizedString.ofEnglish("textLineItemName"), 10L) - .description(LocalizedString.ofEnglish("desc")) - .custom(CustomFieldsDraft.ofTypeIdAndJson("typeId", new HashMap<>())) - .addedAt(addedAt) - .build())) + .lineItems( + singletonList( + LineItemDraftBuilder.ofSku("variant-sku", 20L) + .custom(CustomFieldsDraft.ofTypeIdAndJson("typeId", new HashMap<>())) + .addedAt(addedAt) + .build())) + .textLineItems( + singletonList( + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("textLineItemName"), 10L) + .description(LocalizedString.ofEnglish("desc")) + .custom(CustomFieldsDraft.ofTypeIdAndJson("typeId", new HashMap<>())) + .addedAt(addedAt) + .build())) .build(); - assertThat(resolvedDraft).isEqualTo(expectedDraft); - } + assertThat(resolvedDraft).isEqualTo(expectedDraft); + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListSyncStatisticsTest.java b/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListSyncStatisticsTest.java index 2fcccdcdf1..fb23367e54 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListSyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/helpers/ShoppingListSyncStatisticsTest.java @@ -1,27 +1,28 @@ package com.commercetools.sync.shoppinglists.helpers; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - class ShoppingListSyncStatisticsTest { - private ShoppingListSyncStatistics shoppingListSyncStatistics; + private ShoppingListSyncStatistics shoppingListSyncStatistics; - @BeforeEach - void setup() { - shoppingListSyncStatistics = new ShoppingListSyncStatistics(); - } + @BeforeEach + void setup() { + shoppingListSyncStatistics = new ShoppingListSyncStatistics(); + } - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - shoppingListSyncStatistics.incrementCreated(1); - shoppingListSyncStatistics.incrementFailed(2); - shoppingListSyncStatistics.incrementUpdated(3); - shoppingListSyncStatistics.incrementProcessed(6); + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + shoppingListSyncStatistics.incrementCreated(1); + shoppingListSyncStatistics.incrementFailed(2); + shoppingListSyncStatistics.incrementUpdated(3); + shoppingListSyncStatistics.incrementProcessed(6); - assertThat(shoppingListSyncStatistics.getReportMessage()) - .isEqualTo("Summary: 6 shopping lists were processed in total " + assertThat(shoppingListSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 6 shopping lists were processed in total " + "(1 created, 3 updated and 2 failed to sync)."); - } + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/helpers/TextLineItemReferenceResolverTest.java b/src/test/java/com/commercetools/sync/shoppinglists/helpers/TextLineItemReferenceResolverTest.java index 04852f636b..979f2cbbf6 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/helpers/TextLineItemReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/helpers/TextLineItemReferenceResolverTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.shoppinglists.helpers; +import static com.commercetools.sync.commons.MockUtils.getMockTypeService; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; +import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; +import static com.commercetools.sync.shoppinglists.helpers.TextLineItemReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.services.TypeService; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; @@ -11,146 +21,129 @@ import io.sphere.sdk.shoppinglists.TextLineItemDraftBuilder; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.utils.CompletableFutureUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.Locale; import java.util.Optional; import java.util.concurrent.CompletableFuture; - -import static com.commercetools.sync.commons.MockUtils.getMockTypeService; -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER; -import static com.commercetools.sync.commons.helpers.CustomReferenceResolver.TYPE_DOES_NOT_EXIST; -import static com.commercetools.sync.shoppinglists.helpers.TextLineItemReferenceResolver.FAILED_TO_RESOLVE_CUSTOM_TYPE; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class TextLineItemReferenceResolverTest { - private TypeService typeService; - - private TextLineItemReferenceResolver referenceResolver; - - /** - * Sets up the services and the options needed for reference resolution. - */ - @BeforeEach - void setup() { - typeService = getMockTypeService(); - - final ShoppingListSyncOptions syncOptions = ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - referenceResolver = new TextLineItemReferenceResolver(syncOptions, typeService); - } - - @Test - void resolveReferences_WithCustomTypeId_ShouldNotResolveCustomTypeReferenceWithKey() { - final String customTypeId = "customTypeId"; - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeIdAndJson(customTypeId, new HashMap<>()); - - final TextLineItemDraft textLineItemDraft = - TextLineItemDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) - .custom(customFieldsDraft) - .build(); - - final TextLineItemDraft resolvedDraft = referenceResolver - .resolveReferences(textLineItemDraft) - .toCompletableFuture() - .join(); - - assertThat(resolvedDraft.getCustom()).isNotNull(); - assertThat(resolvedDraft.getCustom()).isEqualTo(customFieldsDraft); - } - - @Test - void resolveReferences_WithNonNullKeyOnCustomTypeResId_ShouldResolveCustomTypeReference() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("customTypeKey", new HashMap<>()); - - final TextLineItemDraft textLineItemDraft = - TextLineItemDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) - .custom(customFieldsDraft) - .build(); - - final TextLineItemDraft resolvedDraft = referenceResolver - .resolveReferences(textLineItemDraft) - .toCompletableFuture() - .join(); - - assertThat(resolvedDraft.getCustom()).isNotNull(); - assertThat(resolvedDraft.getCustom().getType().getId()).isEqualTo("typeId"); - } - - @Test - void resolveReferences_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFutureUtils.failed(new SphereException("CTP error on fetch"))); - - final String customTypeKey = "customTypeKey"; - final CustomFieldsDraft customFieldsDraft = - CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); - - final TextLineItemDraft textLineItemDraft = - TextLineItemDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) - .custom(customFieldsDraft) - .build(); - - assertThat(referenceResolver.resolveReferences(textLineItemDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveReferences_WithNonExistentCustomType_ShouldCompleteExceptionally() { - final String customTypeKey = "customTypeKey"; - final CustomFieldsDraft customFieldsDraft = - CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); - - final TextLineItemDraft textLineItemDraft = - TextLineItemDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) - .custom(customFieldsDraft) - .build(); - - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - - final String expectedExceptionMessage = format(FAILED_TO_RESOLVE_CUSTOM_TYPE, textLineItemDraft.getName()); - - final String expectedMessageWithCause = - format("%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); - - assertThat(referenceResolver.resolveReferences(textLineItemDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(expectedMessageWithCause); - } - - @Test - void resolveReferences_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { - final CustomFieldsDraft customFieldsDraft = CustomFieldsDraft - .ofTypeKeyAndJson("", new HashMap<>()); - - final TextLineItemDraft textLineItemDraft = - TextLineItemDraftBuilder - .of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) - .custom(customFieldsDraft) - .build(); - - assertThat(referenceResolver.resolveReferences(textLineItemDraft)) - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve custom type reference on TextLineItemDraft" - + " with name: '%s'. Reason: %s", LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), + private TypeService typeService; + + private TextLineItemReferenceResolver referenceResolver; + + /** Sets up the services and the options needed for reference resolution. */ + @BeforeEach + void setup() { + typeService = getMockTypeService(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + referenceResolver = new TextLineItemReferenceResolver(syncOptions, typeService); + } + + @Test + void resolveReferences_WithCustomTypeId_ShouldNotResolveCustomTypeReferenceWithKey() { + final String customTypeId = "customTypeId"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson(customTypeId, new HashMap<>()); + + final TextLineItemDraft textLineItemDraft = + TextLineItemDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) + .custom(customFieldsDraft) + .build(); + + final TextLineItemDraft resolvedDraft = + referenceResolver.resolveReferences(textLineItemDraft).toCompletableFuture().join(); + + assertThat(resolvedDraft.getCustom()).isNotNull(); + assertThat(resolvedDraft.getCustom()).isEqualTo(customFieldsDraft); + } + + @Test + void resolveReferences_WithNonNullKeyOnCustomTypeResId_ShouldResolveCustomTypeReference() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("customTypeKey", new HashMap<>()); + + final TextLineItemDraft textLineItemDraft = + TextLineItemDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) + .custom(customFieldsDraft) + .build(); + + final TextLineItemDraft resolvedDraft = + referenceResolver.resolveReferences(textLineItemDraft).toCompletableFuture().join(); + + assertThat(resolvedDraft.getCustom()).isNotNull(); + assertThat(resolvedDraft.getCustom().getType().getId()).isEqualTo("typeId"); + } + + @Test + void resolveReferences_WithExceptionOnCustomTypeFetch_ShouldNotResolveReferences() { + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFutureUtils.failed(new SphereException("CTP error on fetch"))); + + final String customTypeKey = "customTypeKey"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); + + final TextLineItemDraft textLineItemDraft = + TextLineItemDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) + .custom(customFieldsDraft) + .build(); + + assertThat(referenceResolver.resolveReferences(textLineItemDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveReferences_WithNonExistentCustomType_ShouldCompleteExceptionally() { + final String customTypeKey = "customTypeKey"; + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson(customTypeKey, new HashMap<>()); + + final TextLineItemDraft textLineItemDraft = + TextLineItemDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) + .custom(customFieldsDraft) + .build(); + + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(CompletableFuture.completedFuture(Optional.empty())); + + final String expectedExceptionMessage = + format(FAILED_TO_RESOLVE_CUSTOM_TYPE, textLineItemDraft.getName()); + + final String expectedMessageWithCause = + format( + "%s Reason: %s", expectedExceptionMessage, format(TYPE_DOES_NOT_EXIST, customTypeKey)); + + assertThat(referenceResolver.resolveReferences(textLineItemDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage(expectedMessageWithCause); + } + + @Test + void resolveReferences_WithEmptyKeyOnCustomTypeResId_ShouldCompleteExceptionally() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraft.ofTypeKeyAndJson("", new HashMap<>()); + + final TextLineItemDraft textLineItemDraft = + TextLineItemDraftBuilder.of(LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), 10L) + .custom(customFieldsDraft) + .build(); + + assertThat(referenceResolver.resolveReferences(textLineItemDraft)) + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve custom type reference on TextLineItemDraft" + + " with name: '%s'. Reason: %s", + LocalizedString.of(Locale.ENGLISH, "dummy-custom-key"), BLANK_KEY_VALUE_ON_RESOURCE_IDENTIFIER)); - - } + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/utils/LineItemListUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/shoppinglists/utils/LineItemListUpdateActionUtilsTest.java index 11b81e9586..30f0aa3202 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/utils/LineItemListUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/utils/LineItemListUpdateActionUtilsTest.java @@ -1,5 +1,20 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; +import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildLineItemsUpdateActions; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListSyncUtils.buildActions; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.services.TypeService; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptionsBuilder; @@ -29,564 +44,573 @@ import io.sphere.sdk.shoppinglists.commands.updateactions.SetLineItemCustomField; import io.sphere.sdk.shoppinglists.commands.updateactions.SetSlug; import io.sphere.sdk.types.CustomFieldsDraft; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; -import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildLineItemsUpdateActions; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListSyncUtils.buildActions; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class LineItemListUpdateActionUtilsTest { - private static final String RES_ROOT = "com/commercetools/sync/shoppinglists/utils/lineitems/"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123 = - RES_ROOT + "shoppinglist-with-lineitems-sku-123.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123_WITH_CHANGES = - RES_ROOT + "shoppinglist-with-lineitems-sku-123-with-changes.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_12 = - RES_ROOT + "shoppinglist-with-lineitems-sku-12.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1234 = - RES_ROOT + "shoppinglist-with-lineitems-sku-1234.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_124 = - RES_ROOT + "shoppinglist-with-lineitems-sku-124.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_312 = - RES_ROOT + "shoppinglist-with-lineitems-sku-312.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_32 = - RES_ROOT + "shoppinglist-with-lineitems-sku-32.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1324 = - RES_ROOT + "shoppinglist-with-lineitems-sku-1324.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1423 = - RES_ROOT + "shoppinglist-with-lineitems-sku-1423.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_324 = - RES_ROOT + "shoppinglist-with-lineitems-sku-324.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES = - RES_ROOT + "shoppinglist-with-lineitems-sku-132-with-changes.json"; - private static final String SHOPPING_LIST_WITH_LINE_ITEMS_NOT_EXPANDED = - RES_ROOT + "shoppinglist-with-lineitems-not-expanded.json"; - - private static final ShoppingListSyncOptions SYNC_OPTIONS = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - private static final LineItemReferenceResolver lineItemReferenceResolver = - new LineItemReferenceResolver(SYNC_OPTIONS, getMockTypeService()); - - @Test - void buildLineItemsUpdateActions_WithoutNewAndWithNullOldLineItems_ShouldNotBuildActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getLineItems()).thenReturn(emptyList()); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .build(); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildLineItemsUpdateActions_WithNullNewAndNullOldLineItems_ShouldNotBuildActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getLineItems()).thenReturn(null); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .build(); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildLineItemsUpdateActions_WithNullOldAndEmptyNewLineItems_ShouldNotBuildActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getLineItems()).thenReturn(null); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .lineItems(emptyList()) - .build(); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildLineItemsUpdateActions_WithNullOldAndNewLineItemsWithNullElement_ShouldNotBuildActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getLineItems()).thenReturn(null); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .lineItems(singletonList(null)) - .build(); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildLineItemsUpdateActions_WithNullNewLineItemsAndExistingLineItems_ShouldBuild3RemoveActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .build(); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( + private static final String RES_ROOT = "com/commercetools/sync/shoppinglists/utils/lineitems/"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123 = + RES_ROOT + "shoppinglist-with-lineitems-sku-123.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123_WITH_CHANGES = + RES_ROOT + "shoppinglist-with-lineitems-sku-123-with-changes.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_12 = + RES_ROOT + "shoppinglist-with-lineitems-sku-12.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1234 = + RES_ROOT + "shoppinglist-with-lineitems-sku-1234.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_124 = + RES_ROOT + "shoppinglist-with-lineitems-sku-124.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_312 = + RES_ROOT + "shoppinglist-with-lineitems-sku-312.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_32 = + RES_ROOT + "shoppinglist-with-lineitems-sku-32.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1324 = + RES_ROOT + "shoppinglist-with-lineitems-sku-1324.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1423 = + RES_ROOT + "shoppinglist-with-lineitems-sku-1423.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_324 = + RES_ROOT + "shoppinglist-with-lineitems-sku-324.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES = + RES_ROOT + "shoppinglist-with-lineitems-sku-132-with-changes.json"; + private static final String SHOPPING_LIST_WITH_LINE_ITEMS_NOT_EXPANDED = + RES_ROOT + "shoppinglist-with-lineitems-not-expanded.json"; + + private static final ShoppingListSyncOptions SYNC_OPTIONS = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + private static final LineItemReferenceResolver lineItemReferenceResolver = + new LineItemReferenceResolver(SYNC_OPTIONS, getMockTypeService()); + + @Test + void buildLineItemsUpdateActions_WithoutNewAndWithNullOldLineItems_ShouldNotBuildActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getLineItems()).thenReturn(emptyList()); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")).build(); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildLineItemsUpdateActions_WithNullNewAndNullOldLineItems_ShouldNotBuildActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getLineItems()).thenReturn(null); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")).build(); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildLineItemsUpdateActions_WithNullOldAndEmptyNewLineItems_ShouldNotBuildActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getLineItems()).thenReturn(null); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) + .lineItems(emptyList()) + .build(); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildLineItemsUpdateActions_WithNullOldAndNewLineItemsWithNullElement_ShouldNotBuildActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getLineItems()).thenReturn(null); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) + .lineItems(singletonList(null)) + .build(); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildLineItemsUpdateActions_WithNullNewLineItemsAndExistingLineItems_ShouldBuild3RemoveActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")).build(); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveLineItem.of("line_item_id_1"), RemoveLineItem.of("line_item_id_2"), RemoveLineItem.of("line_item_id_3")); - } + } - @Test - void buildLineItemsUpdateActions_WithNewLineItemsAndNoOldLineItems_ShouldBuild3AddActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getLineItems()).thenReturn(null); + @Test + void buildLineItemsUpdateActions_WithNewLineItemsAndNoOldLineItems_ShouldBuild3AddActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getLineItems()).thenReturn(null); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(newShoppingList.getLineItems()).isNotNull(); + assertThat(newShoppingList.getLineItems()).isNotNull(); - assertThat(updateActions) - .hasSize(3) - .extracting("sku") - .asString() - .isEqualTo("[SKU-1, SKU-2, SKU-3]"); + assertThat(updateActions) + .hasSize(3) + .extracting("sku") + .asString() + .isEqualTo("[SKU-1, SKU-2, SKU-3]"); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( AddLineItemWithSku.of(newShoppingList.getLineItems().get(0)), AddLineItemWithSku.of(newShoppingList.getLineItems().get(1)), - AddLineItemWithSku.of(newShoppingList.getLineItems().get(2)) - ); - } - - @Test - void buildLineItemsUpdateActions_WithOnlyNewLineItemsWithoutValidQuantity_ShouldNotBuildAddActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getLineItems()).thenReturn(null); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("name")) - .lineItems(asList( + AddLineItemWithSku.of(newShoppingList.getLineItems().get(2))); + } + + @Test + void + buildLineItemsUpdateActions_WithOnlyNewLineItemsWithoutValidQuantity_ShouldNotBuildAddActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getLineItems()).thenReturn(null); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) + .lineItems( + asList( LineItemDraftBuilder.ofSku("sku1", null).build(), LineItemDraftBuilder.ofSku("sku2", 0L).build(), - LineItemDraftBuilder.ofSku("sku3", -1L).build() - )) - .build(); + LineItemDraftBuilder.ofSku("sku3", -1L).build())) + .build(); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildLineItemsUpdateActions_WithIdenticalLineItems_ShouldNotBuildUpdateActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + @Test + void buildLineItemsUpdateActions_WithIdenticalLineItems_ShouldNotBuildUpdateActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildLineItemsUpdateActions_WithSameLineItemPositionButChangesWithin_ShouldBuildUpdateActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + @Test + void + buildLineItemsUpdateActions_WithSameLineItemPositionButChangesWithin_ShouldBuildUpdateActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123_WITH_CHANGES); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences( + SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123_WITH_CHANGES); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( ChangeLineItemQuantity.of("line_item_id_1", 2L), - SetLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText1"), "line_item_id_1"), - + SetLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText1"), "line_item_id_1"), ChangeLineItemQuantity.of("line_item_id_2", 4L), - SetLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText2"), "line_item_id_2"), - + SetLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText2"), "line_item_id_2"), ChangeLineItemQuantity.of("line_item_id_3", 6L), - SetLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText3"), "line_item_id_3") - ); - } - - @Test - void buildLineItemsUpdateActions_WithOneMissingLineItem_ShouldBuildOneRemoveLineItemAction() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_12); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( - RemoveLineItem.of("line_item_id_3") - ); - } - - @Test - void buildLineItemsUpdateActions_WithOneExtraLineItem_ShouldBuildAddLineItemAction() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1234); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - final LineItemDraft expectedLineItemDraft = - LineItemDraftBuilder.ofSku("SKU-4", 4L) - .custom(CustomFieldsDraft.ofTypeIdAndJson("custom_type_id", - singletonMap("textField", - JsonNodeFactory.instance.textNode("text4")))) - .addedAt(ZonedDateTime.parse("2020-11-05T10:00:00.000Z")) - .build(); - - assertThat(updateActions).containsExactly( - AddLineItemWithSku.of(expectedLineItemDraft) - ); - } - - @Test - void buildLineItemsUpdateAction_WithOneLineItemSwitch_ShouldBuildRemoveAndAddLineItemActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_124); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - final LineItemDraft expectedLineItemDraft = - LineItemDraftBuilder.ofSku("SKU-4", 4L) - .custom(CustomFieldsDraft.ofTypeIdAndJson("custom_type_id", - singletonMap("textField", - JsonNodeFactory.instance.textNode("text4")))) - .addedAt(ZonedDateTime.parse("2020-11-05T10:00:00.000Z")) - .build(); - - assertThat(updateActions).containsExactly( - RemoveLineItem.of("line_item_id_3"), - AddLineItemWithSku.of(expectedLineItemDraft) - ); - } - - @Test - void buildLineItemsUpdateAction_WithDifferentOrder_ShouldBuildRemoveAndAddLineItemActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_312); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( + SetLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText3"), "line_item_id_3")); + } + + @Test + void buildLineItemsUpdateActions_WithOneMissingLineItem_ShouldBuildOneRemoveLineItemAction() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_12); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + assertThat(updateActions).containsExactly(RemoveLineItem.of("line_item_id_3")); + } + + @Test + void buildLineItemsUpdateActions_WithOneExtraLineItem_ShouldBuildAddLineItemAction() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences( + SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1234); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + final LineItemDraft expectedLineItemDraft = + LineItemDraftBuilder.ofSku("SKU-4", 4L) + .custom( + CustomFieldsDraft.ofTypeIdAndJson( + "custom_type_id", + singletonMap("textField", JsonNodeFactory.instance.textNode("text4")))) + .addedAt(ZonedDateTime.parse("2020-11-05T10:00:00.000Z")) + .build(); + + assertThat(updateActions).containsExactly(AddLineItemWithSku.of(expectedLineItemDraft)); + } + + @Test + void buildLineItemsUpdateAction_WithOneLineItemSwitch_ShouldBuildRemoveAndAddLineItemActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_124); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + final LineItemDraft expectedLineItemDraft = + LineItemDraftBuilder.ofSku("SKU-4", 4L) + .custom( + CustomFieldsDraft.ofTypeIdAndJson( + "custom_type_id", + singletonMap("textField", JsonNodeFactory.instance.textNode("text4")))) + .addedAt(ZonedDateTime.parse("2020-11-05T10:00:00.000Z")) + .build(); + + assertThat(updateActions) + .containsExactly( + RemoveLineItem.of("line_item_id_3"), AddLineItemWithSku.of(expectedLineItemDraft)); + } + + @Test + void buildLineItemsUpdateAction_WithDifferentOrder_ShouldBuildRemoveAndAddLineItemActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_312); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveLineItem.of("line_item_id_1"), RemoveLineItem.of("line_item_id_2"), RemoveLineItem.of("line_item_id_3"), AddLineItemWithSku.of(newShoppingList.getLineItems().get(0)), // SKU-3 AddLineItemWithSku.of(newShoppingList.getLineItems().get(1)), // SKU-1 - AddLineItemWithSku.of(newShoppingList.getLineItems().get(2)) // SKU-2 - ); - } + AddLineItemWithSku.of(newShoppingList.getLineItems().get(2)) // SKU-2 + ); + } - @Test - void buildLineItemsUpdateAction_WithRemovedAndDifferentOrder_ShouldBuildRemoveAndAddLineItemActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + @Test + void + buildLineItemsUpdateAction_WithRemovedAndDifferentOrder_ShouldBuildRemoveAndAddLineItemActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_32); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_32); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( RemoveLineItem.of("line_item_id_1"), RemoveLineItem.of("line_item_id_2"), RemoveLineItem.of("line_item_id_3"), AddLineItemWithSku.of(newShoppingList.getLineItems().get(0)), // SKU-3 AddLineItemWithSku.of(newShoppingList.getLineItems().get(1)) // SKU-2 - ); - } + ); + } - @Test - void buildLineItemsUpdateAction_WithAddedAndDifferentOrder_ShouldBuildRemoveAndAddLineItemActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + @Test + void + buildLineItemsUpdateAction_WithAddedAndDifferentOrder_ShouldBuildRemoveAndAddLineItemActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1324); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences( + SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1324); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( RemoveLineItem.of("line_item_id_2"), RemoveLineItem.of("line_item_id_3"), AddLineItemWithSku.of(newShoppingList.getLineItems().get(1)), // SKU-3 AddLineItemWithSku.of(newShoppingList.getLineItems().get(2)), // SKU-2 AddLineItemWithSku.of(newShoppingList.getLineItems().get(3)) // SKU-4 - ); - } + ); + } - @Test - void buildLineItemsUpdateAction_WithAddedLineItemInBetween_ShouldBuildRemoveAndAddLineItemActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + @Test + void + buildLineItemsUpdateAction_WithAddedLineItemInBetween_ShouldBuildRemoveAndAddLineItemActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1423); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences( + SHOPPING_LIST_WITH_LINE_ITEMS_SKU_1423); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( RemoveLineItem.of("line_item_id_2"), RemoveLineItem.of("line_item_id_3"), AddLineItemWithSku.of(newShoppingList.getLineItems().get(1)), // SKU-4 AddLineItemWithSku.of(newShoppingList.getLineItems().get(2)), // SKU-2 AddLineItemWithSku.of(newShoppingList.getLineItems().get(3)) // SKU-3 - ); - } + ); + } - @Test - void buildLineItemsUpdateAction_WithAddedRemovedAndDifOrder_ShouldBuildRemoveAndAddLineItemActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + @Test + void + buildLineItemsUpdateAction_WithAddedRemovedAndDifOrder_ShouldBuildRemoveAndAddLineItemActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_324); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_324); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( RemoveLineItem.of("line_item_id_1"), RemoveLineItem.of("line_item_id_2"), RemoveLineItem.of("line_item_id_3"), AddLineItemWithSku.of(newShoppingList.getLineItems().get(0)), // SKU-3 AddLineItemWithSku.of(newShoppingList.getLineItems().get(1)), // SKU-2 AddLineItemWithSku.of(newShoppingList.getLineItems().get(2)) // SKU-4 - ); - } + ); + } - @Test - void buildLineItemsUpdateAction_WithAddedRemovedAndDifOrderAndChangedLineItem_ShouldBuildAllDiffLineItemActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + @Test + void + buildLineItemsUpdateAction_WithAddedRemovedAndDifOrderAndChangedLineItem_ShouldBuildAllDiffLineItemActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences( + SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( ChangeLineItemQuantity.of("line_item_id_1", 2L), - SetLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText1"), "line_item_id_1"), + SetLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText1"), "line_item_id_1"), RemoveLineItem.of("line_item_id_2"), RemoveLineItem.of("line_item_id_3"), AddLineItemWithSku.of(newShoppingList.getLineItems().get(1)), // SKU-3 AddLineItemWithSku.of(newShoppingList.getLineItems().get(2)) // SKU-2 - ); - } - - @Test - void buildLineItemsUpdateAction_WithNotExpandedLineItem_ShouldTriggerErrorCallback() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_NOT_EXPANDED, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES); - - - final List errors = new ArrayList<>(); - final List> updateActionsBeforeCallback = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errors.add(exception.getMessage()); - updateActionsBeforeCallback.addAll(updateActions); - }) - .build(); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); - - assertThat(updateActions).isEmpty(); - assertThat(updateActionsBeforeCallback).containsExactly( + ); + } + + @Test + void buildLineItemsUpdateAction_WithNotExpandedLineItem_ShouldTriggerErrorCallback() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_NOT_EXPANDED, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences( + SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES); + + final List errors = new ArrayList<>(); + final List> updateActionsBeforeCallback = new ArrayList<>(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errors.add(exception.getMessage()); + updateActionsBeforeCallback.addAll(updateActions); + }) + .build(); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(updateActionsBeforeCallback) + .containsExactly( ChangeLineItemQuantity.of("line_item_id_1", 2L), - SetLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText1"), "line_item_id_1")); + SetLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText1"), "line_item_id_1")); - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo("LineItem at position '1' of the ShoppingList with key " + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + "LineItem at position '1' of the ShoppingList with key " + "'shoppinglist-with-lineitems-not-expanded' has no SKU set. " + "Please make sure all line items have SKUs."); - } + } - @Test - void buildLineItemsUpdateAction_WithOldLineItemWithoutSku_ShouldTriggerErrorCallback() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getKey()).thenReturn("shoppinglist-with-lineitems-not-expanded"); + @Test + void buildLineItemsUpdateAction_WithOldLineItemWithoutSku_ShouldTriggerErrorCallback() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getKey()).thenReturn("shoppinglist-with-lineitems-not-expanded"); - final ProductVariant mockProductVariant = mock(ProductVariant.class); - when(mockProductVariant.getSku()).thenReturn(null); + final ProductVariant mockProductVariant = mock(ProductVariant.class); + when(mockProductVariant.getSku()).thenReturn(null); - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getVariant()).thenReturn(mockProductVariant); + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getVariant()).thenReturn(mockProductVariant); - when(oldShoppingList.getLineItems()).thenReturn(singletonList(oldLineItem)); + when(oldShoppingList.getLineItems()).thenReturn(singletonList(oldLineItem)); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123); - final List errors = new ArrayList<>(); - final List> updateActionsBeforeCallback = new ArrayList<>(); + final List errors = new ArrayList<>(); + final List> updateActionsBeforeCallback = new ArrayList<>(); - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errors.add(exception.getMessage()); - updateActionsBeforeCallback.addAll(updateActions); - }) - .build(); + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errors.add(exception.getMessage()); + updateActionsBeforeCallback.addAll(updateActions); + }) + .build(); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); - assertThat(updateActions).isEmpty(); - assertThat(updateActionsBeforeCallback).isEmpty(); + assertThat(updateActions).isEmpty(); + assertThat(updateActionsBeforeCallback).isEmpty(); - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo("LineItem at position '0' of the ShoppingList with key " + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + "LineItem at position '0' of the ShoppingList with key " + "'shoppinglist-with-lineitems-not-expanded' has no SKU set. " + "Please make sure all line items have SKUs."); - } - - @Test - void buildLineItemsUpdateAction_WithNewLineItemWithoutSku_ShouldTriggerErrorCallback() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_NOT_EXPANDED); - - final List errors = new ArrayList<>(); - final List> updateActionsBeforeCallback = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errors.add(exception.getMessage()); - updateActionsBeforeCallback.addAll(updateActions); - }) - .build(); - - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); - - assertThat(updateActions).isEmpty(); - assertThat(updateActionsBeforeCallback).containsExactly( + } + + @Test + void buildLineItemsUpdateAction_WithNewLineItemWithoutSku_ShouldTriggerErrorCallback() { + final ShoppingList oldShoppingList = + readObjectFromResource( + SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences( + SHOPPING_LIST_WITH_LINE_ITEMS_NOT_EXPANDED); + + final List errors = new ArrayList<>(); + final List> updateActionsBeforeCallback = new ArrayList<>(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errors.add(exception.getMessage()); + updateActionsBeforeCallback.addAll(updateActions); + }) + .build(); + + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(updateActionsBeforeCallback) + .containsExactly( ChangeLineItemQuantity.of("line_item_id_1", 1L), - SetLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("text1"), "line_item_id_1")); + SetLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("text1"), "line_item_id_1")); - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo("LineItemDraft at position '1' of the ShoppingListDraft with key " + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + "LineItemDraft at position '1' of the ShoppingListDraft with key " + "'shoppinglist-with-lineitems-not-expanded' has no SKU set. " + "Please make sure all line item drafts have SKUs."); - } + } - @Test - void buildLineItemsUpdateActions_WithNewLineItemsWithoutValidQuantity_ShouldNotBuildAddActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + @Test + void buildLineItemsUpdateActions_WithNewLineItemsWithoutValidQuantity_ShouldNotBuildAddActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123); - Objects.requireNonNull(newShoppingList.getLineItems()).addAll(Arrays.asList( - LineItemDraftBuilder.ofSku("sku1", null).build(), - LineItemDraftBuilder.ofSku("sku2", 0L).build(), - LineItemDraftBuilder.ofSku("sku3", -1L).build() - )); + Objects.requireNonNull(newShoppingList.getLineItems()) + .addAll( + Arrays.asList( + LineItemDraftBuilder.ofSku("sku1", null).build(), + LineItemDraftBuilder.ofSku("sku2", 0L).build(), + LineItemDraftBuilder.ofSku("sku3", -1L).build())); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildActions_WithDifferentValuesWithLineItems_ShouldReturnActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); + @Test + void buildActions_WithDifferentValuesWithLineItems_ShouldReturnActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedLineItemReferences(SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedLineItemReferences( + SHOPPING_LIST_WITH_LINE_ITEMS_SKU_132_WITH_CHANGES); - final List> updateActions = - buildActions(oldShoppingList, newShoppingList, mock(ShoppingListSyncOptions.class)); + final List> updateActions = + buildActions(oldShoppingList, newShoppingList, mock(ShoppingListSyncOptions.class)); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( SetSlug.of(LocalizedString.ofEnglish("newSlug")), ChangeName.of(LocalizedString.ofEnglish("newName")), SetDescription.of(LocalizedString.ofEnglish("newDescription")), @@ -595,51 +619,55 @@ void buildActions_WithDifferentValuesWithLineItems_ShouldReturnActions() { SetDeleteDaysAfterLastModification.of(45), SetCustomField.ofJson("textField", JsonNodeFactory.instance.textNode("newTextValue")), ChangeLineItemQuantity.of("line_item_id_1", 2L), - SetLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText1"), "line_item_id_1"), - + SetLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText1"), "line_item_id_1"), RemoveLineItem.of("line_item_id_2"), RemoveLineItem.of("line_item_id_3"), - AddLineItemWithSku.of(LineItemDraftBuilder - .ofSku("SKU-3", 6L) - .custom(CustomFieldsDraft.ofTypeIdAndJson("custom_type_id", - singletonMap("textField", - JsonNodeFactory.instance.textNode("newText3")))) - .addedAt(ZonedDateTime.parse("2020-11-04T10:00:00.000Z")) - .build()), - AddLineItemWithSku.of(LineItemDraftBuilder - .ofSku("SKU-2", 4L) - .custom(CustomFieldsDraft.ofTypeIdAndJson("custom_type_id", - singletonMap("textField", - JsonNodeFactory.instance.textNode("newText2")))) - .addedAt(ZonedDateTime.parse("2020-11-03T10:00:00.000Z")) - .build()) - ); - } - - @Nonnull - private static ShoppingListDraft mapToShoppingListDraftWithResolvedLineItemReferences( - @Nonnull final String resourcePath) { - - final ShoppingListDraft template = - ShoppingListReferenceResolutionUtils.mapToShoppingListDraft( - readObjectFromResource(resourcePath, ShoppingList.class)); - - final ShoppingListDraftBuilder builder = ShoppingListDraftBuilder.of(template); - - mapValuesToFutureOfCompletedValues( - Objects.requireNonNull(builder.getLineItems()), lineItemReferenceResolver::resolveReferences, toList()) - .thenApply(builder::lineItems) - .join(); - - return builder.build(); - } - - @Nonnull - private static TypeService getMockTypeService() { - final TypeService typeService = mock(TypeService.class); - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(completedFuture(Optional.of("custom_type_id"))); - return typeService; - } + AddLineItemWithSku.of( + LineItemDraftBuilder.ofSku("SKU-3", 6L) + .custom( + CustomFieldsDraft.ofTypeIdAndJson( + "custom_type_id", + singletonMap( + "textField", JsonNodeFactory.instance.textNode("newText3")))) + .addedAt(ZonedDateTime.parse("2020-11-04T10:00:00.000Z")) + .build()), + AddLineItemWithSku.of( + LineItemDraftBuilder.ofSku("SKU-2", 4L) + .custom( + CustomFieldsDraft.ofTypeIdAndJson( + "custom_type_id", + singletonMap( + "textField", JsonNodeFactory.instance.textNode("newText2")))) + .addedAt(ZonedDateTime.parse("2020-11-03T10:00:00.000Z")) + .build())); + } + + @Nonnull + private static ShoppingListDraft mapToShoppingListDraftWithResolvedLineItemReferences( + @Nonnull final String resourcePath) { + + final ShoppingListDraft template = + ShoppingListReferenceResolutionUtils.mapToShoppingListDraft( + readObjectFromResource(resourcePath, ShoppingList.class)); + + final ShoppingListDraftBuilder builder = ShoppingListDraftBuilder.of(template); + + mapValuesToFutureOfCompletedValues( + Objects.requireNonNull(builder.getLineItems()), + lineItemReferenceResolver::resolveReferences, + toList()) + .thenApply(builder::lineItems) + .join(); + + return builder.build(); + } + + @Nonnull + private static TypeService getMockTypeService() { + final TypeService typeService = mock(TypeService.class); + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(completedFuture(Optional.of("custom_type_id"))); + return typeService; + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/utils/LineItemUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/shoppinglists/utils/LineItemUpdateActionUtilsTest.java index dd26aad8e5..11fda36d7b 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/utils/LineItemUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/utils/LineItemUpdateActionUtilsTest.java @@ -1,5 +1,13 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildChangeLineItemQuantityUpdateAction; +import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildLineItemCustomUpdateActions; +import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildLineItemUpdateActions; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -17,8 +25,6 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; @@ -26,420 +32,413 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; - -import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildChangeLineItemQuantityUpdateAction; -import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildLineItemCustomUpdateActions; -import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildLineItemUpdateActions; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class LineItemUpdateActionUtilsTest { - private static final ShoppingListSyncOptions SYNC_OPTIONS = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - - final ShoppingList oldShoppingList = mock(ShoppingList.class); - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - - @Test - void buildLineItemCustomUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val")); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); - - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getCustom()).thenReturn(oldCustomFields); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 1L) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = - buildLineItemCustomUpdateActions(oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildLineItemCustomUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getCustom()).thenReturn(oldCustomFields); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 1L) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = - buildLineItemCustomUpdateActions(oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( - SetLineItemCustomField.ofJson("field1", - JsonNodeFactory.instance.booleanNode(false), "line_item_id"), - SetLineItemCustomField.ofJson("field2", - JsonNodeFactory.instance.objectNode().put("es", "val2"), "line_item_id") - ); - } - - @Test - void buildLineItemCustomUpdateActions_WithNullOldValues_ShouldBuildUpdateAction() { - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val")); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getCustom()).thenReturn(null); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 1L) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = - buildLineItemCustomUpdateActions(oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( - SetLineItemCustomType.ofTypeIdAndJson("1", newCustomFieldsMap, "line_item_id") - ); - } - - @Test - void buildLineItemCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerErrorCallback() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); - - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getCustom()).thenReturn(oldCustomFields); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 1L) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errors.add(exception.getMessage())) - .build(); - - final List> updateActions = - buildLineItemCustomUpdateActions(oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions); - - assertThat(updateActions).isEmpty(); - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo(format("Failed to build custom fields update actions on the line-item with id '%s'." - + " Reason: Custom type ids are not set for both the old and new line-item.", oldLineItem.getId())); - } - - @Test - void buildLineItemCustomUpdateActions_WithNullValue_ShouldCorrectlyBuildAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - newCustomFieldsMap.put("field2", null); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - final String typeId = UUID.randomUUID().toString(); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); - - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getCustom()).thenReturn(oldCustomFields); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 1L) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errors.add(exception.getMessage())) - .build(); - - final List> updateActions = - buildLineItemCustomUpdateActions(oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions); - - assertThat(errors).isEmpty(); - assertThat(updateActions) - .containsExactly(SetLineItemCustomField.ofJson("field2", null, "line_item_id")); - } - - @Test - void buildLineItemCustomUpdateActions_WithNullJsonNodeValue_ShouldCorrectlyBuildAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field", JsonNodeFactory.instance.nullNode()); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - final String typeId = UUID.randomUUID().toString(); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); - - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getCustom()).thenReturn(oldCustomFields); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 1L) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errors.add(exception.getMessage())) - .build(); - - final List> updateActions = - buildLineItemCustomUpdateActions(oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions); - - assertThat(errors).isEmpty(); - assertThat(updateActions) - .containsExactly(SetLineItemCustomField.ofJson("field", null, "line_item_id")); - } - - @Test - void buildChangeLineItemQuantityUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getQuantity()).thenReturn(2L); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 2L) - .addedAt(ZonedDateTime.now()) - .build(); - - final Optional> updateAction = - buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isNotPresent(); - } - - @Test - void buildChangeLineItemQuantityUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getQuantity()).thenReturn(2L); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 4L) - .addedAt(ZonedDateTime.now()) - .build(); - - final UpdateAction updateAction = - buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("changeLineItemQuantity"); - assertThat((ChangeLineItemQuantity) updateAction) - .isEqualTo(ChangeLineItemQuantity.of("line_item_id", 4L)); - } - - @Test - void buildChangeLineItemQuantityUpdateAction_WithNewNullValue_ShouldBuildUpdateAction() { - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getQuantity()).thenReturn(2L); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", null) - .addedAt(ZonedDateTime.now()) - .build(); - - final UpdateAction updateAction = - buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("changeLineItemQuantity"); - assertThat((ChangeLineItemQuantity) updateAction) - .isEqualTo(ChangeLineItemQuantity.of("line_item_id", 1L)); - } - - @Test - void buildChangeLineItemQuantityUpdateAction_WithNewZeroValue_ShouldBuildUpdateAction() { - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getQuantity()).thenReturn(2L); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", null) - .quantity(0L) - .addedAt(ZonedDateTime.now()) - .build(); - - final UpdateAction updateAction = - buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("changeLineItemQuantity"); - assertThat((ChangeLineItemQuantity) updateAction) - .isEqualTo(ChangeLineItemQuantity.of("line_item_id", 0L)); - } - - @Test - void buildChangeLineItemQuantityUpdateAction_WithNewNullValueAndOldDefaultValue_ShouldNotBuildAction() { - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getQuantity()).thenReturn(1L); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", null) - .addedAt(ZonedDateTime.now()) - .build(); - - final Optional> updateAction = - buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem); - - assertThat(updateAction).isNotPresent(); - } - - @Test - void buildLineItemUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val")); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); - - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getQuantity()).thenReturn(1L); - when(oldLineItem.getCustom()).thenReturn(oldCustomFields); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 1L) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = - buildLineItemUpdateActions(oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildLineItemUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - - final LineItem oldLineItem = mock(LineItem.class); - when(oldLineItem.getId()).thenReturn("line_item_id"); - when(oldLineItem.getQuantity()).thenReturn(2L); - when(oldLineItem.getCustom()).thenReturn(oldCustomFields); - - final LineItemDraft newLineItem = - LineItemDraftBuilder.ofSku("sku", 4L) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = - buildLineItemUpdateActions(oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( + private static final ShoppingListSyncOptions SYNC_OPTIONS = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final ShoppingList oldShoppingList = mock(ShoppingList.class); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + + @Test + void buildLineItemCustomUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); + + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getCustom()).thenReturn(oldCustomFields); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 1L) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildLineItemCustomUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildLineItemCustomUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getCustom()).thenReturn(oldCustomFields); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 1L) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildLineItemCustomUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + SetLineItemCustomField.ofJson( + "field1", JsonNodeFactory.instance.booleanNode(false), "line_item_id"), + SetLineItemCustomField.ofJson( + "field2", JsonNodeFactory.instance.objectNode().put("es", "val2"), "line_item_id")); + } + + @Test + void buildLineItemCustomUpdateActions_WithNullOldValues_ShouldBuildUpdateAction() { + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val")); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getCustom()).thenReturn(null); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 1L) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildLineItemCustomUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + SetLineItemCustomType.ofTypeIdAndJson("1", newCustomFieldsMap, "line_item_id")); + } + + @Test + void + buildLineItemCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerErrorCallback() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); + + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getCustom()).thenReturn(oldCustomFields); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 1L) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List errors = new ArrayList<>(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errors.add(exception.getMessage())) + .build(); + + final List> updateActions = + buildLineItemCustomUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + format( + "Failed to build custom fields update actions on the line-item with id '%s'." + + " Reason: Custom type ids are not set for both the old and new line-item.", + oldLineItem.getId())); + } + + @Test + void buildLineItemCustomUpdateActions_WithNullValue_ShouldCorrectlyBuildAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "field2", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + newCustomFieldsMap.put("field2", null); + + final CustomFields oldCustomFields = mock(CustomFields.class); + final String typeId = UUID.randomUUID().toString(); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); + + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getCustom()).thenReturn(oldCustomFields); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 1L) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List errors = new ArrayList<>(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errors.add(exception.getMessage())) + .build(); + + final List> updateActions = + buildLineItemCustomUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions); + + assertThat(errors).isEmpty(); + assertThat(updateActions) + .containsExactly(SetLineItemCustomField.ofJson("field2", null, "line_item_id")); + } + + @Test + void buildLineItemCustomUpdateActions_WithNullJsonNodeValue_ShouldCorrectlyBuildAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put( + "field", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field", JsonNodeFactory.instance.nullNode()); + + final CustomFields oldCustomFields = mock(CustomFields.class); + final String typeId = UUID.randomUUID().toString(); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); + + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getCustom()).thenReturn(oldCustomFields); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 1L) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List errors = new ArrayList<>(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errors.add(exception.getMessage())) + .build(); + + final List> updateActions = + buildLineItemCustomUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, syncOptions); + + assertThat(errors).isEmpty(); + assertThat(updateActions) + .containsExactly(SetLineItemCustomField.ofJson("field", null, "line_item_id")); + } + + @Test + void buildChangeLineItemQuantityUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getQuantity()).thenReturn(2L); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 2L).addedAt(ZonedDateTime.now()).build(); + + final Optional> updateAction = + buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isNotPresent(); + } + + @Test + void buildChangeLineItemQuantityUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getQuantity()).thenReturn(2L); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 4L).addedAt(ZonedDateTime.now()).build(); + + final UpdateAction updateAction = + buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("changeLineItemQuantity"); + assertThat((ChangeLineItemQuantity) updateAction) + .isEqualTo(ChangeLineItemQuantity.of("line_item_id", 4L)); + } + + @Test + void buildChangeLineItemQuantityUpdateAction_WithNewNullValue_ShouldBuildUpdateAction() { + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getQuantity()).thenReturn(2L); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", null).addedAt(ZonedDateTime.now()).build(); + + final UpdateAction updateAction = + buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("changeLineItemQuantity"); + assertThat((ChangeLineItemQuantity) updateAction) + .isEqualTo(ChangeLineItemQuantity.of("line_item_id", 1L)); + } + + @Test + void buildChangeLineItemQuantityUpdateAction_WithNewZeroValue_ShouldBuildUpdateAction() { + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getQuantity()).thenReturn(2L); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", null).quantity(0L).addedAt(ZonedDateTime.now()).build(); + + final UpdateAction updateAction = + buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("changeLineItemQuantity"); + assertThat((ChangeLineItemQuantity) updateAction) + .isEqualTo(ChangeLineItemQuantity.of("line_item_id", 0L)); + } + + @Test + void + buildChangeLineItemQuantityUpdateAction_WithNewNullValueAndOldDefaultValue_ShouldNotBuildAction() { + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getQuantity()).thenReturn(1L); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", null).addedAt(ZonedDateTime.now()).build(); + + final Optional> updateAction = + buildChangeLineItemQuantityUpdateAction(oldLineItem, newLineItem); + + assertThat(updateAction).isNotPresent(); + } + + @Test + void buildLineItemUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); + + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getQuantity()).thenReturn(1L); + when(oldLineItem.getCustom()).thenReturn(oldCustomFields); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 1L) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildLineItemUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildLineItemUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + + final LineItem oldLineItem = mock(LineItem.class); + when(oldLineItem.getId()).thenReturn("line_item_id"); + when(oldLineItem.getQuantity()).thenReturn(2L); + when(oldLineItem.getCustom()).thenReturn(oldCustomFields); + + final LineItemDraft newLineItem = + LineItemDraftBuilder.ofSku("sku", 4L) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildLineItemUpdateActions( + oldShoppingList, newShoppingList, oldLineItem, newLineItem, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( ChangeLineItemQuantity.of("line_item_id", 4L), - SetLineItemCustomField.ofJson("field1", - JsonNodeFactory.instance.booleanNode(false), "line_item_id"), - SetLineItemCustomField.ofJson("field2", - JsonNodeFactory.instance.objectNode().put("es", "val2"), "line_item_id") - ); - } + SetLineItemCustomField.ofJson( + "field1", JsonNodeFactory.instance.booleanNode(false), "line_item_id"), + SetLineItemCustomField.ofJson( + "field2", JsonNodeFactory.instance.objectNode().put("es", "val2"), "line_item_id")); + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtilsTest.java b/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtilsTest.java index 389764707b..67d48ee22a 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListSyncUtilsTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListSyncUtils.buildActions; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -21,143 +26,152 @@ import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.CustomFieldsDraftBuilder; import io.sphere.sdk.types.Type; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; - -import static com.commercetools.sync.shoppinglists.utils.ShoppingListSyncUtils.buildActions; -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class ShoppingListSyncUtilsTest { - private static final Locale LOCALE = Locale.GERMAN; - - private static final String CUSTOM_TYPE_ID = "id"; - private static final String CUSTOM_FIELD_NAME = "field"; - private static final String CUSTOM_FIELD_VALUE = "value"; - - private ShoppingList oldShoppingList; - - @BeforeEach - void setup() { - oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("name")); - when(oldShoppingList.getDeleteDaysAfterLastModification()).thenReturn(null); - - final CustomFields customFields = mock(CustomFields.class); - when(customFields.getType()).thenReturn(Type.referenceOfId(CUSTOM_TYPE_ID)); - - final Map customFieldsJsonMapMock = new HashMap<>(); - customFieldsJsonMapMock.put(CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode(CUSTOM_FIELD_VALUE)); - when(customFields.getFieldsJsonMap()).thenReturn(customFieldsJsonMapMock); - - when(oldShoppingList.getCustom()).thenReturn(customFields); - } - - @Test - void buildActions_WithDifferentValuesExceptLineItems_ShouldReturnActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); - when(oldShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "oldName")); - when(oldShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "oldDescription")); - when(oldShoppingList.getAnonymousId()).thenReturn("oldAnonymousId"); - when(oldShoppingList.getDeleteDaysAfterLastModification()).thenReturn(50); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "newSlug")); - when(newShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "newName")); - when(newShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "newDescription")); - when(newShoppingList.getAnonymousId()).thenReturn("newAnonymousId"); - when(newShoppingList.getDeleteDaysAfterLastModification()).thenReturn(70); - - final List> updateActions = buildActions(oldShoppingList, newShoppingList, - mock(ShoppingListSyncOptions.class)); - - assertThat(updateActions).isNotEmpty(); - assertThat(updateActions).contains(SetSlug.of(newShoppingList.getSlug())); - assertThat(updateActions).contains(ChangeName.of(newShoppingList.getName())); - assertThat(updateActions).contains(SetDescription.of(newShoppingList.getDescription())); - assertThat(updateActions).contains(SetAnonymousId.of(newShoppingList.getAnonymousId())); - assertThat(updateActions).contains( - SetDeleteDaysAfterLastModification.of(newShoppingList.getDeleteDaysAfterLastModification())); - } - - @Test - void buildActions_WithDifferentCustomType_ShouldBuildUpdateAction() { - final CustomFieldsDraft customFieldsDraft = - CustomFieldsDraftBuilder.ofTypeId("newId") - .addObject("newField", "newValue") - .build(); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .custom(customFieldsDraft) - .build(); - - final List> actions = buildActions(oldShoppingList, newShoppingList, + private static final Locale LOCALE = Locale.GERMAN; + + private static final String CUSTOM_TYPE_ID = "id"; + private static final String CUSTOM_FIELD_NAME = "field"; + private static final String CUSTOM_FIELD_VALUE = "value"; + + private ShoppingList oldShoppingList; + + @BeforeEach + void setup() { + oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("name")); + when(oldShoppingList.getDeleteDaysAfterLastModification()).thenReturn(null); + + final CustomFields customFields = mock(CustomFields.class); + when(customFields.getType()).thenReturn(Type.referenceOfId(CUSTOM_TYPE_ID)); + + final Map customFieldsJsonMapMock = new HashMap<>(); + customFieldsJsonMapMock.put( + CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode(CUSTOM_FIELD_VALUE)); + when(customFields.getFieldsJsonMap()).thenReturn(customFieldsJsonMapMock); + + when(oldShoppingList.getCustom()).thenReturn(customFields); + } + + @Test + void buildActions_WithDifferentValuesExceptLineItems_ShouldReturnActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); + when(oldShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "oldName")); + when(oldShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "oldDescription")); + when(oldShoppingList.getAnonymousId()).thenReturn("oldAnonymousId"); + when(oldShoppingList.getDeleteDaysAfterLastModification()).thenReturn(50); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "newSlug")); + when(newShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "newName")); + when(newShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "newDescription")); + when(newShoppingList.getAnonymousId()).thenReturn("newAnonymousId"); + when(newShoppingList.getDeleteDaysAfterLastModification()).thenReturn(70); + + final List> updateActions = + buildActions(oldShoppingList, newShoppingList, mock(ShoppingListSyncOptions.class)); + + assertThat(updateActions).isNotEmpty(); + assertThat(updateActions).contains(SetSlug.of(newShoppingList.getSlug())); + assertThat(updateActions).contains(ChangeName.of(newShoppingList.getName())); + assertThat(updateActions).contains(SetDescription.of(newShoppingList.getDescription())); + assertThat(updateActions).contains(SetAnonymousId.of(newShoppingList.getAnonymousId())); + assertThat(updateActions) + .contains( + SetDeleteDaysAfterLastModification.of( + newShoppingList.getDeleteDaysAfterLastModification())); + } + + @Test + void buildActions_WithDifferentCustomType_ShouldBuildUpdateAction() { + final CustomFieldsDraft customFieldsDraft = + CustomFieldsDraftBuilder.ofTypeId("newId").addObject("newField", "newValue").build(); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) + .custom(customFieldsDraft) + .build(); + + final List> actions = + buildActions( + oldShoppingList, + newShoppingList, ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - Assertions.assertThat(actions).containsExactly( - SetCustomType.ofTypeIdAndJson(customFieldsDraft.getType().getId(), customFieldsDraft.getFields())); - } - - @Test - void buildActions_WithSameCustomTypeWithNewCustomFields_ShouldBuildUpdateAction() { - final CustomFieldsDraft sameCustomFieldDraftWithNewCustomField = - CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) - .addObject(CUSTOM_FIELD_NAME, CUSTOM_FIELD_VALUE) - .addObject("name_2", "value_2") - .build(); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .custom(sameCustomFieldDraftWithNewCustomField) - .build(); - - final List> actions = buildActions(oldShoppingList, newShoppingList, + Assertions.assertThat(actions) + .containsExactly( + SetCustomType.ofTypeIdAndJson( + customFieldsDraft.getType().getId(), customFieldsDraft.getFields())); + } + + @Test + void buildActions_WithSameCustomTypeWithNewCustomFields_ShouldBuildUpdateAction() { + final CustomFieldsDraft sameCustomFieldDraftWithNewCustomField = + CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) + .addObject(CUSTOM_FIELD_NAME, CUSTOM_FIELD_VALUE) + .addObject("name_2", "value_2") + .build(); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) + .custom(sameCustomFieldDraftWithNewCustomField) + .build(); + + final List> actions = + buildActions( + oldShoppingList, + newShoppingList, ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - Assertions.assertThat(actions).containsExactly( + Assertions.assertThat(actions) + .containsExactly( SetCustomField.ofJson("name_2", JsonNodeFactory.instance.textNode("value_2"))); - } + } - @Test - void buildActions_WithSameCustomTypeWithDifferentCustomFieldValues_ShouldBuildUpdateAction() { + @Test + void buildActions_WithSameCustomTypeWithDifferentCustomFieldValues_ShouldBuildUpdateAction() { - final CustomFieldsDraft sameCustomFieldDraftWithNewValue = - CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) - .addObject(CUSTOM_FIELD_NAME, - "newValue") - .build(); + final CustomFieldsDraft sameCustomFieldDraftWithNewValue = + CustomFieldsDraftBuilder.ofTypeId(CUSTOM_TYPE_ID) + .addObject(CUSTOM_FIELD_NAME, "newValue") + .build(); - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .custom(sameCustomFieldDraftWithNewValue) - .build(); + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) + .custom(sameCustomFieldDraftWithNewValue) + .build(); - final List> actions = buildActions(oldShoppingList, newShoppingList, + final List> actions = + buildActions( + oldShoppingList, + newShoppingList, ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - Assertions.assertThat(actions).containsExactly( - SetCustomField.ofJson(CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode("newValue"))); - } - - @Test - void buildActions_WithJustNewShoppingListHasNullCustomType_ShouldBuildUpdateAction() { - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .custom(null) - .build(); - - final List> actions = buildActions(oldShoppingList, newShoppingList, + Assertions.assertThat(actions) + .containsExactly( + SetCustomField.ofJson( + CUSTOM_FIELD_NAME, JsonNodeFactory.instance.textNode("newValue"))); + } + + @Test + void buildActions_WithJustNewShoppingListHasNullCustomType_ShouldBuildUpdateAction() { + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")).custom(null).build(); + + final List> actions = + buildActions( + oldShoppingList, + newShoppingList, ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build()); - Assertions.assertThat(actions).containsExactly(SetCustomType.ofRemoveType()); - } + Assertions.assertThat(actions).containsExactly(SetCustomType.ofRemoveType()); + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtilsTest.java index 1e1b09eb6f..8be6109aa4 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListUpdateActionUtilsTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildChangeNameUpdateAction; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetAnonymousIdUpdateAction; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetCustomerUpdateAction; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetDeleteDaysAfterLastModificationUpdateAction; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetDescriptionUpdateAction; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetSlugUpdateAction; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.customers.Customer; import io.sphere.sdk.models.LocalizedString; @@ -13,314 +23,311 @@ import io.sphere.sdk.shoppinglists.commands.updateactions.SetDeleteDaysAfterLastModification; import io.sphere.sdk.shoppinglists.commands.updateactions.SetDescription; import io.sphere.sdk.shoppinglists.commands.updateactions.SetSlug; -import org.junit.jupiter.api.Test; - import java.util.Locale; import java.util.Optional; import java.util.UUID; - -import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildChangeNameUpdateAction; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetAnonymousIdUpdateAction; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetCustomerUpdateAction; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetDeleteDaysAfterLastModificationUpdateAction; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetDescriptionUpdateAction; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListUpdateActionUtils.buildSetSlugUpdateAction; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ShoppingListUpdateActionUtilsTest { - private static final Locale LOCALE = Locale.GERMAN; - - @Test - void buildSetSlugUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "newSlug")); - - final UpdateAction setSlugUpdateAction = - buildSetSlugUpdateAction(oldShoppingList, newShoppingList).orElse(null); - - assertThat(setSlugUpdateAction).isNotNull(); - assertThat(setSlugUpdateAction.getAction()).isEqualTo("setSlug"); - assertThat(((SetSlug) setSlugUpdateAction).getSlug()) - .isEqualTo(LocalizedString.of(LOCALE, "newSlug")); - } - - @Test - void buildSetSlugUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getSlug()).thenReturn(null); - - final UpdateAction setSlugUpdateAction = - buildSetSlugUpdateAction(oldShoppingList, newShoppingList).orElse(null); - - assertThat(setSlugUpdateAction).isNotNull(); - assertThat(setSlugUpdateAction.getAction()).isEqualTo("setSlug"); - assertThat(((SetSlug) setSlugUpdateAction).getSlug()).isNull(); - } - - @Test - void buildSetSlugUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); - - final Optional> setSlugUpdateAction = - buildSetSlugUpdateAction(oldShoppingList, newShoppingList); - - assertThat(setSlugUpdateAction).isNotNull(); - assertThat(setSlugUpdateAction).isNotPresent(); - } - - @Test - void buildChangeNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "oldName")); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "newName")); - - final UpdateAction changeNameUpdateAction = - buildChangeNameUpdateAction(oldShoppingList, newShoppingList).orElse(null); - - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); - assertThat(((ChangeName) changeNameUpdateAction).getName()).isEqualTo(LocalizedString.of(LOCALE, "newName")); - } - - @Test - void buildChangeNameUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "oldName")); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getName()).thenReturn(null); - - final UpdateAction changeNameUpdateAction = - buildChangeNameUpdateAction(oldShoppingList, newShoppingList).orElse(null); - - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); - assertThat(((ChangeName) changeNameUpdateAction).getName()).isNull(); - } - - @Test - void buildChangeNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "oldName")); - - final ShoppingListDraft newShoppingListWithSameName = mock(ShoppingListDraft.class); - when(newShoppingListWithSameName.getName()) - .thenReturn(LocalizedString.of(LOCALE, "oldName")); - - final Optional> changeNameUpdateAction = - buildChangeNameUpdateAction(oldShoppingList, newShoppingListWithSameName); - - assertThat(changeNameUpdateAction).isNotNull(); - assertThat(changeNameUpdateAction).isNotPresent(); - } - - @Test - void buildSetDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "oldDescription")); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "newDescription")); - - final UpdateAction setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(oldShoppingList, newShoppingList).orElse(null); - - assertThat(setDescriptionUpdateAction).isNotNull(); - assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); - assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()) - .isEqualTo(LocalizedString.of(LOCALE, "newDescription")); - } - - @Test - void buildSetDescriptionUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "oldDescription")); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getDescription()).thenReturn(null); - - final UpdateAction setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(oldShoppingList, newShoppingList).orElse(null); - - assertThat(setDescriptionUpdateAction).isNotNull(); - assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); - assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()).isEqualTo(null); - } - - @Test - void buildSetDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "oldDescription")); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getDescription()) - .thenReturn(LocalizedString.of(LOCALE, "oldDescription")); - - final Optional> setDescriptionUpdateAction = - buildSetDescriptionUpdateAction(oldShoppingList, newShoppingList); - - assertThat(setDescriptionUpdateAction).isNotNull(); - assertThat(setDescriptionUpdateAction).isNotPresent(); - } - - @Test - void buildSetCustomerUpdateAction_WithDifferentReference_ShouldBuildUpdateAction() { - final String customerId = UUID.randomUUID().toString(); - final Reference customerReference = Reference.of(Customer.referenceTypeId(), customerId); - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getCustomer()).thenReturn(customerReference); - - final String resolvedCustomerId = UUID.randomUUID().toString(); - final Reference newCustomerReference = Reference.of(Customer.referenceTypeId(), resolvedCustomerId); - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getCustomer()).thenReturn(newCustomerReference); - - final Optional> setCustomerUpdateAction = - buildSetCustomerUpdateAction(oldShoppingList, newShoppingList); - - assertThat(setCustomerUpdateAction).isNotEmpty(); - assertThat(setCustomerUpdateAction).containsInstanceOf(SetCustomer.class); - assertThat(((SetCustomer)setCustomerUpdateAction.get()).getCustomer()) - .isEqualTo(Reference.of(Customer.referenceTypeId(), resolvedCustomerId)); - } - - @Test - void buildSetCustomerUpdateAction_WithSameReference_ShouldNotBuildUpdateAction() { - final String customerId = UUID.randomUUID().toString(); - final Reference customerReference = Reference.of(Customer.referenceTypeId(), customerId); - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getCustomer()).thenReturn(customerReference); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getCustomer()).thenReturn(ResourceIdentifier.ofId(customerId)); - - final Optional> setCustomerUpdateAction = - buildSetCustomerUpdateAction(oldShoppingList, newShoppingList); - - assertThat(setCustomerUpdateAction).isEmpty(); - } - - @Test - void buildSetCustomerUpdateAction_WithOnlyNewReference_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - - final String newCustomerId = UUID.randomUUID().toString(); - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getCustomer()).thenReturn(ResourceIdentifier.ofId(newCustomerId)); - - final Optional> setCustomerUpdateAction = - buildSetCustomerUpdateAction(oldShoppingList, newShoppingList); - - assertThat(setCustomerUpdateAction).isNotEmpty(); - assertThat(setCustomerUpdateAction).containsInstanceOf(SetCustomer.class); - assertThat(((SetCustomer)setCustomerUpdateAction.get()).getCustomer()) - .isEqualTo(Reference.of(Customer.referenceTypeId(), newCustomerId)); - } - - @Test - void buildSetCustomerUpdateAction_WithoutNewReference_ShouldReturnUnsetAction() { - final String customerId = UUID.randomUUID().toString(); - final Reference customerReference = Reference.of(Customer.referenceTypeId(), customerId); - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getCustomer()).thenReturn(customerReference); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getCustomer()).thenReturn(null); - - final Optional> setCustomerUpdateAction = - buildSetCustomerUpdateAction(oldShoppingList, newShoppingList); - - assertThat(setCustomerUpdateAction).isNotEmpty(); - assertThat(setCustomerUpdateAction).containsInstanceOf(SetCustomer.class); - //Note: If the old value is set, but the new one is empty - the command will unset the customer. - assertThat(((SetCustomer) setCustomerUpdateAction.get()).getCustomer()).isNull(); - } + private static final Locale LOCALE = Locale.GERMAN; - @Test - void buildSetAnonymousId_WithDifferentValues_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getAnonymousId()).thenReturn("123"); - - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getAnonymousId()).thenReturn("567"); - - final Optional> setAnonymousIdUpdateAction = - buildSetAnonymousIdUpdateAction(oldShoppingList, newShoppingList); + @Test + void buildSetSlugUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); - assertThat(setAnonymousIdUpdateAction).isNotNull(); - assertThat(setAnonymousIdUpdateAction).containsInstanceOf(SetAnonymousId.class); - } + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "newSlug")); - @Test - void buildSetAnonymousId_WithNullValues_ShouldBuildUpdateActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getAnonymousId()).thenReturn("123"); + final UpdateAction setSlugUpdateAction = + buildSetSlugUpdateAction(oldShoppingList, newShoppingList).orElse(null); - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getAnonymousId()).thenReturn(null); + assertThat(setSlugUpdateAction).isNotNull(); + assertThat(setSlugUpdateAction.getAction()).isEqualTo("setSlug"); + assertThat(((SetSlug) setSlugUpdateAction).getSlug()) + .isEqualTo(LocalizedString.of(LOCALE, "newSlug")); + } - final Optional> setAnonymousIdUpdateAction = - buildSetAnonymousIdUpdateAction(oldShoppingList, newShoppingList); - - assertThat(setAnonymousIdUpdateAction).isNotNull(); - assertThat(setAnonymousIdUpdateAction).containsInstanceOf(SetAnonymousId.class); - } - - @Test - void buildSetAnonymousId_WithSameValues_ShouldNotBuildUpdateActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getAnonymousId()).thenReturn("123"); + @Test + void buildSetSlugUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getAnonymousId()).thenReturn("123"); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getSlug()).thenReturn(null); - final Optional> setAnonymousIdUpdateAction = - buildSetAnonymousIdUpdateAction(oldShoppingList, newShoppingList); + final UpdateAction setSlugUpdateAction = + buildSetSlugUpdateAction(oldShoppingList, newShoppingList).orElse(null); - assertThat(setAnonymousIdUpdateAction).isEmpty(); - } + assertThat(setSlugUpdateAction).isNotNull(); + assertThat(setSlugUpdateAction.getAction()).isEqualTo("setSlug"); + assertThat(((SetSlug) setSlugUpdateAction).getSlug()).isNull(); + } - @Test - void buildSetDeleteDaysAfterLastModificationUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getDeleteDaysAfterLastModification()).thenReturn(50); + @Test + void buildSetSlugUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getDeleteDaysAfterLastModification()).thenReturn(70); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getSlug()).thenReturn(LocalizedString.of(LOCALE, "oldSlug")); - final Optional> setDeleteDaysUpdateAction = - buildSetDeleteDaysAfterLastModificationUpdateAction(oldShoppingList, newShoppingList); + final Optional> setSlugUpdateAction = + buildSetSlugUpdateAction(oldShoppingList, newShoppingList); - assertThat(setDeleteDaysUpdateAction).isNotNull(); - assertThat(setDeleteDaysUpdateAction).containsInstanceOf(SetDeleteDaysAfterLastModification.class); - } + assertThat(setSlugUpdateAction).isNotNull(); + assertThat(setSlugUpdateAction).isNotPresent(); + } - @Test - void buildSetDeleteDaysAfterLastModificationUpdateAction_WithNullValues_ShouldBuildUpdateAction() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getDeleteDaysAfterLastModification()).thenReturn(50); + @Test + void buildChangeNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "oldName")); - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - when(newShoppingList.getDeleteDaysAfterLastModification()).thenReturn(null); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "newName")); + + final UpdateAction changeNameUpdateAction = + buildChangeNameUpdateAction(oldShoppingList, newShoppingList).orElse(null); + + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); + assertThat(((ChangeName) changeNameUpdateAction).getName()) + .isEqualTo(LocalizedString.of(LOCALE, "newName")); + } + + @Test + void buildChangeNameUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "oldName")); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getName()).thenReturn(null); + + final UpdateAction changeNameUpdateAction = + buildChangeNameUpdateAction(oldShoppingList, newShoppingList).orElse(null); + + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction.getAction()).isEqualTo("changeName"); + assertThat(((ChangeName) changeNameUpdateAction).getName()).isNull(); + } + + @Test + void buildChangeNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getName()).thenReturn(LocalizedString.of(LOCALE, "oldName")); + + final ShoppingListDraft newShoppingListWithSameName = mock(ShoppingListDraft.class); + when(newShoppingListWithSameName.getName()).thenReturn(LocalizedString.of(LOCALE, "oldName")); + + final Optional> changeNameUpdateAction = + buildChangeNameUpdateAction(oldShoppingList, newShoppingListWithSameName); + + assertThat(changeNameUpdateAction).isNotNull(); + assertThat(changeNameUpdateAction).isNotPresent(); + } + + @Test + void buildSetDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "oldDescription")); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "newDescription")); + + final UpdateAction setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(oldShoppingList, newShoppingList).orElse(null); + + assertThat(setDescriptionUpdateAction).isNotNull(); + assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); + assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()) + .isEqualTo(LocalizedString.of(LOCALE, "newDescription")); + } + + @Test + void buildSetDescriptionUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "oldDescription")); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getDescription()).thenReturn(null); + + final UpdateAction setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(oldShoppingList, newShoppingList).orElse(null); + + assertThat(setDescriptionUpdateAction).isNotNull(); + assertThat(setDescriptionUpdateAction.getAction()).isEqualTo("setDescription"); + assertThat(((SetDescription) setDescriptionUpdateAction).getDescription()).isEqualTo(null); + } + + @Test + void buildSetDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "oldDescription")); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getDescription()).thenReturn(LocalizedString.of(LOCALE, "oldDescription")); + + final Optional> setDescriptionUpdateAction = + buildSetDescriptionUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setDescriptionUpdateAction).isNotNull(); + assertThat(setDescriptionUpdateAction).isNotPresent(); + } + + @Test + void buildSetCustomerUpdateAction_WithDifferentReference_ShouldBuildUpdateAction() { + final String customerId = UUID.randomUUID().toString(); + final Reference customerReference = + Reference.of(Customer.referenceTypeId(), customerId); + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getCustomer()).thenReturn(customerReference); + + final String resolvedCustomerId = UUID.randomUUID().toString(); + final Reference newCustomerReference = + Reference.of(Customer.referenceTypeId(), resolvedCustomerId); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getCustomer()).thenReturn(newCustomerReference); + + final Optional> setCustomerUpdateAction = + buildSetCustomerUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setCustomerUpdateAction).isNotEmpty(); + assertThat(setCustomerUpdateAction).containsInstanceOf(SetCustomer.class); + assertThat(((SetCustomer) setCustomerUpdateAction.get()).getCustomer()) + .isEqualTo(Reference.of(Customer.referenceTypeId(), resolvedCustomerId)); + } + + @Test + void buildSetCustomerUpdateAction_WithSameReference_ShouldNotBuildUpdateAction() { + final String customerId = UUID.randomUUID().toString(); + final Reference customerReference = + Reference.of(Customer.referenceTypeId(), customerId); + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getCustomer()).thenReturn(customerReference); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getCustomer()).thenReturn(ResourceIdentifier.ofId(customerId)); + + final Optional> setCustomerUpdateAction = + buildSetCustomerUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setCustomerUpdateAction).isEmpty(); + } + + @Test + void buildSetCustomerUpdateAction_WithOnlyNewReference_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + + final String newCustomerId = UUID.randomUUID().toString(); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getCustomer()).thenReturn(ResourceIdentifier.ofId(newCustomerId)); + + final Optional> setCustomerUpdateAction = + buildSetCustomerUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setCustomerUpdateAction).isNotEmpty(); + assertThat(setCustomerUpdateAction).containsInstanceOf(SetCustomer.class); + assertThat(((SetCustomer) setCustomerUpdateAction.get()).getCustomer()) + .isEqualTo(Reference.of(Customer.referenceTypeId(), newCustomerId)); + } + + @Test + void buildSetCustomerUpdateAction_WithoutNewReference_ShouldReturnUnsetAction() { + final String customerId = UUID.randomUUID().toString(); + final Reference customerReference = + Reference.of(Customer.referenceTypeId(), customerId); + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getCustomer()).thenReturn(customerReference); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getCustomer()).thenReturn(null); + + final Optional> setCustomerUpdateAction = + buildSetCustomerUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setCustomerUpdateAction).isNotEmpty(); + assertThat(setCustomerUpdateAction).containsInstanceOf(SetCustomer.class); + // Note: If the old value is set, but the new one is empty - the command will unset the + // customer. + assertThat(((SetCustomer) setCustomerUpdateAction.get()).getCustomer()).isNull(); + } + + @Test + void buildSetAnonymousId_WithDifferentValues_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getAnonymousId()).thenReturn("123"); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getAnonymousId()).thenReturn("567"); + + final Optional> setAnonymousIdUpdateAction = + buildSetAnonymousIdUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setAnonymousIdUpdateAction).isNotNull(); + assertThat(setAnonymousIdUpdateAction).containsInstanceOf(SetAnonymousId.class); + } + + @Test + void buildSetAnonymousId_WithNullValues_ShouldBuildUpdateActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getAnonymousId()).thenReturn("123"); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getAnonymousId()).thenReturn(null); + + final Optional> setAnonymousIdUpdateAction = + buildSetAnonymousIdUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setAnonymousIdUpdateAction).isNotNull(); + assertThat(setAnonymousIdUpdateAction).containsInstanceOf(SetAnonymousId.class); + } + + @Test + void buildSetAnonymousId_WithSameValues_ShouldNotBuildUpdateActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getAnonymousId()).thenReturn("123"); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getAnonymousId()).thenReturn("123"); + + final Optional> setAnonymousIdUpdateAction = + buildSetAnonymousIdUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setAnonymousIdUpdateAction).isEmpty(); + } + + @Test + void + buildSetDeleteDaysAfterLastModificationUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getDeleteDaysAfterLastModification()).thenReturn(50); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getDeleteDaysAfterLastModification()).thenReturn(70); - final Optional> setDeleteDaysUpdateAction = - buildSetDeleteDaysAfterLastModificationUpdateAction(oldShoppingList, newShoppingList); + final Optional> setDeleteDaysUpdateAction = + buildSetDeleteDaysAfterLastModificationUpdateAction(oldShoppingList, newShoppingList); - assertThat(setDeleteDaysUpdateAction).isNotNull(); - assertThat(setDeleteDaysUpdateAction).containsInstanceOf(SetDeleteDaysAfterLastModification.class); - } + assertThat(setDeleteDaysUpdateAction).isNotNull(); + assertThat(setDeleteDaysUpdateAction) + .containsInstanceOf(SetDeleteDaysAfterLastModification.class); + } + + @Test + void + buildSetDeleteDaysAfterLastModificationUpdateAction_WithNullValues_ShouldBuildUpdateAction() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getDeleteDaysAfterLastModification()).thenReturn(50); + + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + when(newShoppingList.getDeleteDaysAfterLastModification()).thenReturn(null); + + final Optional> setDeleteDaysUpdateAction = + buildSetDeleteDaysAfterLastModificationUpdateAction(oldShoppingList, newShoppingList); + + assertThat(setDeleteDaysUpdateAction).isNotNull(); + assertThat(setDeleteDaysUpdateAction) + .containsInstanceOf(SetDeleteDaysAfterLastModification.class); + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListsReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListsReferenceResolutionUtilsTest.java index 2653619f13..a294a13fb6 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListsReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/utils/ShoppingListsReferenceResolutionUtilsTest.java @@ -1,5 +1,16 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.commons.MockUtils.getMockCustomer; +import static com.commercetools.sync.commons.MockUtils.getTypeMock; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.buildShoppingListQuery; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.mapToShoppingListDrafts; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.customers.Customer; import io.sphere.sdk.expansion.ExpansionPath; import io.sphere.sdk.models.LocalizedString; @@ -15,179 +26,166 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; import java.util.UUID; - -import static com.commercetools.sync.commons.MockUtils.getMockCustomer; -import static com.commercetools.sync.commons.MockUtils.getTypeMock; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.buildShoppingListQuery; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListReferenceResolutionUtils.mapToShoppingListDrafts; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class ShoppingListsReferenceResolutionUtilsTest { - @Test - void mapToShoppingListDrafts_WithExpandedReferences_ShouldReturnResourceIdentifiersWithKeys() { - final Type mockCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); - final Customer mockCustomer = getMockCustomer(UUID.randomUUID().toString(), "customerKey"); - final ProductVariant mockProductVariant = mock(ProductVariant.class); - when(mockProductVariant.getSku()).thenReturn("variant-sku"); - - final List mockShoppingLists = new ArrayList<>(); - mockShoppingLists.add(null); + @Test + void mapToShoppingListDrafts_WithExpandedReferences_ShouldReturnResourceIdentifiersWithKeys() { + final Type mockCustomType = getTypeMock(UUID.randomUUID().toString(), "customTypeKey"); + final Customer mockCustomer = getMockCustomer(UUID.randomUUID().toString(), "customerKey"); + final ProductVariant mockProductVariant = mock(ProductVariant.class); + when(mockProductVariant.getSku()).thenReturn("variant-sku"); - for (int i = 0; i < 3; i++) { - final ShoppingList mockShoppingList = mock(ShoppingList.class); + final List mockShoppingLists = new ArrayList<>(); + mockShoppingLists.add(null); - final Reference customerReference = - Reference.ofResourceTypeIdAndObj(Customer.referenceTypeId(), mockCustomer); - when(mockShoppingList.getCustomer()).thenReturn(customerReference); + for (int i = 0; i < 3; i++) { + final ShoppingList mockShoppingList = mock(ShoppingList.class); - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = - Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), mockCustomType); + final Reference customerReference = + Reference.ofResourceTypeIdAndObj(Customer.referenceTypeId(), mockCustomer); + when(mockShoppingList.getCustomer()).thenReturn(customerReference); - when(mockCustomFields.getType()).thenReturn(typeReference); - when(mockShoppingList.getCustom()).thenReturn(mockCustomFields); + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = + Reference.ofResourceTypeIdAndObj(Type.referenceTypeId(), mockCustomType); - final LineItem mockLineItem = mock(LineItem.class); - when(mockLineItem.getVariant()).thenReturn(mockProductVariant); - when(mockLineItem.getCustom()).thenReturn(mockCustomFields); + when(mockCustomFields.getType()).thenReturn(typeReference); + when(mockShoppingList.getCustom()).thenReturn(mockCustomFields); - when(mockShoppingList.getLineItems()) - .thenReturn(singletonList(mockLineItem)); + final LineItem mockLineItem = mock(LineItem.class); + when(mockLineItem.getVariant()).thenReturn(mockProductVariant); + when(mockLineItem.getCustom()).thenReturn(mockCustomFields); - final TextLineItem mockTextLineItem = mock(TextLineItem.class); - when(mockTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("textLineItemName")); - when(mockTextLineItem.getCustom()).thenReturn(mockCustomFields); + when(mockShoppingList.getLineItems()).thenReturn(singletonList(mockLineItem)); - when(mockShoppingList.getTextLineItems()).thenReturn(singletonList(mockTextLineItem)); + final TextLineItem mockTextLineItem = mock(TextLineItem.class); + when(mockTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("textLineItemName")); + when(mockTextLineItem.getCustom()).thenReturn(mockCustomFields); - mockShoppingLists.add(mockShoppingList); - } + when(mockShoppingList.getTextLineItems()).thenReturn(singletonList(mockTextLineItem)); - final List shoppingListDrafts = mapToShoppingListDrafts(mockShoppingLists); - - assertThat(shoppingListDrafts).hasSize(3); - shoppingListDrafts.forEach(draft -> { - assertThat(draft.getCustomer().getKey()).isEqualTo("customerKey"); - assertThat(draft.getCustom().getType().getKey()).isEqualTo("customTypeKey"); - - assertThat(draft.getLineItems()).containsExactly( - LineItemDraftBuilder - .ofSku("variant-sku", 0L) - .custom(CustomFieldsDraft.ofTypeKeyAndJson("customTypeKey", emptyMap())) - .build()); - - assertThat(draft.getTextLineItems()).containsExactly( - TextLineItemDraftBuilder - .of(LocalizedString.ofEnglish("textLineItemName"), 0L) - .custom(CustomFieldsDraft.ofTypeKeyAndJson("customTypeKey", emptyMap())) - .build()); - }); + mockShoppingLists.add(mockShoppingList); } - @Test - void mapToShoppingListDrafts_WithNonExpandedReferences_ShouldReturnResourceIdentifiersWithoutReferencedKeys() { - final String customTypeId = UUID.randomUUID().toString(); - final String customerId = UUID.randomUUID().toString(); - - - final List mockShoppingLists = new ArrayList<>(); - mockShoppingLists.add(null); - - for (int i = 0; i < 3; i++) { - final ShoppingList mockShoppingList = mock(ShoppingList.class); - - final Reference customerReference = - Reference.ofResourceTypeIdAndId(Customer.referenceTypeId(), customerId); - when(mockShoppingList.getCustomer()).thenReturn(customerReference); + final List shoppingListDrafts = mapToShoppingListDrafts(mockShoppingLists); + + assertThat(shoppingListDrafts).hasSize(3); + shoppingListDrafts.forEach( + draft -> { + assertThat(draft.getCustomer().getKey()).isEqualTo("customerKey"); + assertThat(draft.getCustom().getType().getKey()).isEqualTo("customTypeKey"); + + assertThat(draft.getLineItems()) + .containsExactly( + LineItemDraftBuilder.ofSku("variant-sku", 0L) + .custom(CustomFieldsDraft.ofTypeKeyAndJson("customTypeKey", emptyMap())) + .build()); + + assertThat(draft.getTextLineItems()) + .containsExactly( + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("textLineItemName"), 0L) + .custom(CustomFieldsDraft.ofTypeKeyAndJson("customTypeKey", emptyMap())) + .build()); + }); + } - final CustomFields mockCustomFields = mock(CustomFields.class); - final Reference typeReference = Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), - customTypeId); - when(mockCustomFields.getType()).thenReturn(typeReference); - when(mockShoppingList.getCustom()).thenReturn(mockCustomFields); + @Test + void + mapToShoppingListDrafts_WithNonExpandedReferences_ShouldReturnResourceIdentifiersWithoutReferencedKeys() { + final String customTypeId = UUID.randomUUID().toString(); + final String customerId = UUID.randomUUID().toString(); - final LineItem mockLineItemWithNullVariant = mock(LineItem.class); - when(mockLineItemWithNullVariant.getVariant()).thenReturn(null); + final List mockShoppingLists = new ArrayList<>(); + mockShoppingLists.add(null); - when(mockShoppingList.getLineItems()) - .thenReturn(singletonList(mockLineItemWithNullVariant)); + for (int i = 0; i < 3; i++) { + final ShoppingList mockShoppingList = mock(ShoppingList.class); - final TextLineItem mockTextLineItem = mock(TextLineItem.class); - when(mockTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("textLineItemName")); - when(mockTextLineItem.getCustom()).thenReturn(mockCustomFields); + final Reference customerReference = + Reference.ofResourceTypeIdAndId(Customer.referenceTypeId(), customerId); + when(mockShoppingList.getCustomer()).thenReturn(customerReference); - when(mockShoppingList.getTextLineItems()).thenReturn(singletonList(mockTextLineItem)); + final CustomFields mockCustomFields = mock(CustomFields.class); + final Reference typeReference = + Reference.ofResourceTypeIdAndId(Type.referenceTypeId(), customTypeId); + when(mockCustomFields.getType()).thenReturn(typeReference); + when(mockShoppingList.getCustom()).thenReturn(mockCustomFields); - mockShoppingLists.add(mockShoppingList); - } + final LineItem mockLineItemWithNullVariant = mock(LineItem.class); + when(mockLineItemWithNullVariant.getVariant()).thenReturn(null); - final List shoppingListDrafts = mapToShoppingListDrafts(mockShoppingLists); + when(mockShoppingList.getLineItems()).thenReturn(singletonList(mockLineItemWithNullVariant)); - assertThat(shoppingListDrafts).hasSize(3); - shoppingListDrafts.forEach(draft -> { - assertThat(draft.getCustomer().getId()).isEqualTo(customerId); - assertThat(draft.getCustom().getType().getKey()).isNull(); + final TextLineItem mockTextLineItem = mock(TextLineItem.class); + when(mockTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("textLineItemName")); + when(mockTextLineItem.getCustom()).thenReturn(mockCustomFields); - assertThat(draft.getLineItems()).isEqualTo(emptyList()); + when(mockShoppingList.getTextLineItems()).thenReturn(singletonList(mockTextLineItem)); - assertThat(draft.getTextLineItems()).containsExactly( - TextLineItemDraftBuilder - .of(LocalizedString.ofEnglish("textLineItemName"), 0L) - .custom(CustomFieldsDraft.ofTypeIdAndJson(customTypeId, emptyMap())) - .build()); - }); + mockShoppingLists.add(mockShoppingList); } - @Test - void mapToShoppingListDrafts_WithOtherFields_ShouldReturnDraftsCorrectly() { + final List shoppingListDrafts = mapToShoppingListDrafts(mockShoppingLists); - final ShoppingList mockShoppingList = mock(ShoppingList.class); - when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("name")); - when(mockShoppingList.getDescription()).thenReturn(LocalizedString.ofEnglish("desc")); - when(mockShoppingList.getKey()).thenReturn("key"); - when(mockShoppingList.getSlug()).thenReturn(LocalizedString.ofEnglish("slug")); - when(mockShoppingList.getDeleteDaysAfterLastModification()).thenReturn(2); - when(mockShoppingList.getAnonymousId()).thenReturn("anonymousId"); + assertThat(shoppingListDrafts).hasSize(3); + shoppingListDrafts.forEach( + draft -> { + assertThat(draft.getCustomer().getId()).isEqualTo(customerId); + assertThat(draft.getCustom().getType().getKey()).isNull(); - when(mockShoppingList.getCustomer()).thenReturn(null); - when(mockShoppingList.getCustom()).thenReturn(null); - when(mockShoppingList.getLineItems()).thenReturn(null); - when(mockShoppingList.getTextLineItems()).thenReturn(null); + assertThat(draft.getLineItems()).isEqualTo(emptyList()); - final List shoppingListDrafts = - mapToShoppingListDrafts(singletonList(mockShoppingList)); - - assertThat(shoppingListDrafts).containsExactly( - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("name")) + assertThat(draft.getTextLineItems()) + .containsExactly( + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("textLineItemName"), 0L) + .custom(CustomFieldsDraft.ofTypeIdAndJson(customTypeId, emptyMap())) + .build()); + }); + } + + @Test + void mapToShoppingListDrafts_WithOtherFields_ShouldReturnDraftsCorrectly() { + + final ShoppingList mockShoppingList = mock(ShoppingList.class); + when(mockShoppingList.getName()).thenReturn(LocalizedString.ofEnglish("name")); + when(mockShoppingList.getDescription()).thenReturn(LocalizedString.ofEnglish("desc")); + when(mockShoppingList.getKey()).thenReturn("key"); + when(mockShoppingList.getSlug()).thenReturn(LocalizedString.ofEnglish("slug")); + when(mockShoppingList.getDeleteDaysAfterLastModification()).thenReturn(2); + when(mockShoppingList.getAnonymousId()).thenReturn("anonymousId"); + + when(mockShoppingList.getCustomer()).thenReturn(null); + when(mockShoppingList.getCustom()).thenReturn(null); + when(mockShoppingList.getLineItems()).thenReturn(null); + when(mockShoppingList.getTextLineItems()).thenReturn(null); + + final List shoppingListDrafts = + mapToShoppingListDrafts(singletonList(mockShoppingList)); + + assertThat(shoppingListDrafts) + .containsExactly( + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) .description(LocalizedString.ofEnglish("desc")) .key("key") .slug(LocalizedString.ofEnglish("slug")) .deleteDaysAfterLastModification(2) .anonymousId("anonymousId") - .build() - ); - } - - @Test - void buildShoppingListQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { - assertThat(buildShoppingListQuery().expansionPaths()) - .containsExactly( - ExpansionPath.of("customer"), - ExpansionPath.of("custom.type"), - ExpansionPath.of("lineItems[*].variant"), - ExpansionPath.of("lineItems[*].custom.type"), - ExpansionPath.of("textLineItems[*].custom.type")); - } + .build()); + } + + @Test + void buildShoppingListQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { + assertThat(buildShoppingListQuery().expansionPaths()) + .containsExactly( + ExpansionPath.of("customer"), + ExpansionPath.of("custom.type"), + ExpansionPath.of("lineItems[*].variant"), + ExpansionPath.of("lineItems[*].custom.type"), + ExpansionPath.of("textLineItems[*].custom.type")); + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/utils/TextLineItemListUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/shoppinglists/utils/TextLineItemListUpdateActionUtilsTest.java index 348c7b298d..f3237dd2c8 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/utils/TextLineItemListUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/utils/TextLineItemListUpdateActionUtilsTest.java @@ -1,5 +1,21 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; +import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildLineItemsUpdateActions; +import static com.commercetools.sync.shoppinglists.utils.ShoppingListSyncUtils.buildActions; +import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildTextLineItemsUpdateActions; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.services.TypeService; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptionsBuilder; @@ -29,9 +45,6 @@ import io.sphere.sdk.shoppinglists.commands.updateactions.SetTextLineItemCustomField; import io.sphere.sdk.shoppinglists.commands.updateactions.SetTextLineItemDescription; import io.sphere.sdk.types.CustomFieldsDraft; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -39,374 +52,383 @@ import java.util.Locale; import java.util.Objects; import java.util.Optional; - -import static com.commercetools.sync.commons.utils.CompletableFutureUtils.mapValuesToFutureOfCompletedValues; -import static com.commercetools.sync.shoppinglists.utils.LineItemUpdateActionUtils.buildLineItemsUpdateActions; -import static com.commercetools.sync.shoppinglists.utils.ShoppingListSyncUtils.buildActions; -import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildTextLineItemsUpdateActions; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class TextLineItemListUpdateActionUtilsTest { - private static final String RES_ROOT = "com/commercetools/sync/shoppinglists/utils/textlineitems/"; - private static final String SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123 = - RES_ROOT + "shoppinglist-with-textlineitems-name-123.json"; - private static final String SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123_WITH_CHANGES = - RES_ROOT + "shoppinglist-with-textlineitems-name-123-with-changes.json"; - private static final String SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_12 = - RES_ROOT + "shoppinglist-with-textlineitems-name-12.json"; - private static final String SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_1234 = - RES_ROOT + "shoppinglist-with-textlineitems-name-1234.json"; + private static final String RES_ROOT = + "com/commercetools/sync/shoppinglists/utils/textlineitems/"; + private static final String SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123 = + RES_ROOT + "shoppinglist-with-textlineitems-name-123.json"; + private static final String SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123_WITH_CHANGES = + RES_ROOT + "shoppinglist-with-textlineitems-name-123-with-changes.json"; + private static final String SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_12 = + RES_ROOT + "shoppinglist-with-textlineitems-name-12.json"; + private static final String SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_1234 = + RES_ROOT + "shoppinglist-with-textlineitems-name-1234.json"; - private static final ShoppingListSyncOptions SYNC_OPTIONS = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + private static final ShoppingListSyncOptions SYNC_OPTIONS = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - private static final TextLineItemReferenceResolver textLineItemReferenceResolver = - new TextLineItemReferenceResolver(SYNC_OPTIONS, getMockTypeService()); + private static final TextLineItemReferenceResolver textLineItemReferenceResolver = + new TextLineItemReferenceResolver(SYNC_OPTIONS, getMockTypeService()); - @Test - void buildTextLineItemsUpdateActions_WithoutNewAndWithNullOldTextLineItems_ShouldNotBuildActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getTextLineItems()).thenReturn(emptyList()); + @Test + void + buildTextLineItemsUpdateActions_WithoutNewAndWithNullOldTextLineItems_ShouldNotBuildActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getTextLineItems()).thenReturn(emptyList()); - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .build(); + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")).build(); - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildTextLineItemsUpdateActions_WithNullNewAndNullOldTextLineItems_ShouldNotBuildActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getTextLineItems()).thenReturn(null); + @Test + void buildTextLineItemsUpdateActions_WithNullNewAndNullOldTextLineItems_ShouldNotBuildActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getTextLineItems()).thenReturn(null); - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .build(); + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")).build(); - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildTextLineItemsUpdateActions_WithNullOldAndEmptyNewTextLineItems_ShouldNotBuildActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getTextLineItems()).thenReturn(null); + @Test + void buildTextLineItemsUpdateActions_WithNullOldAndEmptyNewTextLineItems_ShouldNotBuildActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getTextLineItems()).thenReturn(null); - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .textLineItems(emptyList()) - .build(); + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) + .textLineItems(emptyList()) + .build(); - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildTextLineItemsUpdateActions_WithNullOldAndNewTextLineItemsWithNullElement_ShouldNotBuildActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getTextLineItems()).thenReturn(null); + @Test + void + buildTextLineItemsUpdateActions_WithNullOldAndNewTextLineItemsWithNullElement_ShouldNotBuildActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getTextLineItems()).thenReturn(null); - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .textLineItems(singletonList(null)) - .build(); + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) + .textLineItems(singletonList(null)) + .build(); - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildTextLineItemsUpdateActions_WithNullNewLineItemsAndExistingLineItems_ShouldBuild3RemoveActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + @Test + void + buildTextLineItemsUpdateActions_WithNullNewLineItemsAndExistingLineItems_ShouldBuild3RemoveActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) - .build(); + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")).build(); - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( RemoveTextLineItem.of("text_line_item_id_1"), RemoveTextLineItem.of("text_line_item_id_2"), RemoveTextLineItem.of("text_line_item_id_3")); - } + } - @Test - void buildTextLineItemsUpdateActions_WithNewTextLineItemsAndNoOldTextLineItems_ShouldBuild3AddActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getTextLineItems()).thenReturn(null); + @Test + void + buildTextLineItemsUpdateActions_WithNewTextLineItemsAndNoOldTextLineItems_ShouldBuild3AddActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getTextLineItems()).thenReturn(null); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedTextLineItemReferences(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedTextLineItemReferences( + SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123); - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(newShoppingList.getTextLineItems()).isNotNull(); - assertThat(updateActions).containsExactly( + assertThat(newShoppingList.getTextLineItems()).isNotNull(); + assertThat(updateActions) + .containsExactly( AddTextLineItemWithAddedAt.of(newShoppingList.getTextLineItems().get(0)), AddTextLineItemWithAddedAt.of(newShoppingList.getTextLineItems().get(1)), - AddTextLineItemWithAddedAt.of(newShoppingList.getTextLineItems().get(2)) - ); - } - - @Test - void buildTextLineItemsUpdateActions_WithOnlyNewTextLineItemsWithoutValidQuantity_ShouldNotBuildAddActions() { - final ShoppingList oldShoppingList = mock(ShoppingList.class); - when(oldShoppingList.getTextLineItems()).thenReturn(null); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder - .of(LocalizedString.ofEnglish("name")) - .textLineItems(asList( + AddTextLineItemWithAddedAt.of(newShoppingList.getTextLineItems().get(2))); + } + + @Test + void + buildTextLineItemsUpdateActions_WithOnlyNewTextLineItemsWithoutValidQuantity_ShouldNotBuildAddActions() { + final ShoppingList oldShoppingList = mock(ShoppingList.class); + when(oldShoppingList.getTextLineItems()).thenReturn(null); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("name")) + .textLineItems( + asList( TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name1"), null).build(), TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name2"), 0L).build(), - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name3"), -1L).build() - )) - .build(); + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name3"), -1L).build())) + .build(); - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildLineItemsUpdateActions_WithIdenticalTextLineItems_ShouldNotBuildUpdateActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + @Test + void buildLineItemsUpdateActions_WithIdenticalTextLineItems_ShouldNotBuildUpdateActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedTextLineItemReferences(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123); + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedTextLineItemReferences( + SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123); - final List> updateActions = - buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildLineItemsUpdateActions_WithAllDifferentFields_ShouldBuildUpdateActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + @Test + void buildLineItemsUpdateActions_WithAllDifferentFields_ShouldBuildUpdateActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); - final ShoppingListDraft newShoppingList = mapToShoppingListDraftWithResolvedTextLineItemReferences( + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedTextLineItemReferences( SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123_WITH_CHANGES); - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( - ChangeTextLineItemName.of("text_line_item_id_1", + assertThat(updateActions) + .containsExactly( + ChangeTextLineItemName.of( + "text_line_item_id_1", LocalizedString.ofEnglish("newName1-EN").plus(Locale.GERMAN, "newName1-DE")), - SetTextLineItemDescription.of("text_line_item_id_1").withDescription( - LocalizedString.ofEnglish("newDesc1-EN").plus(Locale.GERMAN, "newDesc1-DE")), + SetTextLineItemDescription.of("text_line_item_id_1") + .withDescription( + LocalizedString.ofEnglish("newDesc1-EN").plus(Locale.GERMAN, "newDesc1-DE")), ChangeTextLineItemQuantity.of("text_line_item_id_1", 2L), - SetTextLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText1"), "text_line_item_id_1"), - - ChangeTextLineItemName.of("text_line_item_id_2", + SetTextLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText1"), "text_line_item_id_1"), + ChangeTextLineItemName.of( + "text_line_item_id_2", LocalizedString.ofEnglish("newName2-EN").plus(Locale.GERMAN, "newName2-DE")), - SetTextLineItemDescription.of("text_line_item_id_2").withDescription( - LocalizedString.ofEnglish("newDesc2-EN").plus(Locale.GERMAN, "newDesc2-DE")), + SetTextLineItemDescription.of("text_line_item_id_2") + .withDescription( + LocalizedString.ofEnglish("newDesc2-EN").plus(Locale.GERMAN, "newDesc2-DE")), ChangeTextLineItemQuantity.of("text_line_item_id_2", 4L), - SetTextLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText2"), "text_line_item_id_2"), - - ChangeTextLineItemName.of("text_line_item_id_3", + SetTextLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText2"), "text_line_item_id_2"), + ChangeTextLineItemName.of( + "text_line_item_id_3", LocalizedString.ofEnglish("newName3-EN").plus(Locale.GERMAN, "newName3-DE")), - SetTextLineItemDescription.of("text_line_item_id_3").withDescription( - LocalizedString.ofEnglish("newDesc3-EN").plus(Locale.GERMAN, "newDesc3-DE")), + SetTextLineItemDescription.of("text_line_item_id_3") + .withDescription( + LocalizedString.ofEnglish("newDesc3-EN").plus(Locale.GERMAN, "newDesc3-DE")), ChangeTextLineItemQuantity.of("text_line_item_id_3", 6L), - SetTextLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText3"), "text_line_item_id_3") - ); - } - - @Test - void buildTextLineItemsUpdateActions_WithOneMissingTextLineItem_ShouldBuildOneRemoveTextLineItemAction() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedTextLineItemReferences(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_12); - - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - assertThat(updateActions).containsExactly( - RemoveTextLineItem.of("text_line_item_id_3") - ); - } - - @Test - void buildTextLineItemsUpdateActions_WithOneExtraTextLineItem_ShouldBuildAddTextLineItemAction() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedTextLineItemReferences(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_1234); - - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); - - final TextLineItemDraft expectedLineItemDraft = - TextLineItemDraftBuilder - .of(LocalizedString.ofEnglish("name4-EN").plus(Locale.GERMAN, "name4-DE"), 4L) - .description(LocalizedString.ofEnglish("desc4-EN").plus(Locale.GERMAN, "desc4-DE")) - .custom(CustomFieldsDraft.ofTypeIdAndJson("custom_type_id", - singletonMap("textField", - JsonNodeFactory.instance.textNode("text4")))) - .addedAt(ZonedDateTime.parse("2020-11-06T10:00:00.000Z")) - .build(); - - assertThat(updateActions).containsExactly( - AddTextLineItemWithAddedAt.of(expectedLineItemDraft) - ); - } - - @Test - void buildTextLineItemsUpdateAction_WithTextLineItemWithNullName_ShouldTriggerErrorCallback() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); - - final TextLineItemDraft updatedTextLineItemDraft1 = TextLineItemDraftBuilder - .of(LocalizedString.ofEnglish("newName1-EN").plus(Locale.GERMAN, "newName1-DE"), 2L) - .description(LocalizedString.ofEnglish("newDesc1-EN").plus(Locale.GERMAN, "newDesc1-DE")) - .custom(CustomFieldsDraft.ofTypeIdAndJson("custom_type_id", - singletonMap("textField", - JsonNodeFactory.instance.textNode("newText1")))) + SetTextLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText3"), "text_line_item_id_3")); + } + + @Test + void + buildTextLineItemsUpdateActions_WithOneMissingTextLineItem_ShouldBuildOneRemoveTextLineItemAction() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedTextLineItemReferences( + SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_12); + + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + assertThat(updateActions).containsExactly(RemoveTextLineItem.of("text_line_item_id_3")); + } + + @Test + void buildTextLineItemsUpdateActions_WithOneExtraTextLineItem_ShouldBuildAddTextLineItemAction() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedTextLineItemReferences( + SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_1234); + + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + final TextLineItemDraft expectedLineItemDraft = + TextLineItemDraftBuilder.of( + LocalizedString.ofEnglish("name4-EN").plus(Locale.GERMAN, "name4-DE"), 4L) + .description(LocalizedString.ofEnglish("desc4-EN").plus(Locale.GERMAN, "desc4-DE")) + .custom( + CustomFieldsDraft.ofTypeIdAndJson( + "custom_type_id", + singletonMap("textField", JsonNodeFactory.instance.textNode("text4")))) + .addedAt(ZonedDateTime.parse("2020-11-06T10:00:00.000Z")) + .build(); + + assertThat(updateActions).containsExactly(AddTextLineItemWithAddedAt.of(expectedLineItemDraft)); + } + + @Test + void buildTextLineItemsUpdateAction_WithTextLineItemWithNullName_ShouldTriggerErrorCallback() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + + final TextLineItemDraft updatedTextLineItemDraft1 = + TextLineItemDraftBuilder.of( + LocalizedString.ofEnglish("newName1-EN").plus(Locale.GERMAN, "newName1-DE"), 2L) + .description( + LocalizedString.ofEnglish("newDesc1-EN").plus(Locale.GERMAN, "newDesc1-DE")) + .custom( + CustomFieldsDraft.ofTypeIdAndJson( + "custom_type_id", + singletonMap("textField", JsonNodeFactory.instance.textNode("newText1")))) + .build(); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppinglist")) + .key("key") + .textLineItems( + asList(updatedTextLineItemDraft1, TextLineItemDraftBuilder.of(null, 1L).build())) + .build(); + + final List errors = new ArrayList<>(); + final List> updateActionsBeforeCallback = new ArrayList<>(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errors.add(exception.getMessage()); + updateActionsBeforeCallback.addAll(updateActions); + }) .build(); - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppinglist")) - .key("key") - .textLineItems(asList( - updatedTextLineItemDraft1, - TextLineItemDraftBuilder.of(null, 1L).build())) - .build(); - - final List errors = new ArrayList<>(); - final List> updateActionsBeforeCallback = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errors.add(exception.getMessage()); - updateActionsBeforeCallback.addAll(updateActions); - }) - .build(); - - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); - - assertThat(updateActions).isEmpty(); - assertThat(updateActionsBeforeCallback).containsExactly( - ChangeTextLineItemName.of("text_line_item_id_1", + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(updateActionsBeforeCallback) + .containsExactly( + ChangeTextLineItemName.of( + "text_line_item_id_1", LocalizedString.ofEnglish("newName1-EN").plus(Locale.GERMAN, "newName1-DE")), - SetTextLineItemDescription.of("text_line_item_id_1").withDescription( - LocalizedString.ofEnglish("newDesc1-EN").plus(Locale.GERMAN, "newDesc1-DE")), + SetTextLineItemDescription.of("text_line_item_id_1") + .withDescription( + LocalizedString.ofEnglish("newDesc1-EN").plus(Locale.GERMAN, "newDesc1-DE")), ChangeTextLineItemQuantity.of("text_line_item_id_1", 2L), - SetTextLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText1"), "text_line_item_id_1")); + SetTextLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText1"), "text_line_item_id_1")); - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo("TextLineItemDraft at position '1' of the ShoppingListDraft with key " - + "'key' has no name set. Please make sure all text line items have names."); - } - - @Test - void buildTextLineItemsUpdateAction_WithTextLineItemWithoutName_ShouldTriggerErrorCallback() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppinglist")) - .key("key") - .textLineItems(singletonList( - TextLineItemDraftBuilder.of(LocalizedString.of(), 1L).build())) - .build(); - - final List errors = new ArrayList<>(); - final List> updateActionsBeforeCallback = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errors.add(exception.getMessage()); - updateActionsBeforeCallback.addAll(updateActions); - }) - .build(); - - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); - - assertThat(updateActions).isEmpty(); - assertThat(updateActionsBeforeCallback).isEmpty(); - - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo("TextLineItemDraft at position '0' of the ShoppingListDraft with key " + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + "TextLineItemDraft at position '1' of the ShoppingListDraft with key " + "'key' has no name set. Please make sure all text line items have names."); - } - - @Test - void buildTextLineItemsUpdateActions_WithNewTextLineItemsWithoutValidQuantity_ShouldNotBuildAddActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); - - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedTextLineItemReferences(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123); + } + + @Test + void buildTextLineItemsUpdateAction_WithTextLineItemWithoutName_ShouldTriggerErrorCallback() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + ShoppingListDraftBuilder.of(LocalizedString.ofEnglish("shoppinglist")) + .key("key") + .textLineItems( + singletonList(TextLineItemDraftBuilder.of(LocalizedString.of(), 1L).build())) + .build(); - Objects.requireNonNull(newShoppingList.getTextLineItems()).addAll(Arrays.asList( - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name1"), null).build(), - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name2"), 0L).build(), - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name3"), -1L).build() - )); + final List errors = new ArrayList<>(); + final List> updateActionsBeforeCallback = new ArrayList<>(); - final List> updateActions = - buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errors.add(exception.getMessage()); + updateActionsBeforeCallback.addAll(updateActions); + }) + .build(); - assertThat(updateActions).isEmpty(); - } + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, syncOptions); - @Test - void buildActions_WithDifferentValuesWithTextLineItems_ShouldReturnActions() { - final ShoppingList oldShoppingList = - readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + assertThat(updateActions).isEmpty(); + assertThat(updateActionsBeforeCallback).isEmpty(); - final ShoppingListDraft newShoppingList = - mapToShoppingListDraftWithResolvedTextLineItemReferences( - SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123_WITH_CHANGES); + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + "TextLineItemDraft at position '0' of the ShoppingListDraft with key " + + "'key' has no name set. Please make sure all text line items have names."); + } + + @Test + void + buildTextLineItemsUpdateActions_WithNewTextLineItemsWithoutValidQuantity_ShouldNotBuildAddActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedTextLineItemReferences( + SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123); + + Objects.requireNonNull(newShoppingList.getTextLineItems()) + .addAll( + Arrays.asList( + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name1"), null).build(), + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name2"), 0L).build(), + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name3"), -1L).build())); + + final List> updateActions = + buildTextLineItemsUpdateActions(oldShoppingList, newShoppingList, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildActions_WithDifferentValuesWithTextLineItems_ShouldReturnActions() { + final ShoppingList oldShoppingList = + readObjectFromResource(SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123, ShoppingList.class); + + final ShoppingListDraft newShoppingList = + mapToShoppingListDraftWithResolvedTextLineItemReferences( + SHOPPING_LIST_WITH_TEXT_LINE_ITEMS_NAME_123_WITH_CHANGES); - final List> updateActions = - buildActions(oldShoppingList, newShoppingList, mock(ShoppingListSyncOptions.class)); + final List> updateActions = + buildActions(oldShoppingList, newShoppingList, mock(ShoppingListSyncOptions.class)); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( SetSlug.of(LocalizedString.ofEnglish("newSlug")), ChangeName.of(LocalizedString.ofEnglish("newName")), SetDescription.of(LocalizedString.ofEnglish("newDescription")), @@ -414,57 +436,60 @@ void buildActions_WithDifferentValuesWithTextLineItems_ShouldReturnActions() { SetCustomer.of(Reference.of(Customer.referenceTypeId(), "customer_id_2")), SetDeleteDaysAfterLastModification.of(45), SetCustomField.ofJson("textField", JsonNodeFactory.instance.textNode("newTextValue")), - - ChangeTextLineItemName.of("text_line_item_id_1", + ChangeTextLineItemName.of( + "text_line_item_id_1", LocalizedString.ofEnglish("newName1-EN").plus(Locale.GERMAN, "newName1-DE")), - SetTextLineItemDescription.of("text_line_item_id_1").withDescription( - LocalizedString.ofEnglish("newDesc1-EN").plus(Locale.GERMAN, "newDesc1-DE")), + SetTextLineItemDescription.of("text_line_item_id_1") + .withDescription( + LocalizedString.ofEnglish("newDesc1-EN").plus(Locale.GERMAN, "newDesc1-DE")), ChangeTextLineItemQuantity.of("text_line_item_id_1", 2L), - SetTextLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText1"), "text_line_item_id_1"), - - ChangeTextLineItemName.of("text_line_item_id_2", + SetTextLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText1"), "text_line_item_id_1"), + ChangeTextLineItemName.of( + "text_line_item_id_2", LocalizedString.ofEnglish("newName2-EN").plus(Locale.GERMAN, "newName2-DE")), - SetTextLineItemDescription.of("text_line_item_id_2").withDescription( - LocalizedString.ofEnglish("newDesc2-EN").plus(Locale.GERMAN, "newDesc2-DE")), + SetTextLineItemDescription.of("text_line_item_id_2") + .withDescription( + LocalizedString.ofEnglish("newDesc2-EN").plus(Locale.GERMAN, "newDesc2-DE")), ChangeTextLineItemQuantity.of("text_line_item_id_2", 4L), - SetTextLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText2"), "text_line_item_id_2"), - - ChangeTextLineItemName.of("text_line_item_id_3", + SetTextLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText2"), "text_line_item_id_2"), + ChangeTextLineItemName.of( + "text_line_item_id_3", LocalizedString.ofEnglish("newName3-EN").plus(Locale.GERMAN, "newName3-DE")), - SetTextLineItemDescription.of("text_line_item_id_3").withDescription( - LocalizedString.ofEnglish("newDesc3-EN").plus(Locale.GERMAN, "newDesc3-DE")), + SetTextLineItemDescription.of("text_line_item_id_3") + .withDescription( + LocalizedString.ofEnglish("newDesc3-EN").plus(Locale.GERMAN, "newDesc3-DE")), ChangeTextLineItemQuantity.of("text_line_item_id_3", 6L), - SetTextLineItemCustomField.ofJson("textField", - JsonNodeFactory.instance.textNode("newText3"), "text_line_item_id_3") - ); - } + SetTextLineItemCustomField.ofJson( + "textField", JsonNodeFactory.instance.textNode("newText3"), "text_line_item_id_3")); + } - @Nonnull - private static ShoppingListDraft mapToShoppingListDraftWithResolvedTextLineItemReferences( - @Nonnull final String resourcePath) { + @Nonnull + private static ShoppingListDraft mapToShoppingListDraftWithResolvedTextLineItemReferences( + @Nonnull final String resourcePath) { - final ShoppingListDraft template = - ShoppingListReferenceResolutionUtils.mapToShoppingListDraft( - readObjectFromResource(resourcePath, ShoppingList.class)); + final ShoppingListDraft template = + ShoppingListReferenceResolutionUtils.mapToShoppingListDraft( + readObjectFromResource(resourcePath, ShoppingList.class)); - final ShoppingListDraftBuilder builder = ShoppingListDraftBuilder.of(template); + final ShoppingListDraftBuilder builder = ShoppingListDraftBuilder.of(template); - mapValuesToFutureOfCompletedValues( + mapValuesToFutureOfCompletedValues( Objects.requireNonNull(builder.getTextLineItems()), - textLineItemReferenceResolver::resolveReferences, toList()) - .thenApply(builder::textLineItems) - .join(); - - return builder.build(); - } - - @Nonnull - private static TypeService getMockTypeService() { - final TypeService typeService = mock(TypeService.class); - when(typeService.fetchCachedTypeId(anyString())) - .thenReturn(completedFuture(Optional.of("custom_type_id"))); - return typeService; - } + textLineItemReferenceResolver::resolveReferences, + toList()) + .thenApply(builder::textLineItems) + .join(); + + return builder.build(); + } + + @Nonnull + private static TypeService getMockTypeService() { + final TypeService typeService = mock(TypeService.class); + when(typeService.fetchCachedTypeId(anyString())) + .thenReturn(completedFuture(Optional.of("custom_type_id"))); + return typeService; + } } diff --git a/src/test/java/com/commercetools/sync/shoppinglists/utils/TextLineItemUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/shoppinglists/utils/TextLineItemUpdateActionUtilsTest.java index f9fb2a4154..540d9fbe3c 100644 --- a/src/test/java/com/commercetools/sync/shoppinglists/utils/TextLineItemUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/shoppinglists/utils/TextLineItemUpdateActionUtilsTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.shoppinglists.utils; +import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildChangeTextLineItemNameUpdateAction; +import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildChangeTextLineItemQuantityUpdateAction; +import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildSetTextLineItemDescriptionUpdateAction; +import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildTextLineItemCustomUpdateActions; +import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildTextLineItemUpdateActions; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.shoppinglists.ShoppingListSyncOptions; import com.commercetools.sync.shoppinglists.ShoppingListSyncOptionsBuilder; import com.fasterxml.jackson.databind.JsonNode; @@ -20,8 +30,6 @@ import io.sphere.sdk.types.CustomFields; import io.sphere.sdk.types.CustomFieldsDraft; import io.sphere.sdk.types.Type; -import org.junit.jupiter.api.Test; - import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; @@ -29,548 +37,551 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; - -import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildChangeTextLineItemNameUpdateAction; -import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildChangeTextLineItemQuantityUpdateAction; -import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildSetTextLineItemDescriptionUpdateAction; -import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildTextLineItemCustomUpdateActions; -import static com.commercetools.sync.shoppinglists.utils.TextLineItemUpdateActionUtils.buildTextLineItemUpdateActions; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class TextLineItemUpdateActionUtilsTest { - private static final ShoppingListSyncOptions SYNC_OPTIONS = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + private static final ShoppingListSyncOptions SYNC_OPTIONS = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final ShoppingList oldShoppingList = mock(ShoppingList.class); - final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); + final ShoppingList oldShoppingList = mock(ShoppingList.class); + final ShoppingListDraft newShoppingList = mock(ShoppingListDraft.class); - @Test - void buildTextLineItemCustomUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val")); + @Test + void buildTextLineItemCustomUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val")); - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) - .description(LocalizedString.ofEnglish("desc")) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) + .description(LocalizedString.ofEnglish("desc")) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); - final List> updateActions = buildTextLineItemCustomUpdateActions( + final List> updateActions = + buildTextLineItemCustomUpdateActions( oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildTextLineItemCustomUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); + @Test + void buildTextLineItemCustomUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) - .description(LocalizedString.ofEnglish("desc")) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) + .description(LocalizedString.ofEnglish("desc")) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); - final List> updateActions = buildTextLineItemCustomUpdateActions( + final List> updateActions = + buildTextLineItemCustomUpdateActions( oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( - SetTextLineItemCustomField.ofJson("field1", - JsonNodeFactory.instance.booleanNode(false), "text_line_item_id"), - SetTextLineItemCustomField.ofJson("field2", - JsonNodeFactory.instance.objectNode().put("es", "val2"), "text_line_item_id") - ); - } - - @Test - void buildTextLineItemCustomUpdateActions_WithNullOldValues_ShouldBuildUpdateAction() { - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val")); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getCustom()).thenReturn(null); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) - .description(LocalizedString.ofEnglish("desc")) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = buildTextLineItemCustomUpdateActions( + assertThat(updateActions) + .containsExactly( + SetTextLineItemCustomField.ofJson( + "field1", JsonNodeFactory.instance.booleanNode(false), "text_line_item_id"), + SetTextLineItemCustomField.ofJson( + "field2", + JsonNodeFactory.instance.objectNode().put("es", "val2"), + "text_line_item_id")); + } + + @Test + void buildTextLineItemCustomUpdateActions_WithNullOldValues_ShouldBuildUpdateAction() { + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val")); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getCustom()).thenReturn(null); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) + .description(LocalizedString.ofEnglish("desc")) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildTextLineItemCustomUpdateActions( oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( - SetTextLineItemCustomType.ofTypeIdAndJson("1", newCustomFieldsMap, "text_line_item_id") - ); - } - - @Test - void buildTextLineItemCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerCallback() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); - - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) - .description(LocalizedString.ofEnglish("desc")) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errors.add(exception.getMessage())) - .build(); - - final List> updateActions = buildTextLineItemCustomUpdateActions( + assertThat(updateActions) + .containsExactly( + SetTextLineItemCustomType.ofTypeIdAndJson( + "1", newCustomFieldsMap, "text_line_item_id")); + } + + @Test + void + buildTextLineItemCustomUpdateActions_WithBadCustomFieldData_ShouldNotBuildUpdateActionAndTriggerCallback() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("", newCustomFieldsMap); + + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) + .description(LocalizedString.ofEnglish("desc")) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List errors = new ArrayList<>(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errors.add(exception.getMessage())) + .build(); + + final List> updateActions = + buildTextLineItemCustomUpdateActions( oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, syncOptions); - assertThat(updateActions).isEmpty(); - assertThat(errors).hasSize(1); - assertThat(errors.get(0)) - .isEqualTo(format("Failed to build custom fields update actions on the shopping-list-text-line-item with " - + "id '%s'. Reason: Custom type ids are not set for both the old " - + "and new shopping-list-text-line-item.", oldTextLineItem.getId())); - } - - @Test - void buildTextLineItemCustomUpdateActions_WithNullValue_ShouldCorrectlyBuildAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - newCustomFieldsMap.put("field2", null); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - final String typeId = UUID.randomUUID().toString(); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); - - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) - .description(LocalizedString.ofEnglish("desc")) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errors.add(exception.getMessage())) - .build(); - - final List> updateActions = buildTextLineItemCustomUpdateActions( + assertThat(updateActions).isEmpty(); + assertThat(errors).hasSize(1); + assertThat(errors.get(0)) + .isEqualTo( + format( + "Failed to build custom fields update actions on the shopping-list-text-line-item with " + + "id '%s'. Reason: Custom type ids are not set for both the old " + + "and new shopping-list-text-line-item.", + oldTextLineItem.getId())); + } + + @Test + void buildTextLineItemCustomUpdateActions_WithNullValue_ShouldCorrectlyBuildAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put( + "field2", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + newCustomFieldsMap.put("field2", null); + + final CustomFields oldCustomFields = mock(CustomFields.class); + final String typeId = UUID.randomUUID().toString(); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); + + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) + .description(LocalizedString.ofEnglish("desc")) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List errors = new ArrayList<>(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errors.add(exception.getMessage())) + .build(); + + final List> updateActions = + buildTextLineItemCustomUpdateActions( oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, syncOptions); - assertThat(errors).isEmpty(); - assertThat(updateActions) - .containsExactly(SetTextLineItemCustomField.ofJson("field2", null, "text_line_item_id")); - } - - @Test - void buildTextLineItemCustomUpdateActions_WithNullJsonNodeValue_ShouldCorrectlyBuildAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field", JsonNodeFactory - .instance - .arrayNode() - .add(JsonNodeFactory.instance.booleanNode(false))); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field", JsonNodeFactory.instance.nullNode()); - - - final CustomFields oldCustomFields = mock(CustomFields.class); - final String typeId = UUID.randomUUID().toString(); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); - - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) - .description(LocalizedString.ofEnglish("desc")) - .addedAt(ZonedDateTime.now()) - .custom(newCustomFieldsDraft) - .build(); - - final List errors = new ArrayList<>(); - - final ShoppingListSyncOptions syncOptions = - ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errors.add(exception.getMessage())) - .build(); - - final List> updateActions = buildTextLineItemCustomUpdateActions( + assertThat(errors).isEmpty(); + assertThat(updateActions) + .containsExactly(SetTextLineItemCustomField.ofJson("field2", null, "text_line_item_id")); + } + + @Test + void buildTextLineItemCustomUpdateActions_WithNullJsonNodeValue_ShouldCorrectlyBuildAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put( + "field", + JsonNodeFactory.instance.arrayNode().add(JsonNodeFactory.instance.booleanNode(false))); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field", JsonNodeFactory.instance.nullNode()); + + final CustomFields oldCustomFields = mock(CustomFields.class); + final String typeId = UUID.randomUUID().toString(); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId(typeId)); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson(typeId, newCustomFieldsMap); + + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) + .description(LocalizedString.ofEnglish("desc")) + .addedAt(ZonedDateTime.now()) + .custom(newCustomFieldsDraft) + .build(); + + final List errors = new ArrayList<>(); + + final ShoppingListSyncOptions syncOptions = + ShoppingListSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errors.add(exception.getMessage())) + .build(); + + final List> updateActions = + buildTextLineItemCustomUpdateActions( oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, syncOptions); - assertThat(errors).isEmpty(); - assertThat(updateActions) - .containsExactly(SetTextLineItemCustomField.ofJson("field", null, "text_line_item_id")); - } - - @Test - void buildChangeTextLineItemQuantityUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final TextLineItem oldLineItem = mock(TextLineItem.class); - when(oldLineItem.getId()).thenReturn("text_line_item_id"); - when(oldLineItem.getQuantity()).thenReturn(2L); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 2L) - .addedAt(ZonedDateTime.now()) - .build(); - - final Optional> updateAction = - buildChangeTextLineItemQuantityUpdateAction(oldLineItem, newTextLineItem); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isNotPresent(); - } - - @Test - void buildChangeTextLineItemQuantityUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final TextLineItem oldLineItem = mock(TextLineItem.class); - when(oldLineItem.getId()).thenReturn("text_line_item_id"); - when(oldLineItem.getQuantity()).thenReturn(2L); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 4L) - .addedAt(ZonedDateTime.now()) - .build(); - - final UpdateAction updateAction = - buildChangeTextLineItemQuantityUpdateAction(oldLineItem, newTextLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemQuantity"); - assertThat((ChangeTextLineItemQuantity) updateAction) - .isEqualTo(ChangeTextLineItemQuantity.of("text_line_item_id", 4L)); - } - - @Test - void buildChangeTextLineItemQuantityUpdateAction_WithNewNullValue_ShouldBuildUpdateAction() { - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getQuantity()).thenReturn(2L); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), null) - .addedAt(ZonedDateTime.now()) - .build(); - - final UpdateAction updateAction = - buildChangeTextLineItemQuantityUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemQuantity"); - assertThat((ChangeTextLineItemQuantity) updateAction) - .isEqualTo(ChangeTextLineItemQuantity.of("text_line_item_id", 1L)); - } - - @Test - void buildChangeTextLineItemQuantityUpdateAction_WithNewZeroValue_ShouldBuildUpdateAction() { - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getQuantity()).thenReturn(2L); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 0L) - .addedAt(ZonedDateTime.now()) - .build(); - - final UpdateAction updateAction = - buildChangeTextLineItemQuantityUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemQuantity"); - assertThat((ChangeTextLineItemQuantity) updateAction) - .isEqualTo(ChangeTextLineItemQuantity.of("text_line_item_id", 0L)); - } - - @Test - void buildChangeTextLineItemQuantityUpdateAction_WithNewNullValueAndOldDefaultValue_ShouldNotBuildAction() { - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getQuantity()).thenReturn(1L); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), null) - .addedAt(ZonedDateTime.now()) - .build(); - - final Optional> updateAction = - buildChangeTextLineItemQuantityUpdateAction(oldTextLineItem, newTextLineItem); - - assertThat(updateAction).isNotPresent(); - } - - @Test - void buildSetTextLineItemDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("oldDescription")); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) - .description(LocalizedString.ofEnglish("newDescription")) - .build(); - - final UpdateAction updateAction = - buildSetTextLineItemDescriptionUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("setTextLineItemDescription"); - assertThat(((SetTextLineItemDescription) updateAction)) - .isEqualTo(SetTextLineItemDescription.of("text_line_item_id") - .withDescription(LocalizedString.ofEnglish("newDescription"))); - } - - @Test - void buildSetTextLineItemDescriptionUpdateAction_WithNullNewValue_ShouldBuildUpdateAction() { - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("oldDescription")); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(null, 1L) - .build(); - - final UpdateAction updateAction = - buildSetTextLineItemDescriptionUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("setTextLineItemDescription"); - assertThat(((SetTextLineItemDescription) updateAction)) - .isEqualTo(SetTextLineItemDescription.of("text_line_item_id") - .withDescription(null)); - } - - @Test - void buildSetTextLineItemDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("oldDescription")); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) - .description(LocalizedString.ofEnglish("oldDescription")) - .build(); - - final Optional> updateAction = - buildSetTextLineItemDescriptionUpdateAction(oldTextLineItem, newTextLineItem); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isNotPresent(); - } - - @Test - void buildChangeTextLineItemNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("oldName")); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("newName"), 1L) - .build(); - - final UpdateAction updateAction = - buildChangeTextLineItemNameUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemName"); - assertThat(((ChangeTextLineItemName) updateAction)).isEqualTo( + assertThat(errors).isEmpty(); + assertThat(updateActions) + .containsExactly(SetTextLineItemCustomField.ofJson("field", null, "text_line_item_id")); + } + + @Test + void buildChangeTextLineItemQuantityUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final TextLineItem oldLineItem = mock(TextLineItem.class); + when(oldLineItem.getId()).thenReturn("text_line_item_id"); + when(oldLineItem.getQuantity()).thenReturn(2L); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 2L) + .addedAt(ZonedDateTime.now()) + .build(); + + final Optional> updateAction = + buildChangeTextLineItemQuantityUpdateAction(oldLineItem, newTextLineItem); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isNotPresent(); + } + + @Test + void buildChangeTextLineItemQuantityUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final TextLineItem oldLineItem = mock(TextLineItem.class); + when(oldLineItem.getId()).thenReturn("text_line_item_id"); + when(oldLineItem.getQuantity()).thenReturn(2L); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 4L) + .addedAt(ZonedDateTime.now()) + .build(); + + final UpdateAction updateAction = + buildChangeTextLineItemQuantityUpdateAction(oldLineItem, newTextLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemQuantity"); + assertThat((ChangeTextLineItemQuantity) updateAction) + .isEqualTo(ChangeTextLineItemQuantity.of("text_line_item_id", 4L)); + } + + @Test + void buildChangeTextLineItemQuantityUpdateAction_WithNewNullValue_ShouldBuildUpdateAction() { + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getQuantity()).thenReturn(2L); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), null) + .addedAt(ZonedDateTime.now()) + .build(); + + final UpdateAction updateAction = + buildChangeTextLineItemQuantityUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemQuantity"); + assertThat((ChangeTextLineItemQuantity) updateAction) + .isEqualTo(ChangeTextLineItemQuantity.of("text_line_item_id", 1L)); + } + + @Test + void buildChangeTextLineItemQuantityUpdateAction_WithNewZeroValue_ShouldBuildUpdateAction() { + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getQuantity()).thenReturn(2L); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 0L) + .addedAt(ZonedDateTime.now()) + .build(); + + final UpdateAction updateAction = + buildChangeTextLineItemQuantityUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemQuantity"); + assertThat((ChangeTextLineItemQuantity) updateAction) + .isEqualTo(ChangeTextLineItemQuantity.of("text_line_item_id", 0L)); + } + + @Test + void + buildChangeTextLineItemQuantityUpdateAction_WithNewNullValueAndOldDefaultValue_ShouldNotBuildAction() { + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getQuantity()).thenReturn(1L); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), null) + .addedAt(ZonedDateTime.now()) + .build(); + + final Optional> updateAction = + buildChangeTextLineItemQuantityUpdateAction(oldTextLineItem, newTextLineItem); + + assertThat(updateAction).isNotPresent(); + } + + @Test + void buildSetTextLineItemDescriptionUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("oldDescription")); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) + .description(LocalizedString.ofEnglish("newDescription")) + .build(); + + final UpdateAction updateAction = + buildSetTextLineItemDescriptionUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("setTextLineItemDescription"); + assertThat(((SetTextLineItemDescription) updateAction)) + .isEqualTo( + SetTextLineItemDescription.of("text_line_item_id") + .withDescription(LocalizedString.ofEnglish("newDescription"))); + } + + @Test + void buildSetTextLineItemDescriptionUpdateAction_WithNullNewValue_ShouldBuildUpdateAction() { + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("oldDescription")); + + final TextLineItemDraft newTextLineItem = TextLineItemDraftBuilder.of(null, 1L).build(); + + final UpdateAction updateAction = + buildSetTextLineItemDescriptionUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("setTextLineItemDescription"); + assertThat(((SetTextLineItemDescription) updateAction)) + .isEqualTo(SetTextLineItemDescription.of("text_line_item_id").withDescription(null)); + } + + @Test + void buildSetTextLineItemDescriptionUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("oldDescription")); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) + .description(LocalizedString.ofEnglish("oldDescription")) + .build(); + + final Optional> updateAction = + buildSetTextLineItemDescriptionUpdateAction(oldTextLineItem, newTextLineItem); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isNotPresent(); + } + + @Test + void buildChangeTextLineItemNameUpdateAction_WithDifferentValues_ShouldBuildUpdateAction() { + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("oldName")); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("newName"), 1L).build(); + + final UpdateAction updateAction = + buildChangeTextLineItemNameUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemName"); + assertThat(((ChangeTextLineItemName) updateAction)) + .isEqualTo( ChangeTextLineItemName.of("text_line_item_id", LocalizedString.ofEnglish("newName"))); - } - - @Test - void buildChangeTextLineItemNameUpdateAction_WithNullNewValue_ShouldBuildUpdateAction() { - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("oldName")); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(null, 1L) - .build(); - - final UpdateAction updateAction = - buildChangeTextLineItemNameUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemName"); - assertThat(((ChangeTextLineItemName) updateAction)) - .isEqualTo(ChangeTextLineItemName.of("text_line_item_id", null)); - } - - @Test - void buildChangeTextLineItemNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("oldName")); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("oldName"), 1L) - .build(); - - final Optional> updateAction = - buildChangeTextLineItemNameUpdateAction(oldTextLineItem, newTextLineItem); - - assertThat(updateAction).isNotNull(); - assertThat(updateAction).isNotPresent(); - } - - @Test - void buildTextLineItemUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val")); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); - - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("name")); - when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("desc")); - when(oldTextLineItem.getQuantity()).thenReturn(1L); - when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) - .description(LocalizedString.ofEnglish("desc")) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = buildTextLineItemUpdateActions( + } + + @Test + void buildChangeTextLineItemNameUpdateAction_WithNullNewValue_ShouldBuildUpdateAction() { + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("oldName")); + + final TextLineItemDraft newTextLineItem = TextLineItemDraftBuilder.of(null, 1L).build(); + + final UpdateAction updateAction = + buildChangeTextLineItemNameUpdateAction(oldTextLineItem, newTextLineItem).orElse(null); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction.getAction()).isEqualTo("changeTextLineItemName"); + assertThat(((ChangeTextLineItemName) updateAction)) + .isEqualTo(ChangeTextLineItemName.of("text_line_item_id", null)); + } + + @Test + void buildChangeTextLineItemNameUpdateAction_WithSameValues_ShouldNotBuildUpdateAction() { + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("oldName")); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("oldName"), 1L).build(); + + final Optional> updateAction = + buildChangeTextLineItemNameUpdateAction(oldTextLineItem, newTextLineItem); + + assertThat(updateAction).isNotNull(); + assertThat(updateAction).isNotPresent(); + } + + @Test + void buildTextLineItemUpdateActions_WithSameValues_ShouldNotBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", oldCustomFieldsMap); + + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("name")); + when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("desc")); + when(oldTextLineItem.getQuantity()).thenReturn(1L); + when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("name"), 1L) + .description(LocalizedString.ofEnglish("desc")) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildTextLineItemUpdateActions( oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, SYNC_OPTIONS); - assertThat(updateActions).isEmpty(); - } - - @Test - void buildTextLineItemUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { - final Map oldCustomFieldsMap = new HashMap<>(); - oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); - oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); - - final Map newCustomFieldsMap = new HashMap<>(); - newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); - newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); - - final CustomFields oldCustomFields = mock(CustomFields.class); - when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); - when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); - - final CustomFieldsDraft newCustomFieldsDraft = - CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); - - final TextLineItem oldTextLineItem = mock(TextLineItem.class); - when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); - when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("name")); - when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("desc")); - when(oldTextLineItem.getQuantity()).thenReturn(1L); - when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); - - final TextLineItemDraft newTextLineItem = - TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("newName"), 2L) - .description(LocalizedString.ofEnglish("newDesc")) - .custom(newCustomFieldsDraft) - .build(); - - final List> updateActions = buildTextLineItemUpdateActions( + assertThat(updateActions).isEmpty(); + } + + @Test + void buildTextLineItemUpdateActions_WithDifferentValues_ShouldBuildUpdateAction() { + final Map oldCustomFieldsMap = new HashMap<>(); + oldCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(true)); + oldCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("de", "val1")); + + final Map newCustomFieldsMap = new HashMap<>(); + newCustomFieldsMap.put("field1", JsonNodeFactory.instance.booleanNode(false)); + newCustomFieldsMap.put("field2", JsonNodeFactory.instance.objectNode().put("es", "val2")); + + final CustomFields oldCustomFields = mock(CustomFields.class); + when(oldCustomFields.getType()).thenReturn(Type.referenceOfId("1")); + when(oldCustomFields.getFieldsJsonMap()).thenReturn(oldCustomFieldsMap); + + final CustomFieldsDraft newCustomFieldsDraft = + CustomFieldsDraft.ofTypeIdAndJson("1", newCustomFieldsMap); + + final TextLineItem oldTextLineItem = mock(TextLineItem.class); + when(oldTextLineItem.getId()).thenReturn("text_line_item_id"); + when(oldTextLineItem.getName()).thenReturn(LocalizedString.ofEnglish("name")); + when(oldTextLineItem.getDescription()).thenReturn(LocalizedString.ofEnglish("desc")); + when(oldTextLineItem.getQuantity()).thenReturn(1L); + when(oldTextLineItem.getCustom()).thenReturn(oldCustomFields); + + final TextLineItemDraft newTextLineItem = + TextLineItemDraftBuilder.of(LocalizedString.ofEnglish("newName"), 2L) + .description(LocalizedString.ofEnglish("newDesc")) + .custom(newCustomFieldsDraft) + .build(); + + final List> updateActions = + buildTextLineItemUpdateActions( oldShoppingList, newShoppingList, oldTextLineItem, newTextLineItem, SYNC_OPTIONS); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( ChangeTextLineItemName.of("text_line_item_id", LocalizedString.ofEnglish("newName")), - SetTextLineItemDescription.of("text_line_item_id").withDescription(LocalizedString.ofEnglish("newDesc")), + SetTextLineItemDescription.of("text_line_item_id") + .withDescription(LocalizedString.ofEnglish("newDesc")), ChangeTextLineItemQuantity.of("text_line_item_id", 2L), - SetTextLineItemCustomField.ofJson("field1", - JsonNodeFactory.instance.booleanNode(false), "text_line_item_id"), - SetTextLineItemCustomField.ofJson("field2", - JsonNodeFactory.instance.objectNode().put("es", "val2"), "text_line_item_id") - ); - } + SetTextLineItemCustomField.ofJson( + "field1", JsonNodeFactory.instance.booleanNode(false), "text_line_item_id"), + SetTextLineItemCustomField.ofJson( + "field2", + JsonNodeFactory.instance.objectNode().put("es", "val2"), + "text_line_item_id")); + } } diff --git a/src/test/java/com/commercetools/sync/states/StateSyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/states/StateSyncOptionsBuilderTest.java index c3f83152d0..d19059038f 100644 --- a/src/test/java/com/commercetools/sync/states/StateSyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/states/StateSyncOptionsBuilderTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.states; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -8,144 +13,131 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; - -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.Test; class StateSyncOptionsBuilderTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private StateSyncOptionsBuilder stateSyncOptionsBuilder = StateSyncOptionsBuilder.of(CTP_CLIENT); + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private StateSyncOptionsBuilder stateSyncOptionsBuilder = StateSyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateStateSyncOptionsBuilder() { + StateSyncOptionsBuilder builder = StateSyncOptionsBuilder.of(CTP_CLIENT); + + assertThat(builder).isNotNull(); + } + + @Test + void getThis_ShouldReturnBuilderInstance() { + StateSyncOptionsBuilder instance = stateSyncOptionsBuilder.getThis(); + + assertThat(instance).isNotNull(); + assertThat(instance).isInstanceOf(StateSyncOptionsBuilder.class); + } - @Test - void of_WithClient_ShouldCreateStateSyncOptionsBuilder() { - StateSyncOptionsBuilder builder = StateSyncOptionsBuilder.of(CTP_CLIENT); - - assertThat(builder).isNotNull(); - } + @Test + void build_WithClient_ShouldBuildSyncOptions() { + StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); - @Test - void getThis_ShouldReturnBuilderInstance() { - StateSyncOptionsBuilder instance = stateSyncOptionsBuilder.getThis(); + assertThat(stateSyncOptions).isNotNull(); + assertAll( + () -> assertThat(stateSyncOptions.getBeforeUpdateCallback()).isNull(), + () -> assertThat(stateSyncOptions.getBeforeCreateCallback()).isNull(), + () -> assertThat(stateSyncOptions.getErrorCallback()).isNull(), + () -> assertThat(stateSyncOptions.getWarningCallback()).isNull(), + () -> assertThat(stateSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT), + () -> + assertThat(stateSyncOptions.getBatchSize()) + .isEqualTo(StateSyncOptionsBuilder.BATCH_SIZE_DEFAULT), + () -> assertThat(stateSyncOptions.getCacheSize()).isEqualTo(10_000)); + } - assertThat(instance).isNotNull(); - assertThat(instance).isInstanceOf(StateSyncOptionsBuilder.class); - } + @Test + void build_WithBeforeUpdateCallback_ShouldSetBeforeUpdateCallback() { + final TriFunction>, StateDraft, State, List>> + beforeUpdateCallback = (updateActions, newState, oldState) -> emptyList(); + stateSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - @Test - void build_WithClient_ShouldBuildSyncOptions() { - StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); + StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); - assertThat(stateSyncOptions).isNotNull(); - assertAll( - () -> assertThat(stateSyncOptions.getBeforeUpdateCallback()).isNull(), - () -> assertThat(stateSyncOptions.getBeforeCreateCallback()).isNull(), - () -> assertThat(stateSyncOptions.getErrorCallback()).isNull(), - () -> assertThat(stateSyncOptions.getWarningCallback()).isNull(), - () -> assertThat(stateSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT), - () -> assertThat(stateSyncOptions.getBatchSize()).isEqualTo(StateSyncOptionsBuilder.BATCH_SIZE_DEFAULT), - () -> assertThat(stateSyncOptions.getCacheSize()).isEqualTo(10_000) - ); - } + assertThat(stateSyncOptions.getBeforeUpdateCallback()).isNotNull(); + } - @Test - void build_WithBeforeUpdateCallback_ShouldSetBeforeUpdateCallback() { - final TriFunction>, StateDraft, State, List>> - beforeUpdateCallback = (updateActions, newState, oldState) -> emptyList(); - stateSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); + @Test + void build_WithBeforeCreateCallback_ShouldSetBeforeCreateCallback() { + stateSyncOptionsBuilder.beforeCreateCallback((newState) -> null); - StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); + StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); - assertThat(stateSyncOptions.getBeforeUpdateCallback()).isNotNull(); - } + assertThat(stateSyncOptions.getBeforeCreateCallback()).isNotNull(); + } - @Test - void build_WithBeforeCreateCallback_ShouldSetBeforeCreateCallback() { - stateSyncOptionsBuilder.beforeCreateCallback((newState) -> null); + @Test + void build_WithErrorCallback_ShouldSetErrorCallback() { + final QuadConsumer< + SyncException, Optional, Optional, List>> + mockErrorCallback = (exception, newDraft, old, actions) -> {}; + stateSyncOptionsBuilder.errorCallback(mockErrorCallback); - StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); + StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); - assertThat(stateSyncOptions.getBeforeCreateCallback()).isNotNull(); - } + assertThat(stateSyncOptions.getErrorCallback()).isNotNull(); + } - @Test - void build_WithErrorCallback_ShouldSetErrorCallback() { - final QuadConsumer, Optional, - List>> mockErrorCallback = (exception, newDraft, old, actions) -> { }; - stateSyncOptionsBuilder.errorCallback(mockErrorCallback); + @Test + void build_WithWarningCallback_ShouldSetWarningCallback() { + final TriConsumer, Optional> mockWarningCallback = + (exception, newDraft, old) -> {}; + stateSyncOptionsBuilder.warningCallback(mockWarningCallback); - StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); + StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); - assertThat(stateSyncOptions.getErrorCallback()).isNotNull(); - } + assertThat(stateSyncOptions.getWarningCallback()).isNotNull(); + } - @Test - void build_WithWarningCallback_ShouldSetWarningCallback() { - final TriConsumer, Optional> mockWarningCallback = - (exception, newDraft, old) -> { }; - stateSyncOptionsBuilder.warningCallback(mockWarningCallback); + @Test + void build_WithBatchSize_ShouldSetBatchSize() { + StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); - StateSyncOptions stateSyncOptions = stateSyncOptionsBuilder.build(); + assertThat(stateSyncOptions.getBatchSize()).isEqualTo(10); + } - assertThat(stateSyncOptions.getWarningCallback()).isNotNull(); - } + @Test + void build_WithInvalidBatchSize_ShouldBuildSyncOptions() { + StateSyncOptions stateSyncOptionsWithZeroBatchSize = + StateSyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); - @Test - void build_WithBatchSize_ShouldSetBatchSize() { - StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(10) - .build(); + assertThat(stateSyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(StateSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(stateSyncOptions.getBatchSize()).isEqualTo(10); - } + StateSyncOptions stateSyncOptionsWithNegativeBatchSize = + StateSyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); - @Test - void build_WithInvalidBatchSize_ShouldBuildSyncOptions() { - StateSyncOptions stateSyncOptionsWithZeroBatchSize = StateSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(0) - .build(); + assertThat(stateSyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(StateSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } - assertThat(stateSyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(StateSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + @Test + void build_WithCacheSize_ShouldSetCacheSize() { + StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); - StateSyncOptions stateSyncOptionsWithNegativeBatchSize = StateSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(-100) - .build(); + assertThat(stateSyncOptions.getCacheSize()).isEqualTo(10); + } - assertThat(stateSyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(StateSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } + @Test + void build_WithZeroOrNegativeCacheSize_ShouldBuildSyncOptions() { + StateSyncOptions stateSyncOptionsWithZeroCacheSize = + StateSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); - @Test - void build_WithCacheSize_ShouldSetCacheSize() { - StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(10) - .build(); - - assertThat(stateSyncOptions.getCacheSize()).isEqualTo(10); - } - - @Test - void build_WithZeroOrNegativeCacheSize_ShouldBuildSyncOptions() { - StateSyncOptions stateSyncOptionsWithZeroCacheSize = StateSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(0) - .build(); + assertThat(stateSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - assertThat(stateSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - - StateSyncOptions stateSyncOptionsWithNegativeCacheSize = StateSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(-100) - .build(); - - assertThat(stateSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } + StateSyncOptions stateSyncOptionsWithNegativeCacheSize = + StateSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); + assertThat(stateSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/states/StateSyncOptionsTest.java b/src/test/java/com/commercetools/sync/states/StateSyncOptionsTest.java index d1a7bee62c..2e99546bd1 100644 --- a/src/test/java/com/commercetools/sync/states/StateSyncOptionsTest.java +++ b/src/test/java/com/commercetools/sync/states/StateSyncOptionsTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.states; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.utils.TriFunction; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; @@ -7,127 +17,117 @@ import io.sphere.sdk.states.StateDraft; import io.sphere.sdk.states.StateDraftBuilder; import io.sphere.sdk.states.commands.updateactions.ChangeInitial; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class StateSyncOptionsTest { - private static SphereClient CTP_CLIENT = mock(SphereClient.class); - - @Test - void applyBeforeUpdateCallback_WithNullCallback_ShouldReturnIdenticalList() { - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT) - .build(); - final List> updateActions = singletonList(ChangeInitial.of(false)); - - final List> filteredList = - stateSyncOptions.applyBeforeUpdateCallback(updateActions, mock(StateDraft.class), mock(State.class)); - - assertThat(filteredList).isSameAs(updateActions); - } - - @Test - void applyBeforeUpdateCallback_WithNullReturnCallback_ShouldReturnEmptyList() { - final TriFunction>, StateDraft, State, List>> - beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - final List> updateActions = singletonList(ChangeInitial.of(false)); - - final List> filteredList = - stateSyncOptions.applyBeforeUpdateCallback(updateActions, mock(StateDraft.class), mock(State.class)); - - assertAll( - () -> assertThat(filteredList).isNotEqualTo(updateActions), - () -> assertThat(filteredList).isEmpty() - ); - } - - private interface MockTriFunction extends - TriFunction>, StateDraft, State, List>> { - } - - @Test - void applyBeforeUpdateCallback_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final StateSyncOptionsTest.MockTriFunction beforeUpdateCallback = - mock(StateSyncOptionsTest.MockTriFunction.class); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - final List> filteredList = - stateSyncOptions.applyBeforeUpdateCallback(emptyList(), mock(StateDraft.class), mock(State.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - @Test - void applyBeforeUpdateCallback_WithCallback_ShouldReturnFilteredList() { - final TriFunction>, StateDraft, State, List>> - beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> emptyList(); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - final List> updateActions = singletonList(ChangeInitial.of(false)); - - final List> filteredList = - stateSyncOptions.applyBeforeUpdateCallback(updateActions, mock(StateDraft.class), mock(State.class)); - - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeCreateCallback_WithCallback_ShouldReturnFilteredDraft() { - final Function draftFunction = - stateDraft -> StateDraftBuilder.of(stateDraft).key(stateDraft.getKey() + "_filteredKey").build(); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - final StateDraft resourceDraft = mock(StateDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); - - final Optional filteredDraft = stateSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).hasValueSatisfying(stateDraft -> - assertThat(stateDraft.getKey()).isEqualTo("myKey_filteredKey")); - } - - @Test - void applyBeforeCreateCallback_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT).build(); - final StateDraft resourceDraft = mock(StateDraft.class); - - final Optional filteredDraft = stateSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).containsSame(resourceDraft); - } - - @Test - void applyBeforeCreateCallback_WithCallbackReturningNull_ShouldReturnEmptyOptional() { - final Function draftFunction = stateDraft -> null; - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - final StateDraft resourceDraft = mock(StateDraft.class); - - final Optional filteredDraft = stateSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isEmpty(); - } - + private static SphereClient CTP_CLIENT = mock(SphereClient.class); + + @Test + void applyBeforeUpdateCallback_WithNullCallback_ShouldReturnIdenticalList() { + final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT).build(); + final List> updateActions = singletonList(ChangeInitial.of(false)); + + final List> filteredList = + stateSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(StateDraft.class), mock(State.class)); + + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void applyBeforeUpdateCallback_WithNullReturnCallback_ShouldReturnEmptyList() { + final TriFunction>, StateDraft, State, List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_CLIENT).beforeUpdateCallback(beforeUpdateCallback).build(); + final List> updateActions = singletonList(ChangeInitial.of(false)); + + final List> filteredList = + stateSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(StateDraft.class), mock(State.class)); + + assertAll( + () -> assertThat(filteredList).isNotEqualTo(updateActions), + () -> assertThat(filteredList).isEmpty()); + } + + private interface MockTriFunction + extends TriFunction< + List>, StateDraft, State, List>> {} + + @Test + void applyBeforeUpdateCallback_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final StateSyncOptionsTest.MockTriFunction beforeUpdateCallback = + mock(StateSyncOptionsTest.MockTriFunction.class); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_CLIENT).beforeUpdateCallback(beforeUpdateCallback).build(); + + final List> filteredList = + stateSyncOptions.applyBeforeUpdateCallback( + emptyList(), mock(StateDraft.class), mock(State.class)); + + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeUpdateCallback_WithCallback_ShouldReturnFilteredList() { + final TriFunction>, StateDraft, State, List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> emptyList(); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_CLIENT).beforeUpdateCallback(beforeUpdateCallback).build(); + final List> updateActions = singletonList(ChangeInitial.of(false)); + + final List> filteredList = + stateSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(StateDraft.class), mock(State.class)); + + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeCreateCallback_WithCallback_ShouldReturnFilteredDraft() { + final Function draftFunction = + stateDraft -> + StateDraftBuilder.of(stateDraft).key(stateDraft.getKey() + "_filteredKey").build(); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + final StateDraft resourceDraft = mock(StateDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); + + final Optional filteredDraft = + stateSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft) + .hasValueSatisfying( + stateDraft -> assertThat(stateDraft.getKey()).isEqualTo("myKey_filteredKey")); + } + + @Test + void applyBeforeCreateCallback_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(CTP_CLIENT).build(); + final StateDraft resourceDraft = mock(StateDraft.class); + + final Optional filteredDraft = + stateSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).containsSame(resourceDraft); + } + + @Test + void applyBeforeCreateCallback_WithCallbackReturningNull_ShouldReturnEmptyOptional() { + final Function draftFunction = stateDraft -> null; + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + final StateDraft resourceDraft = mock(StateDraft.class); + + final Optional filteredDraft = + stateSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/states/StateSyncTest.java b/src/test/java/com/commercetools/sync/states/StateSyncTest.java index f5a680e3c9..4de2273cff 100644 --- a/src/test/java/com/commercetools/sync/states/StateSyncTest.java +++ b/src/test/java/com/commercetools/sync/states/StateSyncTest.java @@ -1,5 +1,23 @@ package com.commercetools.sync.states; +import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + import com.commercetools.sync.services.StateService; import com.commercetools.sync.services.impl.StateServiceImpl; import com.commercetools.sync.states.helpers.StateSyncStatistics; @@ -11,8 +29,6 @@ import io.sphere.sdk.states.StateDraftBuilder; import io.sphere.sdk.states.StateType; import io.sphere.sdk.states.queries.StateQuery; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -20,225 +36,212 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletionException; - -import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.CompletableFuture.supplyAsync; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class StateSyncTest { - @Test - void sync_WithInvalidDrafts_ShouldCompleteWithoutAnyProcessing() { - // preparation - final SphereClient ctpClient = mock(SphereClient.class); - final List errors = new ArrayList<>(); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errors.add(exception.getMessage()); - }) + @Test + void sync_WithInvalidDrafts_ShouldCompleteWithoutAnyProcessing() { + // preparation + final SphereClient ctpClient = mock(SphereClient.class); + final List errors = new ArrayList<>(); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errors.add(exception.getMessage()); + }) .build(); - final StateService stateService = mock(StateService.class); - final StateSync stateSync = new StateSync(stateSyncOptions, stateService); + final StateService stateService = mock(StateService.class); + final StateSync stateSync = new StateSync(stateSyncOptions, stateService); - final StateDraft stateDraftWithoutKey = StateDraftBuilder - .of(null, StateType.LINE_ITEM_STATE) + final StateDraft stateDraftWithoutKey = + StateDraftBuilder.of(null, StateType.LINE_ITEM_STATE) .name(LocalizedString.ofEnglish("state-name")) .build(); - // test - final StateSyncStatistics statistics = stateSync - .sync(asList(stateDraftWithoutKey, null)) - .toCompletableFuture() - .join(); - - // assertion - verifyNoMoreInteractions(ctpClient); - verifyNoMoreInteractions(stateService); - assertThat(errors).hasSize(2); - assertThat(errors).containsExactly( + // test + final StateSyncStatistics statistics = + stateSync.sync(asList(stateDraftWithoutKey, null)).toCompletableFuture().join(); + + // assertion + verifyNoMoreInteractions(ctpClient); + verifyNoMoreInteractions(stateService); + assertThat(errors).hasSize(2); + assertThat(errors) + .containsExactly( "StateDraft with name: LocalizedString(en -> state-name) doesn't have a key. " + "Please make sure all state drafts have keys.", "StateDraft is null."); - assertThat(statistics).hasValues(2, 0, 0, 2, 0); - } - - @Test - void sync_WithErrorCachingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final StateDraft stateDraft = StateDraftBuilder - .of("state-1", StateType.LINE_ITEM_STATE) + assertThat(statistics).hasValues(2, 0, 0, 2, 0); + } + + @Test + void sync_WithErrorCachingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final StateDraft stateDraft = + StateDraftBuilder.of("state-1", StateType.LINE_ITEM_STATE).build(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final StateSyncOptions syncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); + final StateService stateService = spy(new StateServiceImpl(syncOptions)); + when(stateService.cacheKeysToIds(anySet())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); - final StateSyncOptions syncOptions = StateSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) - .build(); + final StateSync stateSync = new StateSync(syncOptions, stateService); - final StateService stateService = spy(new StateServiceImpl(syncOptions)); - when(stateService.cacheKeysToIds(anySet())) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - - final StateSync stateSync = new StateSync(syncOptions, stateService); - - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to build a cache of keys to ids.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); - }); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); - assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1); - } + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to build a cache of keys to ids.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + }); - @Test - void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - final StateDraft stateDraft = StateDraftBuilder - .of("state-1", StateType.LINE_ITEM_STATE) + assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1); + } + + @Test + void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + final StateDraft stateDraft = + StateDraftBuilder.of("state-1", StateType.LINE_ITEM_STATE).build(); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final SphereClient mockClient = mock(SphereClient.class); + when(mockClient.execute(any(StateQuery.class))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final StateSyncOptions syncOptions = + StateSyncOptionsBuilder.of(mockClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); + final StateService stateService = spy(new StateServiceImpl(syncOptions)); + final Map keyToIds = new HashMap<>(); + keyToIds.put(stateDraft.getKey(), UUID.randomUUID().toString()); + when(stateService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); - final SphereClient mockClient = mock(SphereClient.class); - when(mockClient.execute(any(StateQuery.class))) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); + final StateSync stateSync = new StateSync(syncOptions, stateService); - final StateSyncOptions syncOptions = StateSyncOptionsBuilder - .of(mockClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) - .build(); + // test + final StateSyncStatistics stateSyncStatistics = + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); - final StateService stateService = spy(new StateServiceImpl(syncOptions)); - final Map keyToIds = new HashMap<>(); - keyToIds.put(stateDraft.getKey(), UUID.randomUUID().toString()); - when(stateService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); - - final StateSync stateSync = new StateSync(syncOptions, stateService); - - // test - final StateSyncStatistics stateSyncStatistics = stateSync - .sync(singletonList(stateDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).contains("Failed to fetch existing states") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); - }); - - assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { - // preparation - final StateDraft stateDraft = StateDraftBuilder - .of("state-1", StateType.LINE_ITEM_STATE) - .build(); + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> assertThat(message).contains("Failed to fetch existing states")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + }); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); + assertThat(stateSyncStatistics).hasValues(1, 0, 0, 1); + } - final StateService stateService = mock(StateService.class); - when(stateService.cacheKeysToIds(anySet())).thenReturn(completedFuture(emptyMap())); - when(stateService.fetchMatchingStatesByKeysWithTransitions(anySet())).thenReturn(completedFuture(emptySet())); - when(stateService.createState(any())).thenReturn(completedFuture(Optional.empty())); + @Test + void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { + // preparation + final StateDraft stateDraft = + StateDraftBuilder.of("state-1", StateType.LINE_ITEM_STATE).build(); - final StateSyncOptions spyStateSyncOptions = spy(stateSyncOptions); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final StateSync stateSync = new StateSync(spyStateSyncOptions, stateService); + final StateService stateService = mock(StateService.class); + when(stateService.cacheKeysToIds(anySet())).thenReturn(completedFuture(emptyMap())); + when(stateService.fetchMatchingStatesByKeysWithTransitions(anySet())) + .thenReturn(completedFuture(emptySet())); + when(stateService.createState(any())).thenReturn(completedFuture(Optional.empty())); - // test - stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); + final StateSyncOptions spyStateSyncOptions = spy(stateSyncOptions); + + final StateSync stateSync = new StateSync(spyStateSyncOptions, stateService); - // assertion - verify(spyStateSyncOptions).applyBeforeCreateCallback(any()); - verify(spyStateSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } + // test + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); - @Test - void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { - // preparation - final StateDraft stateDraft = StateDraftBuilder - .of("state-1", StateType.LINE_ITEM_STATE) + // assertion + verify(spyStateSyncOptions).applyBeforeCreateCallback(any()); + verify(spyStateSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } + + @Test + void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { + // preparation + final StateDraft stateDraft = + StateDraftBuilder.of("state-1", StateType.LINE_ITEM_STATE) .name(LocalizedString.ofEnglish("foo")) .transitions(null) .build(); - final State mockedExistingState = mock(State.class); - when(mockedExistingState.getKey()).thenReturn(stateDraft.getKey()); - when(mockedExistingState.getName()).thenReturn(LocalizedString.ofEnglish("bar")); - when(mockedExistingState.getTransitions()).thenReturn(null); + final State mockedExistingState = mock(State.class); + when(mockedExistingState.getKey()).thenReturn(stateDraft.getKey()); + when(mockedExistingState.getName()).thenReturn(LocalizedString.ofEnglish("bar")); + when(mockedExistingState.getTransitions()).thenReturn(null); - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final StateService stateService = mock(StateService.class); - final Map keyToIds = new HashMap<>(); - keyToIds.put(stateDraft.getKey(), UUID.randomUUID().toString()); - when(stateService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); - when(stateService.fetchMatchingStatesByKeysWithTransitions(anySet())) - .thenReturn(completedFuture(singleton(mockedExistingState))); - when(stateService.updateState(any(), any())).thenReturn(completedFuture(mockedExistingState)); + final StateService stateService = mock(StateService.class); + final Map keyToIds = new HashMap<>(); + keyToIds.put(stateDraft.getKey(), UUID.randomUUID().toString()); + when(stateService.cacheKeysToIds(anySet())).thenReturn(completedFuture(keyToIds)); + when(stateService.fetchMatchingStatesByKeysWithTransitions(anySet())) + .thenReturn(completedFuture(singleton(mockedExistingState))); + when(stateService.updateState(any(), any())).thenReturn(completedFuture(mockedExistingState)); - final StateSyncOptions spyStateSyncOptions = spy(stateSyncOptions); + final StateSyncOptions spyStateSyncOptions = spy(stateSyncOptions); - final StateSync stateSync = new StateSync(spyStateSyncOptions, stateService); + final StateSync stateSync = new StateSync(spyStateSyncOptions, stateService); - // test - stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); + // test + stateSync.sync(singletonList(stateDraft)).toCompletableFuture().join(); - // assertion - verify(spyStateSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spyStateSyncOptions, never()).applyBeforeCreateCallback(any()); - } + // assertion + verify(spyStateSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spyStateSyncOptions, never()).applyBeforeCreateCallback(any()); + } } diff --git a/src/test/java/com/commercetools/sync/states/helpers/StateBatchValidatorTest.java b/src/test/java/com/commercetools/sync/states/helpers/StateBatchValidatorTest.java index 525162a871..dc2fb4a320 100644 --- a/src/test/java/com/commercetools/sync/states/helpers/StateBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/states/helpers/StateBatchValidatorTest.java @@ -1,121 +1,126 @@ package com.commercetools.sync.states.helpers; +import static com.commercetools.sync.states.helpers.StateBatchValidator.STATE_DRAFT_IS_NULL; +import static com.commercetools.sync.states.helpers.StateBatchValidator.STATE_DRAFT_KEY_NOT_SET; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.states.StateSyncOptions; import com.commercetools.sync.states.StateSyncOptionsBuilder; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.models.Reference; import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; - -import static com.commercetools.sync.states.helpers.StateBatchValidator.STATE_DRAFT_IS_NULL; -import static com.commercetools.sync.states.helpers.StateBatchValidator.STATE_DRAFT_KEY_NOT_SET; -import static java.lang.String.format; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class StateBatchValidatorTest { - private StateSyncOptions syncOptions; - private StateSyncStatistics syncStatistics; - private List errorCallBackMessages; + private StateSyncOptions syncOptions; + private StateSyncStatistics syncStatistics; + private List errorCallBackMessages; - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = StateSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, updateActions) -> - errorCallBackMessages.add(exception.getMessage())) + syncOptions = + StateSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> + errorCallBackMessages.add(exception.getMessage())) .build(); - syncStatistics = mock(StateSyncStatistics.class); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.emptyList()); - - assertThat(errorCallBackMessages).hasSize(0); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullStateDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.singletonList(null)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(STATE_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithStateDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final StateDraft StateDraft = mock(StateDraft.class); - final Set validDrafts = getValidDrafts(Collections.singletonList(StateDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format(STATE_DRAFT_KEY_NOT_SET, StateDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithStateDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { - final StateDraft stateDraft = mock(StateDraft.class); - when(stateDraft.getKey()).thenReturn(EMPTY); - final Set validDrafts = getValidDrafts(Collections.singletonList(stateDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(format(STATE_DRAFT_KEY_NOT_SET, stateDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { - final StateDraft validStateDraft = mock(StateDraft.class); - when(validStateDraft.getKey()).thenReturn("validDraftKey"); - final Set> transitionRefs = new HashSet<>(); - transitionRefs.add(State.referenceOfId("transition-key-1")); - transitionRefs.add(State.referenceOfId("transition-key-2")); - when(validStateDraft.getTransitions()).thenReturn(transitionRefs); - - final StateDraft validMainStateDraft = mock(StateDraft.class); - when(validMainStateDraft.getKey()).thenReturn("validDraftKey1"); - - final StateDraft invalidStateDraft = mock(StateDraft.class); - - final StateBatchValidator StateBatchValidator = new StateBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair - = StateBatchValidator.validateAndCollectReferencedKeys( + syncStatistics = mock(StateSyncStatistics.class); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.emptyList()); + + assertThat(errorCallBackMessages).hasSize(0); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullStateDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.singletonList(null)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(STATE_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithStateDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final StateDraft StateDraft = mock(StateDraft.class); + final Set validDrafts = getValidDrafts(Collections.singletonList(StateDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(STATE_DRAFT_KEY_NOT_SET, StateDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithStateDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { + final StateDraft stateDraft = mock(StateDraft.class); + when(stateDraft.getKey()).thenReturn(EMPTY); + final Set validDrafts = getValidDrafts(Collections.singletonList(stateDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(STATE_DRAFT_KEY_NOT_SET, stateDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithValidDrafts_ShouldReturnCorrectResults() { + final StateDraft validStateDraft = mock(StateDraft.class); + when(validStateDraft.getKey()).thenReturn("validDraftKey"); + final Set> transitionRefs = new HashSet<>(); + transitionRefs.add(State.referenceOfId("transition-key-1")); + transitionRefs.add(State.referenceOfId("transition-key-2")); + when(validStateDraft.getTransitions()).thenReturn(transitionRefs); + + final StateDraft validMainStateDraft = mock(StateDraft.class); + when(validMainStateDraft.getKey()).thenReturn("validDraftKey1"); + + final StateDraft invalidStateDraft = mock(StateDraft.class); + + final StateBatchValidator StateBatchValidator = + new StateBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + StateBatchValidator.validateAndCollectReferencedKeys( Arrays.asList(validStateDraft, invalidStateDraft, validMainStateDraft)); - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(STATE_DRAFT_KEY_NOT_SET, invalidStateDraft.getName())); - assertThat(pair.getLeft()) - .containsExactlyInAnyOrder(validStateDraft, validMainStateDraft); - assertThat(pair.getRight()) - .containsExactlyInAnyOrder("transition-key-1", "transition-key-2"); - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List stateDrafts) { - final StateBatchValidator StateBatchValidator = new StateBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = - StateBatchValidator.validateAndCollectReferencedKeys(stateDrafts); - return pair.getLeft(); - } + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(STATE_DRAFT_KEY_NOT_SET, invalidStateDraft.getName())); + assertThat(pair.getLeft()).containsExactlyInAnyOrder(validStateDraft, validMainStateDraft); + assertThat(pair.getRight()).containsExactlyInAnyOrder("transition-key-1", "transition-key-2"); + } + + @Nonnull + private Set getValidDrafts(@Nonnull final List stateDrafts) { + final StateBatchValidator StateBatchValidator = + new StateBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + StateBatchValidator.validateAndCollectReferencedKeys(stateDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/states/helpers/StateReferenceResolverTest.java b/src/test/java/com/commercetools/sync/states/helpers/StateReferenceResolverTest.java index b3ae2a71f5..f5fd0c02d1 100644 --- a/src/test/java/com/commercetools/sync/states/helpers/StateReferenceResolverTest.java +++ b/src/test/java/com/commercetools/sync/states/helpers/StateReferenceResolverTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.states.helpers; +import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; +import static java.lang.String.format; +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.ReferenceResolutionException; import com.commercetools.sync.services.StateService; import com.commercetools.sync.states.StateSyncOptions; @@ -11,196 +20,196 @@ import io.sphere.sdk.states.StateDraft; import io.sphere.sdk.states.StateDraftBuilder; import io.sphere.sdk.states.StateType; -import org.junit.jupiter.api.Test; - import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.IntStream; - -import static com.commercetools.sync.commons.helpers.BaseReferenceResolver.BLANK_ID_VALUE_ON_REFERENCE; -import static java.lang.String.format; -import static java.util.Collections.singleton; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class StateReferenceResolverTest { - @Test - void resolveReferences_WithStateKeys_ShouldResolveReferences() { - // preparation - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - final int nStates = 10; - final List states = IntStream.range(0, nStates) + @Test + void resolveReferences_WithStateKeys_ShouldResolveReferences() { + // preparation + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + final int nStates = 10; + final List states = + IntStream.range(0, nStates) .mapToObj(i -> i + "") - .map(key -> { - final State state = mock(State.class); - when(state.getKey()).thenReturn(key); - when(state.getId()).thenReturn(key); - when(state.toReference()).thenReturn(State.referenceOfId(key)); - return state; - }) + .map( + key -> { + final State state = mock(State.class); + when(state.getKey()).thenReturn(key); + when(state.getId()).thenReturn(key); + when(state.toReference()).thenReturn(State.referenceOfId(key)); + return state; + }) .collect(Collectors.toList()); - final StateService mockStateService = mock(StateService.class); - when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) - .thenReturn(CompletableFuture.completedFuture(new HashSet<>(states))); - - final Set> stateReferences = states.stream() - .map(State::toReference) - .collect(Collectors.toSet()); - - final StateDraft stateDraft = StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) - .transitions(stateReferences) - .build(); - - final StateReferenceResolver stateReferenceResolver = new StateReferenceResolver(stateSyncOptions, - mockStateService); - - // test - final StateDraft resolvedDraft = stateReferenceResolver - .resolveReferences(stateDraft) - .toCompletableFuture().join(); - - // assertion - assertThat(resolvedDraft.getTransitions()).isNotNull(); - assertThat(resolvedDraft.getTransitions()).hasSize(nStates); - assertThat(resolvedDraft.getTransitions()).hasSameElementsAs(stateReferences); - } - - @Test - void resolveReferences_WithNullStateReferences_ShouldNotResolveReferences() { - // preparation - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - final StateService mockStateService = mock(StateService.class); - when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) - .thenReturn(CompletableFuture.completedFuture(new HashSet<>())); - - final StateDraft stateDraft = StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) - .build(); - - final StateReferenceResolver stateReferenceResolver = new StateReferenceResolver(stateSyncOptions, - mockStateService); - - // test and assertion - assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) - .hasNotFailed() - .isCompletedWithValueMatching(resolvedDraft -> resolvedDraft.getTransitions() == null); - } + final StateService mockStateService = mock(StateService.class); + when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) + .thenReturn(CompletableFuture.completedFuture(new HashSet<>(states))); - @Test - void resolveReferences_WithNullIdOnStateReference_ShouldNotResolveReference() { - // preparation - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); + final Set> stateReferences = + states.stream().map(State::toReference).collect(Collectors.toSet()); - final StateService mockStateService = mock(StateService.class); - when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) - .thenReturn(CompletableFuture.completedFuture(new HashSet<>())); + final StateDraft stateDraft = + StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) + .transitions(stateReferences) + .build(); - final StateDraft stateDraft = StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) + final StateReferenceResolver stateReferenceResolver = + new StateReferenceResolver(stateSyncOptions, mockStateService); + + // test + final StateDraft resolvedDraft = + stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture().join(); + + // assertion + assertThat(resolvedDraft.getTransitions()).isNotNull(); + assertThat(resolvedDraft.getTransitions()).hasSize(nStates); + assertThat(resolvedDraft.getTransitions()).hasSameElementsAs(stateReferences); + } + + @Test + void resolveReferences_WithNullStateReferences_ShouldNotResolveReferences() { + // preparation + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + final StateService mockStateService = mock(StateService.class); + when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) + .thenReturn(CompletableFuture.completedFuture(new HashSet<>())); + + final StateDraft stateDraft = + StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE).build(); + + final StateReferenceResolver stateReferenceResolver = + new StateReferenceResolver(stateSyncOptions, mockStateService); + + // test and assertion + assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) + .hasNotFailed() + .isCompletedWithValueMatching(resolvedDraft -> resolvedDraft.getTransitions() == null); + } + + @Test + void resolveReferences_WithNullIdOnStateReference_ShouldNotResolveReference() { + // preparation + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final StateService mockStateService = mock(StateService.class); + when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) + .thenReturn(CompletableFuture.completedFuture(new HashSet<>())); + + final StateDraft stateDraft = + StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) .transitions(singleton(State.referenceOfId(null))) .build(); - final StateReferenceResolver stateReferenceResolver = new StateReferenceResolver(stateSyncOptions, - mockStateService); - - // test and assertion - assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'transition' reference on StateDraft with " - + "key:'%s'. Reason: %s", stateDraft.getKey(), BLANK_ID_VALUE_ON_REFERENCE)); - } - - @Test - void resolveReferences_WithEmptyIdOnStateReference_ShouldNotResolveReference() { - // preparation - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - - final StateService mockStateService = mock(StateService.class); - when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) - .thenReturn(CompletableFuture.completedFuture(new HashSet<>())); - - final StateDraft stateDraft = StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) + final StateReferenceResolver stateReferenceResolver = + new StateReferenceResolver(stateSyncOptions, mockStateService); + + // test and assertion + assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'transition' reference on StateDraft with " + + "key:'%s'. Reason: %s", + stateDraft.getKey(), BLANK_ID_VALUE_ON_REFERENCE)); + } + + @Test + void resolveReferences_WithEmptyIdOnStateReference_ShouldNotResolveReference() { + // preparation + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final StateService mockStateService = mock(StateService.class); + when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) + .thenReturn(CompletableFuture.completedFuture(new HashSet<>())); + + final StateDraft stateDraft = + StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) .transitions(singleton(State.referenceOfId(""))) .build(); - final StateReferenceResolver stateReferenceResolver = new StateReferenceResolver(stateSyncOptions, - mockStateService); - - // test and assertion - assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(ReferenceResolutionException.class) - .hasMessage(format("Failed to resolve 'transition' reference on StateDraft with " - + "key:'%s'. Reason: %s", stateDraft.getKey(), BLANK_ID_VALUE_ON_REFERENCE)); - } - - @Test - void resolveReferences_WithExceptionStateFetch_ShouldNotResolveReference() { - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - - final StateService mockStateService = mock(StateService.class); - - final State state = mock(State.class); - when(state.getKey()).thenReturn("state-key"); - when(state.getId()).thenReturn("state-id"); - when(state.toReference()).thenReturn(State.referenceOfId("state-id")); - - when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) - .thenReturn(CompletableFuture.completedFuture(singleton(state))); - - final StateDraft stateDraft = StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) + final StateReferenceResolver stateReferenceResolver = + new StateReferenceResolver(stateSyncOptions, mockStateService); + + // test and assertion + assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(ReferenceResolutionException.class) + .hasMessage( + format( + "Failed to resolve 'transition' reference on StateDraft with " + + "key:'%s'. Reason: %s", + stateDraft.getKey(), BLANK_ID_VALUE_ON_REFERENCE)); + } + + @Test + void resolveReferences_WithExceptionStateFetch_ShouldNotResolveReference() { + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + + final StateService mockStateService = mock(StateService.class); + + final State state = mock(State.class); + when(state.getKey()).thenReturn("state-key"); + when(state.getId()).thenReturn("state-id"); + when(state.toReference()).thenReturn(State.referenceOfId("state-id")); + + when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) + .thenReturn(CompletableFuture.completedFuture(singleton(state))); + + final StateDraft stateDraft = + StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) .transitions(singleton(state.toReference())) .build(); - final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); - futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); - when(mockStateService.fetchMatchingStatesByKeysWithTransitions(anySet())) - .thenReturn(futureThrowingSphereException); - - final StateReferenceResolver stateReferenceResolver = new StateReferenceResolver(stateSyncOptions, - mockStateService); - - // test and assertion - assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) - .hasFailed() - .hasFailedWithThrowableThat() - .isExactlyInstanceOf(SphereException.class) - .hasMessageContaining("CTP error on fetch"); - } - - @Test - void resolveReferences_WithNullTransitionOnTransitionsList_ShouldNotFail() { - // preparation - final StateSyncOptions stateSyncOptions = StateSyncOptionsBuilder.of(mock(SphereClient.class)) - .build(); - final StateService mockStateService = mock(StateService.class); - when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) - .thenReturn(CompletableFuture.completedFuture(new HashSet<>())); - - final StateDraft stateDraft = StateDraftBuilder - .of("state-key", StateType.LINE_ITEM_STATE) + final CompletableFuture> futureThrowingSphereException = new CompletableFuture<>(); + futureThrowingSphereException.completeExceptionally(new SphereException("CTP error on fetch")); + when(mockStateService.fetchMatchingStatesByKeysWithTransitions(anySet())) + .thenReturn(futureThrowingSphereException); + + final StateReferenceResolver stateReferenceResolver = + new StateReferenceResolver(stateSyncOptions, mockStateService); + + // test and assertion + assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) + .hasFailed() + .hasFailedWithThrowableThat() + .isExactlyInstanceOf(SphereException.class) + .hasMessageContaining("CTP error on fetch"); + } + + @Test + void resolveReferences_WithNullTransitionOnTransitionsList_ShouldNotFail() { + // preparation + final StateSyncOptions stateSyncOptions = + StateSyncOptionsBuilder.of(mock(SphereClient.class)).build(); + final StateService mockStateService = mock(StateService.class); + when(mockStateService.fetchMatchingStatesByKeysWithTransitions(any())) + .thenReturn(CompletableFuture.completedFuture(new HashSet<>())); + + final StateDraft stateDraft = + StateDraftBuilder.of("state-key", StateType.LINE_ITEM_STATE) .transitions(singleton(null)) .build(); - final StateReferenceResolver stateReferenceResolver = new StateReferenceResolver(stateSyncOptions, - mockStateService); + final StateReferenceResolver stateReferenceResolver = + new StateReferenceResolver(stateSyncOptions, mockStateService); - assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) - .hasNotFailed() - .isCompleted(); - } + assertThat(stateReferenceResolver.resolveReferences(stateDraft).toCompletableFuture()) + .hasNotFailed() + .isCompleted(); + } } diff --git a/src/test/java/com/commercetools/sync/states/helpers/StateSyncStatisticsTest.java b/src/test/java/com/commercetools/sync/states/helpers/StateSyncStatisticsTest.java index 1ac7b21aca..317e7afdcb 100644 --- a/src/test/java/com/commercetools/sync/states/helpers/StateSyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/states/helpers/StateSyncStatisticsTest.java @@ -1,135 +1,137 @@ package com.commercetools.sync.states.helpers; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class StateSyncStatisticsTest { - private StateSyncStatistics stateSyncStatistics; - - @BeforeEach - void setup() { - stateSyncStatistics = new StateSyncStatistics(); - } - - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - stateSyncStatistics.incrementCreated(1); - stateSyncStatistics.incrementFailed(1); - stateSyncStatistics.incrementUpdated(1); - stateSyncStatistics.incrementProcessed(3); - - assertThat(stateSyncStatistics.getReportMessage()).isEqualTo("Summary: 3 state(s) were processed in total " - + "(1 created, 1 updated, 1 failed to sync and 0 state(s) with missing transition(s))."); - } - - @Test - void getNumberOfStatesWithMissingParents_WithEmptyMap_ShouldReturn0() { - final int result = stateSyncStatistics.getNumberOfStatesWithMissingParents(); - - assertThat(result).isZero(); - } - - @Test - void getNumberOfCategoriesWithMissingParents_WithNonEmptyNonDuplicateKeyMap_ShouldReturnCorrectValue() { - // preparation - stateSyncStatistics.addMissingDependency("parent1", "key1"); - stateSyncStatistics.addMissingDependency("parent1", "key2"); - - stateSyncStatistics.addMissingDependency("parent1", "key3"); - stateSyncStatistics.addMissingDependency("parent2", "key4"); - - // test - final int result = stateSyncStatistics.getNumberOfStatesWithMissingParents(); - - // assert - assertThat(result).isEqualTo(4); - } - - @Test - void getNumberOfCategoriesWithMissingParents_WithNonEmptyDuplicateKeyMap_ShouldReturnCorrectValue() { - // preparation - stateSyncStatistics.addMissingDependency("parent1", "key1"); - stateSyncStatistics.addMissingDependency("parent1", "key2"); - - stateSyncStatistics.addMissingDependency("parent1", "key3"); - stateSyncStatistics.addMissingDependency("parent2", "key1"); - - // test - final int result = stateSyncStatistics.getNumberOfStatesWithMissingParents(); - - // assert - assertThat(result).isEqualTo(3); - } - - @Test - void addMissingDependency_WithEmptyParentsAndChildren_ShouldAddKeys() { - // preparation - stateSyncStatistics.addMissingDependency("", ""); - stateSyncStatistics.addMissingDependency("foo", ""); - stateSyncStatistics.addMissingDependency("", ""); - - // test - final int result = stateSyncStatistics.getNumberOfStatesWithMissingParents(); - - // assert - assertThat(result).isOne(); - } - - @Test - void removeAndGetReferencingKeys_WithEmptyMap_ShouldGetNull() { - // test - final Set result = stateSyncStatistics.removeAndGetReferencingKeys("foo"); - - // assert - assertThat(result).isNull(); - } - - @Test - void removeAndGetReferencingKeys_WithNonExistingKey_ShouldGetAndRemoveNoKey() { - // preparation - stateSyncStatistics.addMissingDependency("bar", "a"); - stateSyncStatistics.addMissingDependency("foo", "b"); - - // test - final Set result = stateSyncStatistics.removeAndGetReferencingKeys("x"); - - // assert - assertThat(result).isNull(); - assertThat(stateSyncStatistics.getNumberOfStatesWithMissingParents()).isEqualTo(2); - } - - @Test - void removeAndGetReferencingKeys_WithExistingKey_ShouldGetAndRemoveKey() { - // preparation - stateSyncStatistics.addMissingDependency("bar", "a"); - stateSyncStatistics.addMissingDependency("foo", "b"); - - // test - final Set result = stateSyncStatistics.removeAndGetReferencingKeys("foo"); - - // assert - assertThat(result).containsExactly("b"); - assertThat(stateSyncStatistics.getNumberOfStatesWithMissingParents()).isEqualTo(1); - } - - @Test - void removeAndGetReferencingKeys_WithExistingKey_ShouldGetAndRemoveAllKeys() { - // preparation - stateSyncStatistics.addMissingDependency("bar", "a"); - stateSyncStatistics.addMissingDependency("foo", "b"); - stateSyncStatistics.addMissingDependency("foo", "c"); - - // test - final Set result = stateSyncStatistics.removeAndGetReferencingKeys("foo"); - - // assert - assertThat(result).containsExactly("b", "c"); - assertThat(stateSyncStatistics.getNumberOfStatesWithMissingParents()).isEqualTo(1); - } - + private StateSyncStatistics stateSyncStatistics; + + @BeforeEach + void setup() { + stateSyncStatistics = new StateSyncStatistics(); + } + + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + stateSyncStatistics.incrementCreated(1); + stateSyncStatistics.incrementFailed(1); + stateSyncStatistics.incrementUpdated(1); + stateSyncStatistics.incrementProcessed(3); + + assertThat(stateSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 3 state(s) were processed in total " + + "(1 created, 1 updated, 1 failed to sync and 0 state(s) with missing transition(s))."); + } + + @Test + void getNumberOfStatesWithMissingParents_WithEmptyMap_ShouldReturn0() { + final int result = stateSyncStatistics.getNumberOfStatesWithMissingParents(); + + assertThat(result).isZero(); + } + + @Test + void + getNumberOfCategoriesWithMissingParents_WithNonEmptyNonDuplicateKeyMap_ShouldReturnCorrectValue() { + // preparation + stateSyncStatistics.addMissingDependency("parent1", "key1"); + stateSyncStatistics.addMissingDependency("parent1", "key2"); + + stateSyncStatistics.addMissingDependency("parent1", "key3"); + stateSyncStatistics.addMissingDependency("parent2", "key4"); + + // test + final int result = stateSyncStatistics.getNumberOfStatesWithMissingParents(); + + // assert + assertThat(result).isEqualTo(4); + } + + @Test + void + getNumberOfCategoriesWithMissingParents_WithNonEmptyDuplicateKeyMap_ShouldReturnCorrectValue() { + // preparation + stateSyncStatistics.addMissingDependency("parent1", "key1"); + stateSyncStatistics.addMissingDependency("parent1", "key2"); + + stateSyncStatistics.addMissingDependency("parent1", "key3"); + stateSyncStatistics.addMissingDependency("parent2", "key1"); + + // test + final int result = stateSyncStatistics.getNumberOfStatesWithMissingParents(); + + // assert + assertThat(result).isEqualTo(3); + } + + @Test + void addMissingDependency_WithEmptyParentsAndChildren_ShouldAddKeys() { + // preparation + stateSyncStatistics.addMissingDependency("", ""); + stateSyncStatistics.addMissingDependency("foo", ""); + stateSyncStatistics.addMissingDependency("", ""); + + // test + final int result = stateSyncStatistics.getNumberOfStatesWithMissingParents(); + + // assert + assertThat(result).isOne(); + } + + @Test + void removeAndGetReferencingKeys_WithEmptyMap_ShouldGetNull() { + // test + final Set result = stateSyncStatistics.removeAndGetReferencingKeys("foo"); + + // assert + assertThat(result).isNull(); + } + + @Test + void removeAndGetReferencingKeys_WithNonExistingKey_ShouldGetAndRemoveNoKey() { + // preparation + stateSyncStatistics.addMissingDependency("bar", "a"); + stateSyncStatistics.addMissingDependency("foo", "b"); + + // test + final Set result = stateSyncStatistics.removeAndGetReferencingKeys("x"); + + // assert + assertThat(result).isNull(); + assertThat(stateSyncStatistics.getNumberOfStatesWithMissingParents()).isEqualTo(2); + } + + @Test + void removeAndGetReferencingKeys_WithExistingKey_ShouldGetAndRemoveKey() { + // preparation + stateSyncStatistics.addMissingDependency("bar", "a"); + stateSyncStatistics.addMissingDependency("foo", "b"); + + // test + final Set result = stateSyncStatistics.removeAndGetReferencingKeys("foo"); + + // assert + assertThat(result).containsExactly("b"); + assertThat(stateSyncStatistics.getNumberOfStatesWithMissingParents()).isEqualTo(1); + } + + @Test + void removeAndGetReferencingKeys_WithExistingKey_ShouldGetAndRemoveAllKeys() { + // preparation + stateSyncStatistics.addMissingDependency("bar", "a"); + stateSyncStatistics.addMissingDependency("foo", "b"); + stateSyncStatistics.addMissingDependency("foo", "c"); + + // test + final Set result = stateSyncStatistics.removeAndGetReferencingKeys("foo"); + + // assert + assertThat(result).containsExactly("b", "c"); + assertThat(stateSyncStatistics.getNumberOfStatesWithMissingParents()).isEqualTo(1); + } } diff --git a/src/test/java/com/commercetools/sync/states/utils/StateReferenceResolutionUtilsTest.java b/src/test/java/com/commercetools/sync/states/utils/StateReferenceResolutionUtilsTest.java index 4070ebf7d8..1adaa42f85 100644 --- a/src/test/java/com/commercetools/sync/states/utils/StateReferenceResolutionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/states/utils/StateReferenceResolutionUtilsTest.java @@ -1,114 +1,118 @@ package com.commercetools.sync.states.utils; +import static com.commercetools.sync.states.utils.StateReferenceResolutionUtils.buildStateQuery; +import static com.commercetools.sync.states.utils.StateReferenceResolutionUtils.mapToStateDrafts; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.expansion.ExpansionPath; import io.sphere.sdk.models.Reference; import io.sphere.sdk.states.State; import io.sphere.sdk.states.StateDraft; import io.sphere.sdk.states.queries.StateQuery; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; - -import static com.commercetools.sync.states.utils.StateReferenceResolutionUtils.buildStateQuery; -import static com.commercetools.sync.states.utils.StateReferenceResolutionUtils.mapToStateDrafts; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; class StateReferenceResolutionUtilsTest { - @Test - void mapToStateDrafts_WithAllExpandedReferences_ShouldReturnReferencesWithReplacedKeys() { - //preparation - final List mockStates = new ArrayList<>(); - final String stateReferenceKey = "state-key"; - - for (int i = 0; i < 3; i++) { - final State mockState = mock(State.class); - - final State state = getStateMock(stateReferenceKey); - final Reference expandedStateReference = - Reference.ofResourceTypeIdAndIdAndObj(State.referenceTypeId(), state.getId(), state); - when(mockState.getTransitions()).thenReturn(Collections.singleton(expandedStateReference)); - - mockStates.add(mockState); - } - - //test - final List referenceReplacedDrafts = - mapToStateDrafts(mockStates); - - //assertion - referenceReplacedDrafts.forEach(stateDraft -> { - stateDraft.getTransitions().forEach(stateReference -> { - assertThat(stateReference.getId()).isEqualTo(stateReferenceKey); - assertThat(stateReference.getObj()).isEqualTo(null); - }); - }); - } - - @Test - void mapToStateDrafts_WithAllNonExpandedReferences_ShouldReturnReferencesWithoutReplacedKeys() { - //preparation - final List mockStates = new ArrayList<>(); - final String stateReferenceKey = "state-key"; - - for (int i = 0; i < 3; i++) { - final State mockState = mock(State.class); + @Test + void mapToStateDrafts_WithAllExpandedReferences_ShouldReturnReferencesWithReplacedKeys() { + // preparation + final List mockStates = new ArrayList<>(); + final String stateReferenceKey = "state-key"; - final State state = getStateMock(stateReferenceKey); - final Reference nonExpandedStateReference = State.referenceOfId(state.getId()); - when(mockState.getTransitions()).thenReturn(Collections.singleton(nonExpandedStateReference)); + for (int i = 0; i < 3; i++) { + final State mockState = mock(State.class); - mockStates.add(mockState); - } + final State state = getStateMock(stateReferenceKey); + final Reference expandedStateReference = + Reference.ofResourceTypeIdAndIdAndObj(State.referenceTypeId(), state.getId(), state); + when(mockState.getTransitions()).thenReturn(Collections.singleton(expandedStateReference)); - //test - final List referenceReplacedDrafts = - mapToStateDrafts(mockStates); - - //assertion - referenceReplacedDrafts.forEach(stateDraft -> { - stateDraft.getTransitions().forEach(stateReference -> { - assertThat(stateReference.getId()).isNotEqualTo(stateReferenceKey); - }); - }); + mockStates.add(mockState); } - @Test - void mapToStateDrafts_WithNullOrEmptyReferences_ShouldNotFail() { - //preparation - final State mockState1 = mock(State.class); - when(mockState1.getTransitions()).thenReturn(null); + // test + final List referenceReplacedDrafts = mapToStateDrafts(mockStates); + + // assertion + referenceReplacedDrafts.forEach( + stateDraft -> { + stateDraft + .getTransitions() + .forEach( + stateReference -> { + assertThat(stateReference.getId()).isEqualTo(stateReferenceKey); + assertThat(stateReference.getObj()).isEqualTo(null); + }); + }); + } - final State mockState2 = mock(State.class); - when(mockState2.getTransitions()).thenReturn(Collections.emptySet()); + @Test + void mapToStateDrafts_WithAllNonExpandedReferences_ShouldReturnReferencesWithoutReplacedKeys() { + // preparation + final List mockStates = new ArrayList<>(); + final String stateReferenceKey = "state-key"; - //test - final List referenceReplacedDrafts = - mapToStateDrafts(Arrays.asList(mockState1, mockState2)); + for (int i = 0; i < 3; i++) { + final State mockState = mock(State.class); - assertThat(referenceReplacedDrafts.get(0).getTransitions()).isEqualTo(Collections.emptySet()); - assertThat(referenceReplacedDrafts.get(1).getTransitions()).isEqualTo(Collections.emptySet()); - } + final State state = getStateMock(stateReferenceKey); + final Reference nonExpandedStateReference = State.referenceOfId(state.getId()); + when(mockState.getTransitions()).thenReturn(Collections.singleton(nonExpandedStateReference)); - @Test - void buildStateQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { - final StateQuery stateQuery = buildStateQuery(); - assertThat(stateQuery.expansionPaths()).containsExactly(ExpansionPath.of("transitions[*]")); - } - - @Nonnull - private static State getStateMock(@Nonnull final String key) { - final State state = mock(State.class); - when(state.getKey()).thenReturn(key); - when(state.getId()).thenReturn(UUID.randomUUID().toString()); - return state; + mockStates.add(mockState); } + // test + final List referenceReplacedDrafts = mapToStateDrafts(mockStates); + + // assertion + referenceReplacedDrafts.forEach( + stateDraft -> { + stateDraft + .getTransitions() + .forEach( + stateReference -> { + assertThat(stateReference.getId()).isNotEqualTo(stateReferenceKey); + }); + }); + } + + @Test + void mapToStateDrafts_WithNullOrEmptyReferences_ShouldNotFail() { + // preparation + final State mockState1 = mock(State.class); + when(mockState1.getTransitions()).thenReturn(null); + + final State mockState2 = mock(State.class); + when(mockState2.getTransitions()).thenReturn(Collections.emptySet()); + + // test + final List referenceReplacedDrafts = + mapToStateDrafts(Arrays.asList(mockState1, mockState2)); + + assertThat(referenceReplacedDrafts.get(0).getTransitions()).isEqualTo(Collections.emptySet()); + assertThat(referenceReplacedDrafts.get(1).getTransitions()).isEqualTo(Collections.emptySet()); + } + + @Test + void buildStateQuery_Always_ShouldReturnQueryWithAllNeededReferencesExpanded() { + final StateQuery stateQuery = buildStateQuery(); + assertThat(stateQuery.expansionPaths()).containsExactly(ExpansionPath.of("transitions[*]")); + } + + @Nonnull + private static State getStateMock(@Nonnull final String key) { + final State state = mock(State.class); + when(state.getKey()).thenReturn(key); + when(state.getId()).thenReturn(UUID.randomUUID().toString()); + return state; + } } diff --git a/src/test/java/com/commercetools/sync/states/utils/StateSyncUtilsTest.java b/src/test/java/com/commercetools/sync/states/utils/StateSyncUtilsTest.java index d7ca4445a4..9f3a252132 100644 --- a/src/test/java/com/commercetools/sync/states/utils/StateSyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/states/utils/StateSyncUtilsTest.java @@ -1,5 +1,12 @@ package com.commercetools.sync.states.utils; +import static com.commercetools.sync.states.utils.StateSyncUtils.buildActions; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.states.State; @@ -12,76 +19,65 @@ import io.sphere.sdk.states.commands.updateactions.RemoveRoles; import io.sphere.sdk.states.commands.updateactions.SetDescription; import io.sphere.sdk.states.commands.updateactions.SetName; -import org.junit.jupiter.api.Test; - import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; - -import static com.commercetools.sync.states.utils.StateSyncUtils.buildActions; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class StateSyncUtilsTest { - private static final String KEY = "key1"; - - @Test - void buildActions_WithSameValues_ShouldNotBuildUpdateActions() { - final StateType type = StateType.LINE_ITEM_STATE; - final LocalizedString name = LocalizedString.of(Locale.GERMANY, "name"); - final LocalizedString description = LocalizedString.of(Locale.GERMANY, "description"); - - final State state = mock(State.class); - when(state.getKey()).thenReturn(KEY); - when(state.getType()).thenReturn(type); - when(state.getName()).thenReturn(name); - when(state.getDescription()).thenReturn(description); - when(state.isInitial()).thenReturn(true); - - final StateDraft stateDraft = StateDraft.of(KEY, type) - .withName(name) - .withDescription(description) - .withInitial(true); - - - final List> result = buildActions(state, stateDraft); - - assertThat(result).isEmpty(); - } - - @Test - void buildActions_WithDifferentValues_ShouldBuildAllUpdateActions() { - final State state = mock(State.class); - when(state.getKey()).thenReturn(KEY); - when(state.getType()).thenReturn(StateType.LINE_ITEM_STATE); - when(state.getName()).thenReturn(LocalizedString.of(Locale.GERMANY, "name")); - when(state.getDescription()).thenReturn(LocalizedString.of(Locale.GERMANY, "description")); - when(state.isInitial()).thenReturn(false); - final Set oldStateRoles = new HashSet<>(singletonList(StateRole.RETURN)); - when(state.getRoles()).thenReturn(oldStateRoles); - - final Set newStateRoles = new HashSet<>(singletonList(StateRole.REVIEW_INCLUDED_IN_STATISTICS)); - final StateDraft stateDraft = StateDraft.of(KEY, StateType.PRODUCT_STATE) + private static final String KEY = "key1"; + + @Test + void buildActions_WithSameValues_ShouldNotBuildUpdateActions() { + final StateType type = StateType.LINE_ITEM_STATE; + final LocalizedString name = LocalizedString.of(Locale.GERMANY, "name"); + final LocalizedString description = LocalizedString.of(Locale.GERMANY, "description"); + + final State state = mock(State.class); + when(state.getKey()).thenReturn(KEY); + when(state.getType()).thenReturn(type); + when(state.getName()).thenReturn(name); + when(state.getDescription()).thenReturn(description); + when(state.isInitial()).thenReturn(true); + + final StateDraft stateDraft = + StateDraft.of(KEY, type).withName(name).withDescription(description).withInitial(true); + + final List> result = buildActions(state, stateDraft); + + assertThat(result).isEmpty(); + } + + @Test + void buildActions_WithDifferentValues_ShouldBuildAllUpdateActions() { + final State state = mock(State.class); + when(state.getKey()).thenReturn(KEY); + when(state.getType()).thenReturn(StateType.LINE_ITEM_STATE); + when(state.getName()).thenReturn(LocalizedString.of(Locale.GERMANY, "name")); + when(state.getDescription()).thenReturn(LocalizedString.of(Locale.GERMANY, "description")); + when(state.isInitial()).thenReturn(false); + final Set oldStateRoles = new HashSet<>(singletonList(StateRole.RETURN)); + when(state.getRoles()).thenReturn(oldStateRoles); + + final Set newStateRoles = + new HashSet<>(singletonList(StateRole.REVIEW_INCLUDED_IN_STATISTICS)); + final StateDraft stateDraft = + StateDraft.of(KEY, StateType.PRODUCT_STATE) .withName(LocalizedString.of(Locale.GERMANY, "different name")) .withDescription(LocalizedString.of(Locale.GERMANY, "different description")) .withInitial(true) .withRoles(newStateRoles); - final List> result = buildActions(state, stateDraft); - - assertAll( - () -> assertThat(result).contains(ChangeType.of(stateDraft.getType())), - () -> assertThat(result).contains(SetName.of(stateDraft.getName())), - () -> assertThat(result).contains(SetDescription.of(stateDraft.getDescription())), - () -> assertThat(result).contains(ChangeInitial.of(stateDraft.isInitial())), - () -> assertThat(result).contains(RemoveRoles.of(oldStateRoles)), - () -> assertThat(result).contains(AddRoles.of(newStateRoles)) - ); - } + final List> result = buildActions(state, stateDraft); + assertAll( + () -> assertThat(result).contains(ChangeType.of(stateDraft.getType())), + () -> assertThat(result).contains(SetName.of(stateDraft.getName())), + () -> assertThat(result).contains(SetDescription.of(stateDraft.getDescription())), + () -> assertThat(result).contains(ChangeInitial.of(stateDraft.isInitial())), + () -> assertThat(result).contains(RemoveRoles.of(oldStateRoles)), + () -> assertThat(result).contains(AddRoles.of(newStateRoles))); + } } diff --git a/src/test/java/com/commercetools/sync/states/utils/StateUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/states/utils/StateUpdateActionUtilsTest.java index d8e995c7ed..596c12f3e9 100644 --- a/src/test/java/com/commercetools/sync/states/utils/StateUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/states/utils/StateUpdateActionUtilsTest.java @@ -1,5 +1,18 @@ package com.commercetools.sync.states.utils; +import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildChangeInitialAction; +import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildChangeTypeAction; +import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildRolesUpdateActions; +import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildSetDescriptionAction; +import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildSetNameAction; +import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildSetTransitionsAction; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.models.Reference; @@ -14,296 +27,298 @@ import io.sphere.sdk.states.commands.updateactions.SetDescription; import io.sphere.sdk.states.commands.updateactions.SetName; import io.sphere.sdk.states.commands.updateactions.SetTransitions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - - import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.Set; - -import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildChangeInitialAction; -import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildChangeTypeAction; -import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildRolesUpdateActions; -import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildSetDescriptionAction; -import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildSetNameAction; -import static com.commercetools.sync.states.utils.StateUpdateActionUtils.buildSetTransitionsAction; -import static java.util.Arrays.asList; -import static java.util.Collections.emptySet; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class StateUpdateActionUtilsTest { - private static final String KEY = "state-1"; - - private State state; - private StateDraft newSameStateDraft; - private StateDraft newDifferentStateDraft; - - @BeforeEach - void setup() { - final StateType type = StateType.LINE_ITEM_STATE; - final LocalizedString name = LocalizedString.of(Locale.GERMANY, "name"); - final LocalizedString description = LocalizedString.of(Locale.GERMANY, "description"); - final Set roles = new HashSet<>(singletonList(StateRole.REVIEW_INCLUDED_IN_STATISTICS)); - - state = mock(State.class); - when(state.getKey()).thenReturn(KEY); - when(state.getType()).thenReturn(type); - when(state.getName()).thenReturn(name); - when(state.getDescription()).thenReturn(description); - when(state.isInitial()).thenReturn(true); - when(state.getRoles()).thenReturn(roles); - - newSameStateDraft = StateDraft.of(KEY, type) + private static final String KEY = "state-1"; + + private State state; + private StateDraft newSameStateDraft; + private StateDraft newDifferentStateDraft; + + @BeforeEach + void setup() { + final StateType type = StateType.LINE_ITEM_STATE; + final LocalizedString name = LocalizedString.of(Locale.GERMANY, "name"); + final LocalizedString description = LocalizedString.of(Locale.GERMANY, "description"); + final Set roles = + new HashSet<>(singletonList(StateRole.REVIEW_INCLUDED_IN_STATISTICS)); + + state = mock(State.class); + when(state.getKey()).thenReturn(KEY); + when(state.getType()).thenReturn(type); + when(state.getName()).thenReturn(name); + when(state.getDescription()).thenReturn(description); + when(state.isInitial()).thenReturn(true); + when(state.getRoles()).thenReturn(roles); + + newSameStateDraft = + StateDraft.of(KEY, type) .withName(name) .withDescription(description) .withRoles(roles) .withInitial(true); - newDifferentStateDraft = StateDraft.of("key2", StateType.PRODUCT_STATE) + newDifferentStateDraft = + StateDraft.of("key2", StateType.PRODUCT_STATE) .withName(LocalizedString.of(Locale.GERMANY, "new name")) .withDescription(LocalizedString.of(Locale.GERMANY, "new desc")) .withRoles(new HashSet<>(singletonList(StateRole.RETURN))) .withInitial(false); - } + } - @Test - void buildChangeTypeAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeTypeAction(state, newDifferentStateDraft); + @Test + void buildChangeTypeAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildChangeTypeAction(state, newDifferentStateDraft); - assertThat(result).contains(ChangeType.of(newDifferentStateDraft.getType())); - } + assertThat(result).contains(ChangeType.of(newDifferentStateDraft.getType())); + } - @Test - void buildChangeTypeAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeTypeAction(state, newSameStateDraft); + @Test + void buildChangeTypeAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildChangeTypeAction(state, newSameStateDraft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildSetNameAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetNameAction(state, newDifferentStateDraft); + @Test + void buildSetNameAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = buildSetNameAction(state, newDifferentStateDraft); - assertThat(result).contains(SetName.of(newDifferentStateDraft.getName())); - } + assertThat(result).contains(SetName.of(newDifferentStateDraft.getName())); + } - @Test - void buildSetNameAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetNameAction(state, newSameStateDraft); + @Test + void buildSetNameAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetNameAction(state, newSameStateDraft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildSetDescriptionAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetDescriptionAction(state, newDifferentStateDraft); + @Test + void buildSetDescriptionAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetDescriptionAction(state, newDifferentStateDraft); - assertThat(result).contains(SetDescription.of(newDifferentStateDraft.getDescription())); - } + assertThat(result).contains(SetDescription.of(newDifferentStateDraft.getDescription())); + } - @Test - void buildSetDescriptionAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetDescriptionAction(state, newSameStateDraft); + @Test + void buildSetDescriptionAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = + buildSetDescriptionAction(state, newSameStateDraft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildChangeInitialAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeInitialAction(state, newDifferentStateDraft); + @Test + void buildChangeInitialAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildChangeInitialAction(state, newDifferentStateDraft); - assertThat(result).contains(ChangeInitial.of(newDifferentStateDraft.isInitial())); - } + assertThat(result).contains(ChangeInitial.of(newDifferentStateDraft.isInitial())); + } - @Test - void buildChangeInitialAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeInitialAction(state, newSameStateDraft); + @Test + void buildChangeInitialAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildChangeInitialAction(state, newSameStateDraft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildRolesUpdateActions_WithSameValues_ShouldNotBuildAction() { - final List> result = buildRolesUpdateActions(state, newSameStateDraft); + @Test + void buildRolesUpdateActions_WithSameValues_ShouldNotBuildAction() { + final List> result = buildRolesUpdateActions(state, newSameStateDraft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildRolesUpdateActions_WithDifferentValues_ShouldReturnActionWithRightOrder() { - final List> result = buildRolesUpdateActions(state, newDifferentStateDraft); + @Test + void buildRolesUpdateActions_WithDifferentValues_ShouldReturnActionWithRightOrder() { + final List> result = buildRolesUpdateActions(state, newDifferentStateDraft); - assertThat(result).containsExactly( - RemoveRoles.of(state.getRoles()), - AddRoles.of(newDifferentStateDraft.getRoles())); - } + assertThat(result) + .containsExactly( + RemoveRoles.of(state.getRoles()), AddRoles.of(newDifferentStateDraft.getRoles())); + } - @Test - void buildRolesUpdateActions_WithNewValues_ShouldReturnAction() { - StateDraft newState = StateDraft.of(KEY, StateType.PRODUCT_STATE) - .withRoles(new HashSet<>(asList(StateRole.RETURN, StateRole.REVIEW_INCLUDED_IN_STATISTICS))); + @Test + void buildRolesUpdateActions_WithNewValues_ShouldReturnAction() { + StateDraft newState = + StateDraft.of(KEY, StateType.PRODUCT_STATE) + .withRoles( + new HashSet<>(asList(StateRole.RETURN, StateRole.REVIEW_INCLUDED_IN_STATISTICS))); - final List> result = buildRolesUpdateActions(state, newState); + final List> result = buildRolesUpdateActions(state, newState); - assertThat(result).containsExactly(AddRoles.of(new HashSet<>(singletonList(StateRole.RETURN)))); - } + assertThat(result).containsExactly(AddRoles.of(new HashSet<>(singletonList(StateRole.RETURN)))); + } - @Test - void buildRolesUpdateActions_WithRemovedRoles_ShouldReturnOnlyRemoveAction() { - final Set roles = new HashSet<>(asList(StateRole.RETURN, StateRole.REVIEW_INCLUDED_IN_STATISTICS)); + @Test + void buildRolesUpdateActions_WithRemovedRoles_ShouldReturnOnlyRemoveAction() { + final Set roles = + new HashSet<>(asList(StateRole.RETURN, StateRole.REVIEW_INCLUDED_IN_STATISTICS)); - State oldState = mock(State.class); - when(oldState.getKey()).thenReturn(KEY); - when(oldState.getRoles()).thenReturn(roles); + State oldState = mock(State.class); + when(oldState.getKey()).thenReturn(KEY); + when(oldState.getRoles()).thenReturn(roles); - StateDraft newState = StateDraft.of(KEY, StateType.PRODUCT_STATE) + StateDraft newState = + StateDraft.of(KEY, StateType.PRODUCT_STATE) .withRoles(new HashSet<>(singletonList(StateRole.REVIEW_INCLUDED_IN_STATISTICS))); - final List> result = buildRolesUpdateActions(oldState, newState); - - assertThat(result).containsExactly(RemoveRoles.of(new HashSet<>(singletonList(StateRole.RETURN)))); - } - - @Test - void buildRolesUpdateActions_WithNullRoles_ShouldNotReturnAction() { - StateDraft newState = StateDraft.of(KEY, StateType.PRODUCT_STATE).withRoles((Set)null); - - State oldState = mock(State.class); - when(oldState.getKey()).thenReturn(KEY); - when(oldState.getRoles()).thenReturn(null); - - final List> result = buildRolesUpdateActions(oldState, newState); - - assertThat(result).isEmpty(); - } - - @Test - void buildRolesUpdateActions_WithEmptyRoles_ShouldNotReturnAction() { - StateDraft newState = StateDraft.of(KEY, StateType.PRODUCT_STATE).withRoles(Collections.emptySet()); - - State oldState = mock(State.class); - when(oldState.getKey()).thenReturn(KEY); - when(oldState.getRoles()).thenReturn(Collections.emptySet()); - - final List> result = buildRolesUpdateActions(oldState, newState); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetTransitionsAction_WithEmptyValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetTransitionsAction(state, newSameStateDraft); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetTransitionsAction_WithNullValuesInList_ShouldReturnEmptyOptional() { - final Set> oldTransitions = new HashSet<>(singletonList(null)); - when(state.getTransitions()).thenReturn(oldTransitions); - - final StateDraft stateDraft = StateDraft.of(KEY, StateType.LINE_ITEM_STATE) - .withTransitions(new HashSet<>(singletonList(null))); - - final Optional> result = buildSetTransitionsAction(state, stateDraft); - assertThat(result).isEmpty(); - } - - @Test - void buildSetTransitionsAction_WithEmptyNewTransitionsValues_ShouldReturnAction() { - final Set> oldTransitions = new HashSet<>(singletonList(State.referenceOfId("id"))); - when(state.getTransitions()).thenReturn(oldTransitions); - - final Optional> result = buildSetTransitionsAction(state, newSameStateDraft); - - assertThat(result).contains(SetTransitions.of(emptySet())); - } - - @Test - void buildSetTransitionsAction_WithSameValues_ShouldReturnEmptyOptional() { - final Set> oldTransitions = new HashSet<>(asList( - State.referenceOfId("state-key-1"), - State.referenceOfId("state-key-2"))); - final Set> newTransitions = new HashSet<>(asList( - State.referenceOfId("state-key-1"), - State.referenceOfId("state-key-2"))); - - when(state.getTransitions()).thenReturn(oldTransitions); - final StateDraft newDifferent = StateDraft.of(KEY, StateType.LINE_ITEM_STATE).withTransitions(newTransitions); - - final Optional> result = buildSetTransitionsAction(state, newDifferent); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetTransitionsAction_WithAllDifferentValues_ShouldReturnAction() { - final Set> oldTransitions = new HashSet<>(asList( - State.referenceOfId("old-state-1"), - State.referenceOfId("old-state-2"))); - - final Set> newTransitions = new HashSet<>(asList( - State.referenceOfId("new-state-1"), - State.referenceOfId("new-state-2"))); - - when(state.getTransitions()).thenReturn(oldTransitions); - final StateDraft newDifferent = StateDraft.of(KEY, StateType.LINE_ITEM_STATE).withTransitions(newTransitions); - - final Optional> result = buildSetTransitionsAction(state, newDifferent); - HashSet> expectedTransitions = new HashSet<>(asList( - State.referenceOfId("new-state-1"), - State.referenceOfId("new-state-2"))); - assertThat(result).contains(SetTransitions.of(expectedTransitions)); - } - - @Test - void buildSetTransitionsAction_WithAnyDifferentValues_ShouldReturnAction() { - final Set> oldTransitions = new HashSet<>(asList( - State.referenceOfId("old-state-1"), - State.referenceOfId("old-state-2"))); - - final Set> newTransitions = new HashSet<>(asList( - State.referenceOfId("old-state-1"), - State.referenceOfId("new-state-2"))); - - when(state.getTransitions()).thenReturn(oldTransitions); - final StateDraft newDifferent = StateDraft.of(KEY, StateType.LINE_ITEM_STATE).withTransitions(newTransitions); - - - - final Optional> result = buildSetTransitionsAction(state, newDifferent); - HashSet> expectedTransitions = new HashSet<>(asList( - State.referenceOfId("old-state-1"), - State.referenceOfId("new-state-2"))); - assertThat(result).contains(SetTransitions.of(expectedTransitions)); - } - - @Test - void buildSetTransitionsAction_WitNullTransitions_ShouldReturnAction() { - final Set> oldTransitions = new HashSet<>(asList( - State.referenceOfId("old-state-1"), - State.referenceOfId("old-state-2"))); - - final Set> newTransitions = new HashSet<>(asList( - null, - State.referenceOfId("new-state-2"))); + final List> result = buildRolesUpdateActions(oldState, newState); - when(state.getTransitions()).thenReturn(oldTransitions); - final StateDraft newDifferent = StateDraft.of(KEY, StateType.LINE_ITEM_STATE).withTransitions(newTransitions); + assertThat(result) + .containsExactly(RemoveRoles.of(new HashSet<>(singletonList(StateRole.RETURN)))); + } + @Test + void buildRolesUpdateActions_WithNullRoles_ShouldNotReturnAction() { + StateDraft newState = + StateDraft.of(KEY, StateType.PRODUCT_STATE).withRoles((Set) null); + + State oldState = mock(State.class); + when(oldState.getKey()).thenReturn(KEY); + when(oldState.getRoles()).thenReturn(null); + + final List> result = buildRolesUpdateActions(oldState, newState); - final Optional> result = buildSetTransitionsAction(state, newDifferent); - HashSet> expectedTransitions = new HashSet<>(singletonList( - State.referenceOfId("new-state-2"))); - assertThat(result).contains(SetTransitions.of(expectedTransitions)); - } + assertThat(result).isEmpty(); + } + @Test + void buildRolesUpdateActions_WithEmptyRoles_ShouldNotReturnAction() { + StateDraft newState = + StateDraft.of(KEY, StateType.PRODUCT_STATE).withRoles(Collections.emptySet()); + + State oldState = mock(State.class); + when(oldState.getKey()).thenReturn(KEY); + when(oldState.getRoles()).thenReturn(Collections.emptySet()); + + final List> result = buildRolesUpdateActions(oldState, newState); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetTransitionsAction_WithEmptyValues_ShouldReturnEmptyOptional() { + final Optional> result = + buildSetTransitionsAction(state, newSameStateDraft); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetTransitionsAction_WithNullValuesInList_ShouldReturnEmptyOptional() { + final Set> oldTransitions = new HashSet<>(singletonList(null)); + when(state.getTransitions()).thenReturn(oldTransitions); + + final StateDraft stateDraft = + StateDraft.of(KEY, StateType.LINE_ITEM_STATE) + .withTransitions(new HashSet<>(singletonList(null))); + + final Optional> result = buildSetTransitionsAction(state, stateDraft); + assertThat(result).isEmpty(); + } + + @Test + void buildSetTransitionsAction_WithEmptyNewTransitionsValues_ShouldReturnAction() { + final Set> oldTransitions = + new HashSet<>(singletonList(State.referenceOfId("id"))); + when(state.getTransitions()).thenReturn(oldTransitions); + + final Optional> result = + buildSetTransitionsAction(state, newSameStateDraft); + + assertThat(result).contains(SetTransitions.of(emptySet())); + } + + @Test + void buildSetTransitionsAction_WithSameValues_ShouldReturnEmptyOptional() { + final Set> oldTransitions = + new HashSet<>( + asList(State.referenceOfId("state-key-1"), State.referenceOfId("state-key-2"))); + final Set> newTransitions = + new HashSet<>( + asList(State.referenceOfId("state-key-1"), State.referenceOfId("state-key-2"))); + + when(state.getTransitions()).thenReturn(oldTransitions); + final StateDraft newDifferent = + StateDraft.of(KEY, StateType.LINE_ITEM_STATE).withTransitions(newTransitions); + + final Optional> result = buildSetTransitionsAction(state, newDifferent); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetTransitionsAction_WithAllDifferentValues_ShouldReturnAction() { + final Set> oldTransitions = + new HashSet<>( + asList(State.referenceOfId("old-state-1"), State.referenceOfId("old-state-2"))); + + final Set> newTransitions = + new HashSet<>( + asList(State.referenceOfId("new-state-1"), State.referenceOfId("new-state-2"))); + + when(state.getTransitions()).thenReturn(oldTransitions); + final StateDraft newDifferent = + StateDraft.of(KEY, StateType.LINE_ITEM_STATE).withTransitions(newTransitions); + + final Optional> result = buildSetTransitionsAction(state, newDifferent); + HashSet> expectedTransitions = + new HashSet<>( + asList(State.referenceOfId("new-state-1"), State.referenceOfId("new-state-2"))); + assertThat(result).contains(SetTransitions.of(expectedTransitions)); + } + + @Test + void buildSetTransitionsAction_WithAnyDifferentValues_ShouldReturnAction() { + final Set> oldTransitions = + new HashSet<>( + asList(State.referenceOfId("old-state-1"), State.referenceOfId("old-state-2"))); + + final Set> newTransitions = + new HashSet<>( + asList(State.referenceOfId("old-state-1"), State.referenceOfId("new-state-2"))); + + when(state.getTransitions()).thenReturn(oldTransitions); + final StateDraft newDifferent = + StateDraft.of(KEY, StateType.LINE_ITEM_STATE).withTransitions(newTransitions); + + final Optional> result = buildSetTransitionsAction(state, newDifferent); + HashSet> expectedTransitions = + new HashSet<>( + asList(State.referenceOfId("old-state-1"), State.referenceOfId("new-state-2"))); + assertThat(result).contains(SetTransitions.of(expectedTransitions)); + } + + @Test + void buildSetTransitionsAction_WitNullTransitions_ShouldReturnAction() { + final Set> oldTransitions = + new HashSet<>( + asList(State.referenceOfId("old-state-1"), State.referenceOfId("old-state-2"))); + + final Set> newTransitions = + new HashSet<>(asList(null, State.referenceOfId("new-state-2"))); + + when(state.getTransitions()).thenReturn(oldTransitions); + final StateDraft newDifferent = + StateDraft.of(KEY, StateType.LINE_ITEM_STATE).withTransitions(newTransitions); + + final Optional> result = buildSetTransitionsAction(state, newDifferent); + HashSet> expectedTransitions = + new HashSet<>(singletonList(State.referenceOfId("new-state-2"))); + assertThat(result).contains(SetTransitions.of(expectedTransitions)); + } } diff --git a/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsBuilderTest.java index 55b453e1d0..890ff067bf 100644 --- a/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsBuilderTest.java @@ -1,5 +1,10 @@ package com.commercetools.sync.taxcategories; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -8,153 +13,139 @@ import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.taxcategories.TaxCategoryDraft; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; - - -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.Test; class TaxCategorySyncOptionsBuilderTest { - private static SphereClient CTP_CLIENT = mock(SphereClient.class); - private TaxCategorySyncOptionsBuilder taxCategorySyncOptionsBuilder = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT); - - @Test - void of_WithClient_ShouldCreateTaxCategorySyncOptionsBuilder() { - final TaxCategorySyncOptionsBuilder builder = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT); - - assertThat(builder).isNotNull(); - } - - @Test - void getThis_ShouldReturnBuilderInstance() { - final TaxCategorySyncOptionsBuilder instance = taxCategorySyncOptionsBuilder.getThis(); - - assertThat(instance).isNotNull(); - assertThat(instance).isInstanceOf(TaxCategorySyncOptionsBuilder.class); - } - - @Test - void build_WithClient_ShouldBuildSyncOptions() { - final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); - - assertThat(taxCategorySyncOptions).isNotNull(); - assertAll( - () -> assertThat(taxCategorySyncOptions.getBeforeUpdateCallback()).isNull(), - () -> assertThat(taxCategorySyncOptions.getBeforeCreateCallback()).isNull(), - () -> assertThat(taxCategorySyncOptions.getErrorCallback()).isNull(), - () -> assertThat(taxCategorySyncOptions.getWarningCallback()).isNull(), - () -> assertThat(taxCategorySyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT), - () -> assertThat(taxCategorySyncOptions.getBatchSize()) + private static SphereClient CTP_CLIENT = mock(SphereClient.class); + private TaxCategorySyncOptionsBuilder taxCategorySyncOptionsBuilder = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateTaxCategorySyncOptionsBuilder() { + final TaxCategorySyncOptionsBuilder builder = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT); + + assertThat(builder).isNotNull(); + } + + @Test + void getThis_ShouldReturnBuilderInstance() { + final TaxCategorySyncOptionsBuilder instance = taxCategorySyncOptionsBuilder.getThis(); + + assertThat(instance).isNotNull(); + assertThat(instance).isInstanceOf(TaxCategorySyncOptionsBuilder.class); + } + + @Test + void build_WithClient_ShouldBuildSyncOptions() { + final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); + + assertThat(taxCategorySyncOptions).isNotNull(); + assertAll( + () -> assertThat(taxCategorySyncOptions.getBeforeUpdateCallback()).isNull(), + () -> assertThat(taxCategorySyncOptions.getBeforeCreateCallback()).isNull(), + () -> assertThat(taxCategorySyncOptions.getErrorCallback()).isNull(), + () -> assertThat(taxCategorySyncOptions.getWarningCallback()).isNull(), + () -> assertThat(taxCategorySyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT), + () -> + assertThat(taxCategorySyncOptions.getBatchSize()) .isEqualTo(TaxCategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT), - () -> assertThat(taxCategorySyncOptions.getCacheSize()).isEqualTo(10_000) - ); - } - - @Test - void build_WithBeforeUpdateCallback_ShouldSetBeforeUpdateCallback() { - final TriFunction>, TaxCategoryDraft, TaxCategory, - List>> beforeUpdateCallback = (updateActions, newTaxCategory, oldTaxCategory) -> - emptyList(); - taxCategorySyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - - final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); - - assertThat(taxCategorySyncOptions.getBeforeUpdateCallback()) - .isNotNull(); - } - - @Test - void build_WithBeforeCreateCallback_ShouldSetBeforeCreateCallback() { - taxCategorySyncOptionsBuilder.beforeCreateCallback((newTaxCategory) -> null); + () -> assertThat(taxCategorySyncOptions.getCacheSize()).isEqualTo(10_000)); + } - final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); + @Test + void build_WithBeforeUpdateCallback_ShouldSetBeforeUpdateCallback() { + final TriFunction< + List>, + TaxCategoryDraft, + TaxCategory, + List>> + beforeUpdateCallback = (updateActions, newTaxCategory, oldTaxCategory) -> emptyList(); + taxCategorySyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - assertThat(taxCategorySyncOptions.getBeforeCreateCallback()).isNotNull(); - } + final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); - @Test - void build_WithErrorCallback_ShouldSetErrorCallback() { - final QuadConsumer, Optional, - List>> mockErrorCallBack = (exception, entry, draft, actions) -> { }; - taxCategorySyncOptionsBuilder.errorCallback(mockErrorCallBack); + assertThat(taxCategorySyncOptions.getBeforeUpdateCallback()).isNotNull(); + } - final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); + @Test + void build_WithBeforeCreateCallback_ShouldSetBeforeCreateCallback() { + taxCategorySyncOptionsBuilder.beforeCreateCallback((newTaxCategory) -> null); - assertThat(taxCategorySyncOptions.getErrorCallback()).isNotNull(); - } + final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); - @Test - void build_WithWarningCallback_ShouldSetWarningCallback() { - final TriConsumer, Optional> - mockWarningCallBack = (warningMessage, draft, entry) -> { }; - taxCategorySyncOptionsBuilder.warningCallback(mockWarningCallBack); + assertThat(taxCategorySyncOptions.getBeforeCreateCallback()).isNotNull(); + } - final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); + @Test + void build_WithErrorCallback_ShouldSetErrorCallback() { + final QuadConsumer< + SyncException, + Optional, + Optional, + List>> + mockErrorCallBack = (exception, entry, draft, actions) -> {}; + taxCategorySyncOptionsBuilder.errorCallback(mockErrorCallBack); - assertThat(taxCategorySyncOptions.getWarningCallback()).isNotNull(); - } + final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); + assertThat(taxCategorySyncOptions.getErrorCallback()).isNotNull(); + } - @Test - void build_WithBatchSize_ShouldSetBatchSize() { - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(10) - .build(); + @Test + void build_WithWarningCallback_ShouldSetWarningCallback() { + final TriConsumer, Optional> + mockWarningCallBack = (warningMessage, draft, entry) -> {}; + taxCategorySyncOptionsBuilder.warningCallback(mockWarningCallBack); - assertThat(taxCategorySyncOptions.getBatchSize()).isEqualTo(10); - } + final TaxCategorySyncOptions taxCategorySyncOptions = taxCategorySyncOptionsBuilder.build(); - @Test - void build_WithInvalidBatchSize_ShouldBuildSyncOptions() { - final TaxCategorySyncOptions taxCategorySyncOptionsWithZeroBatchSize = TaxCategorySyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(0) - .build(); + assertThat(taxCategorySyncOptions.getWarningCallback()).isNotNull(); + } - assertThat(taxCategorySyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(TaxCategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); + @Test + void build_WithBatchSize_ShouldSetBatchSize() { + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); - final TaxCategorySyncOptions taxCategorySyncOptionsWithNegativeBatchSize = TaxCategorySyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(-100) - .build(); + assertThat(taxCategorySyncOptions.getBatchSize()).isEqualTo(10); + } - assertThat(taxCategorySyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(TaxCategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } + @Test + void build_WithInvalidBatchSize_ShouldBuildSyncOptions() { + final TaxCategorySyncOptions taxCategorySyncOptionsWithZeroBatchSize = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); + assertThat(taxCategorySyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(TaxCategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); - @Test - void build_WithCacheSize_ShouldSetCacheSize() { - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(10) - .build(); + final TaxCategorySyncOptions taxCategorySyncOptionsWithNegativeBatchSize = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); - assertThat(taxCategorySyncOptions.getCacheSize()).isEqualTo(10); - } + assertThat(taxCategorySyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(TaxCategorySyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + @Test + void build_WithCacheSize_ShouldSetCacheSize() { + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); - @Test - void build_WithZeroOrNegativeCacheSize_ShouldBuildSyncOptions() { - final TaxCategorySyncOptions taxCategorySyncOptionsWithZeroCacheSize = TaxCategorySyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(0) - .build(); + assertThat(taxCategorySyncOptions.getCacheSize()).isEqualTo(10); + } - assertThat(taxCategorySyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); + @Test + void build_WithZeroOrNegativeCacheSize_ShouldBuildSyncOptions() { + final TaxCategorySyncOptions taxCategorySyncOptionsWithZeroCacheSize = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); - final TaxCategorySyncOptions taxCategorySyncOptionsWithNegativeCacheSize = TaxCategorySyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(-100) - .build(); + assertThat(taxCategorySyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - assertThat(taxCategorySyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } + final TaxCategorySyncOptions taxCategorySyncOptionsWithNegativeCacheSize = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); + assertThat(taxCategorySyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsTest.java b/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsTest.java index 75c5274365..055b39fbe3 100644 --- a/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsTest.java +++ b/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncOptionsTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.taxcategories; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.utils.TriFunction; import io.sphere.sdk.client.SphereClient; import io.sphere.sdk.commands.UpdateAction; @@ -7,126 +16,135 @@ import io.sphere.sdk.taxcategories.TaxCategoryDraft; import io.sphere.sdk.taxcategories.TaxCategoryDraftBuilder; import io.sphere.sdk.taxcategories.commands.updateactions.SetKey; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class TaxCategorySyncOptionsTest { - private static SphereClient CTP_CLIENT = mock(SphereClient.class); - - @Test - void applyBeforeUpdateCallback_WithNullCallback_ShouldReturnIdenticalList() { - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) - .build(); - final List> updateActions = singletonList(SetKey.of("key")); - final List> filteredList = taxCategorySyncOptions - .applyBeforeUpdateCallback(updateActions, mock(TaxCategoryDraft.class), mock(TaxCategory.class)); - - assertThat(filteredList).isSameAs(updateActions); - } - - @Test - void applyBeforeUpdateCallback_WithNullReturnCallback_ShouldReturnEmptyList() { - final TriFunction>, TaxCategoryDraft, TaxCategory, - List>> beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) + private static SphereClient CTP_CLIENT = mock(SphereClient.class); + + @Test + void applyBeforeUpdateCallback_WithNullCallback_ShouldReturnIdenticalList() { + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).build(); + final List> updateActions = singletonList(SetKey.of("key")); + final List> filteredList = + taxCategorySyncOptions.applyBeforeUpdateCallback( + updateActions, mock(TaxCategoryDraft.class), mock(TaxCategory.class)); + + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void applyBeforeUpdateCallback_WithNullReturnCallback_ShouldReturnEmptyList() { + final TriFunction< + List>, + TaxCategoryDraft, + TaxCategory, + List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> null; + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) .beforeUpdateCallback(beforeUpdateCallback) .build(); - final List> updateActions = singletonList(SetKey.of("key")); - - final List> filteredList = taxCategorySyncOptions - .applyBeforeUpdateCallback(updateActions, mock(TaxCategoryDraft.class), mock(TaxCategory.class)); - - assertThat(filteredList).isEmpty(); - } - - private interface MockTriFunction extends - TriFunction>, TaxCategoryDraft, TaxCategory, List>> { - } - - @Test - void applyBeforeUpdateCallback_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final TaxCategorySyncOptionsTest.MockTriFunction beforeUpdateCallback = - mock(TaxCategorySyncOptionsTest.MockTriFunction.class); - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) + final List> updateActions = singletonList(SetKey.of("key")); + + final List> filteredList = + taxCategorySyncOptions.applyBeforeUpdateCallback( + updateActions, mock(TaxCategoryDraft.class), mock(TaxCategory.class)); + + assertThat(filteredList).isEmpty(); + } + + private interface MockTriFunction + extends TriFunction< + List>, + TaxCategoryDraft, + TaxCategory, + List>> {} + + @Test + void applyBeforeUpdateCallback_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final TaxCategorySyncOptionsTest.MockTriFunction beforeUpdateCallback = + mock(TaxCategorySyncOptionsTest.MockTriFunction.class); + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) .beforeUpdateCallback(beforeUpdateCallback) .build(); - final List> filteredList = taxCategorySyncOptions - .applyBeforeUpdateCallback(emptyList(), mock(TaxCategoryDraft.class), mock(TaxCategory.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - @Test - void applyBeforeUpdateCallback_WithCallback_ShouldReturnFilteredList() { - final TriFunction>, TaxCategoryDraft, TaxCategory, - List>> beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> - emptyList(); - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) + final List> filteredList = + taxCategorySyncOptions.applyBeforeUpdateCallback( + emptyList(), mock(TaxCategoryDraft.class), mock(TaxCategory.class)); + + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeUpdateCallback_WithCallback_ShouldReturnFilteredList() { + final TriFunction< + List>, + TaxCategoryDraft, + TaxCategory, + List>> + beforeUpdateCallback = (updateActions, newCategory, oldCategory) -> emptyList(); + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) .beforeUpdateCallback(beforeUpdateCallback) .build(); - final List> updateActions = singletonList(SetKey.of("key")); - - final List> filteredList = taxCategorySyncOptions - .applyBeforeUpdateCallback(updateActions, mock(TaxCategoryDraft.class), mock(TaxCategory.class)); - - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeCreateCallback_WithCallback_ShouldReturnFilteredDraft() { - final Function draftFunction = - taxCategoryDraft -> TaxCategoryDraftBuilder.of(taxCategoryDraft) - .key(taxCategoryDraft.getKey() + "_filteredKey").build(); - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - final TaxCategoryDraft resourceDraft = mock(TaxCategoryDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); - - final Optional filteredDraft = taxCategorySyncOptions - .applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).hasValueSatisfying(taxCategoryDraft -> assertThat(taxCategoryDraft.getKey()) - .isEqualTo("myKey_filteredKey")); - } - - @Test - void applyBeforeCreateCallback_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).build(); - final TaxCategoryDraft resourceDraft = mock(TaxCategoryDraft.class); - - final Optional filteredDraft = taxCategorySyncOptions - .applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).containsSame(resourceDraft); - } - - @Test - void applyBeforeCreateCallback_WithCallbackReturningNull_ShouldReturnEmptyOptional() { - final Function draftFunction = taxCategoryDraft -> null; - final TaxCategorySyncOptions taxCategorySyncOptions = TaxCategorySyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - final TaxCategoryDraft resourceDraft = mock(TaxCategoryDraft.class); - - final Optional filteredDraft = taxCategorySyncOptions - .applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isEmpty(); - } - + final List> updateActions = singletonList(SetKey.of("key")); + + final List> filteredList = + taxCategorySyncOptions.applyBeforeUpdateCallback( + updateActions, mock(TaxCategoryDraft.class), mock(TaxCategory.class)); + + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeCreateCallback_WithCallback_ShouldReturnFilteredDraft() { + final Function draftFunction = + taxCategoryDraft -> + TaxCategoryDraftBuilder.of(taxCategoryDraft) + .key(taxCategoryDraft.getKey() + "_filteredKey") + .build(); + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + final TaxCategoryDraft resourceDraft = mock(TaxCategoryDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); + + final Optional filteredDraft = + taxCategorySyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft) + .hasValueSatisfying( + taxCategoryDraft -> + assertThat(taxCategoryDraft.getKey()).isEqualTo("myKey_filteredKey")); + } + + @Test + void applyBeforeCreateCallback_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).build(); + final TaxCategoryDraft resourceDraft = mock(TaxCategoryDraft.class); + + final Optional filteredDraft = + taxCategorySyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).containsSame(resourceDraft); + } + + @Test + void applyBeforeCreateCallback_WithCallbackReturningNull_ShouldReturnEmptyOptional() { + final Function draftFunction = taxCategoryDraft -> null; + final TaxCategorySyncOptions taxCategorySyncOptions = + TaxCategorySyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + final TaxCategoryDraft resourceDraft = mock(TaxCategoryDraft.class); + + final Optional filteredDraft = + taxCategorySyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isEmpty(); + } } diff --git a/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncTest.java b/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncTest.java index 7ebf269e1f..435d17fd9c 100644 --- a/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncTest.java +++ b/src/test/java/com/commercetools/sync/taxcategories/TaxCategorySyncTest.java @@ -1,25 +1,5 @@ package com.commercetools.sync.taxcategories; -import com.commercetools.sync.services.TaxCategoryService; -import com.commercetools.sync.taxcategories.helpers.TaxCategorySyncStatistics; -import com.neovisionaries.i18n.CountryCode; -import io.sphere.sdk.client.ConcurrentModificationException; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.models.SphereException; -import io.sphere.sdk.taxcategories.TaxCategory; -import io.sphere.sdk.taxcategories.TaxCategoryDraft; -import io.sphere.sdk.taxcategories.TaxCategoryDraftBuilder; -import io.sphere.sdk.taxcategories.TaxRateDraftBuilder; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -38,360 +18,456 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import com.commercetools.sync.services.TaxCategoryService; +import com.commercetools.sync.taxcategories.helpers.TaxCategorySyncStatistics; +import com.neovisionaries.i18n.CountryCode; +import io.sphere.sdk.client.ConcurrentModificationException; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.models.SphereException; +import io.sphere.sdk.taxcategories.TaxCategory; +import io.sphere.sdk.taxcategories.TaxCategoryDraft; +import io.sphere.sdk.taxcategories.TaxCategoryDraftBuilder; +import io.sphere.sdk.taxcategories.TaxRateDraftBuilder; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + class TaxCategorySyncTest { - private final TaxCategoryService taxCategoryService = mock(TaxCategoryService.class); + private final TaxCategoryService taxCategoryService = mock(TaxCategoryService.class); - @AfterEach - void cleanup() { - reset(taxCategoryService); - } + @AfterEach + void cleanup() { + reset(taxCategoryService); + } - @Test - void sync_WithInvalidDrafts_ShouldApplyErrorCallbackAndIncrementFailed() { - final List errors = new ArrayList<>(); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + @Test + void sync_WithInvalidDrafts_ShouldApplyErrorCallbackAndIncrementFailed() { + final List errors = new ArrayList<>(); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) .errorCallback((exception, draft, entry, actions) -> errors.add(exception.getMessage())) .build(); - final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); - final TaxCategoryDraft withoutKeyDraft = TaxCategoryDraftBuilder.of(null, emptyList(), null).build(); - - final TaxCategorySyncStatistics result = sync.sync(asList(null, withoutKeyDraft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(2), - () -> assertThat(result.getFailed().get()).isEqualTo(2), - () -> assertThat(errors).hasSize(2), - () -> assertThat(errors).contains("TaxCategoryDraft is null.", - "TaxCategoryDraft with name: null doesn't have a key. " - + "Please make sure all tax category drafts have keys.") - ); - verifyNoMoreInteractions(taxCategoryService); - } - - @Test - void sync_WithErrorFetchingExistingKeys_ShouldApplyErrorCallbackAndIncrementFailed() { - final List errors = new ArrayList<>(); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); + final TaxCategoryDraft withoutKeyDraft = + TaxCategoryDraftBuilder.of(null, emptyList(), null).build(); + + final TaxCategorySyncStatistics result = + sync.sync(asList(null, withoutKeyDraft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(2), + () -> assertThat(result.getFailed().get()).isEqualTo(2), + () -> assertThat(errors).hasSize(2), + () -> + assertThat(errors) + .contains( + "TaxCategoryDraft is null.", + "TaxCategoryDraft with name: null doesn't have a key. " + + "Please make sure all tax category drafts have keys.")); + verifyNoMoreInteractions(taxCategoryService); + } + + @Test + void sync_WithErrorFetchingExistingKeys_ShouldApplyErrorCallbackAndIncrementFailed() { + final List errors = new ArrayList<>(); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) .errorCallback((exception, draft, entry, actions) -> errors.add(exception.getMessage())) .build(); - final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of("someName", emptyList(), null).key("someKey").build(); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())).thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(errors).hasSize(1), - () -> assertThat(errors).contains("Failed to fetch existing tax categories with keys: '[someKey]'.") - ); - verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); - verifyNoMoreInteractions(taxCategoryService); - } - - @Test - void sync_WithErrorCreating_ShouldIncrementFailedButNotApplyErrorCallback() { - final List errors = new ArrayList<>(); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of("someName", emptyList(), null).key("someKey").build(); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(errors).hasSize(1), + () -> + assertThat(errors) + .contains("Failed to fetch existing tax categories with keys: '[someKey]'.")); + verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); + verifyNoMoreInteractions(taxCategoryService); + } + + @Test + void sync_WithErrorCreating_ShouldIncrementFailedButNotApplyErrorCallback() { + final List errors = new ArrayList<>(); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) .errorCallback((exception, draft, entry, actions) -> errors.add(exception.getMessage())) .build(); - final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of("someName", emptyList(), null).key("someKey").build(); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())).thenReturn(completedFuture(emptySet())); - when(taxCategoryService.createTaxCategory(any())).thenReturn(completedFuture(empty())); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(result.getFailed().get()).isEqualTo(1), - () -> assertThat(errors).isEmpty() - ); - verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); - verify(taxCategoryService, times(1)).createTaxCategory(any()); - verifyNoMoreInteractions(taxCategoryService); - } - - @Test - void sync_WithNoError_ShouldApplyBeforeCreateCallbackAndIncrementCreated() { - final AtomicBoolean callbackApplied = new AtomicBoolean(false); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) - .beforeCreateCallback((draft) -> { - callbackApplied.set(true); - return draft; - }) + final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of("someName", emptyList(), null).key("someKey").build(); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn(completedFuture(emptySet())); + when(taxCategoryService.createTaxCategory(any())).thenReturn(completedFuture(empty())); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(result.getFailed().get()).isEqualTo(1), + () -> assertThat(errors).isEmpty()); + verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); + verify(taxCategoryService, times(1)).createTaxCategory(any()); + verifyNoMoreInteractions(taxCategoryService); + } + + @Test + void sync_WithNoError_ShouldApplyBeforeCreateCallbackAndIncrementCreated() { + final AtomicBoolean callbackApplied = new AtomicBoolean(false); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + .beforeCreateCallback( + (draft) -> { + callbackApplied.set(true); + return draft; + }) .build(); - final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of("someName", emptyList(), null).key("someKey").build(); - final TaxCategory taxCategory = mock(TaxCategory.class); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())).thenReturn(completedFuture(emptySet())); - when(taxCategoryService.createTaxCategory(any())).thenReturn(completedFuture(Optional.of(taxCategory))); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(result.getCreated().get()).isEqualTo(1), - () -> assertThat(result.getFailed().get()).isEqualTo(0), - () -> assertThat(callbackApplied.get()).isTrue() - ); - verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); - verify(taxCategoryService, times(1)).createTaxCategory(any()); - verifyNoMoreInteractions(taxCategoryService); - } - - @Test - void sync_WithErrorUpdating_ShouldApplyErrorCallbackAndIncrementFailed() { - final List errors = new ArrayList<>(); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of("someName", emptyList(), null).key("someKey").build(); + final TaxCategory taxCategory = mock(TaxCategory.class); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn(completedFuture(emptySet())); + when(taxCategoryService.createTaxCategory(any())) + .thenReturn(completedFuture(Optional.of(taxCategory))); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(result.getCreated().get()).isEqualTo(1), + () -> assertThat(result.getFailed().get()).isEqualTo(0), + () -> assertThat(callbackApplied.get()).isTrue()); + verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); + verify(taxCategoryService, times(1)).createTaxCategory(any()); + verifyNoMoreInteractions(taxCategoryService); + } + + @Test + void sync_WithErrorUpdating_ShouldApplyErrorCallbackAndIncrementFailed() { + final List errors = new ArrayList<>(); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) .errorCallback((exception, draft, entry, actions) -> errors.add(exception.getMessage())) .build(); - final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of("someName", emptyList(), "changed") - .key("someKey").build(); - final TaxCategory taxCategory = mock(TaxCategory.class); - - when(taxCategory.getKey()).thenReturn("someKey"); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) - .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); - when(taxCategoryService.updateTaxCategory(any(), any())).thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(result.getUpdated().get()).isEqualTo(0), - () -> assertThat(result.getFailed().get()).isEqualTo(1), - () -> assertThat(errors).hasSize(1) - ); - verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); - verify(taxCategoryService, times(1)).updateTaxCategory(any(), any()); - verifyNoMoreInteractions(taxCategoryService); - } - - @Test - void sync_WithErrorUpdatingAndTryingToRecoverWithFetchException_ShouldApplyErrorCallbackAndIncrementFailed() { - final List errors = new ArrayList<>(); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of("someName", emptyList(), "changed").key("someKey").build(); + final TaxCategory taxCategory = mock(TaxCategory.class); + + when(taxCategory.getKey()).thenReturn("someKey"); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); + when(taxCategoryService.updateTaxCategory(any(), any())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(result.getUpdated().get()).isEqualTo(0), + () -> assertThat(result.getFailed().get()).isEqualTo(1), + () -> assertThat(errors).hasSize(1)); + verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); + verify(taxCategoryService, times(1)).updateTaxCategory(any(), any()); + verifyNoMoreInteractions(taxCategoryService); + } + + @Test + void + sync_WithErrorUpdatingAndTryingToRecoverWithFetchException_ShouldApplyErrorCallbackAndIncrementFailed() { + final List errors = new ArrayList<>(); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) .errorCallback((exception, draft, entry, actions) -> errors.add(exception.getMessage())) .build(); - final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of("someName", emptyList(), "changed") - .key("someKey").build(); - final TaxCategory taxCategory = mock(TaxCategory.class); - - when(taxCategory.getKey()).thenReturn("someKey"); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) - .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); - when(taxCategoryService.updateTaxCategory(any(), any())).thenReturn(supplyAsync(() -> { - throw new io.sphere.sdk.client.ConcurrentModificationException(); - })); - when(taxCategoryService.fetchTaxCategory(any())).thenReturn(supplyAsync(() -> { - throw new SphereException(); - })); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(result.getUpdated().get()).isEqualTo(0), - () -> assertThat(result.getFailed().get()).isEqualTo(1), - () -> assertThat(errors).hasSize(1), - () -> assertThat(errors).hasOnlyOneElementSatisfying(msg -> assertThat(msg) - .contains("Failed to fetch from CTP while retrying after concurrency modification.")) - ); - verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); - verify(taxCategoryService, times(1)).updateTaxCategory(any(), any()); - verify(taxCategoryService, times(1)).fetchTaxCategory(any()); - verifyNoMoreInteractions(taxCategoryService); - } - - @Test - void sync_WithErrorUpdatingAndTryingToRecoverWithEmptyResponse_ShouldApplyErrorCallbackAndIncrementFailed() { - final List errors = new ArrayList<>(); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of("someName", emptyList(), "changed").key("someKey").build(); + final TaxCategory taxCategory = mock(TaxCategory.class); + + when(taxCategory.getKey()).thenReturn("someKey"); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); + when(taxCategoryService.updateTaxCategory(any(), any())) + .thenReturn( + supplyAsync( + () -> { + throw new io.sphere.sdk.client.ConcurrentModificationException(); + })); + when(taxCategoryService.fetchTaxCategory(any())) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(result.getUpdated().get()).isEqualTo(0), + () -> assertThat(result.getFailed().get()).isEqualTo(1), + () -> assertThat(errors).hasSize(1), + () -> + assertThat(errors) + .hasOnlyOneElementSatisfying( + msg -> + assertThat(msg) + .contains( + "Failed to fetch from CTP while retrying after concurrency modification."))); + verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); + verify(taxCategoryService, times(1)).updateTaxCategory(any(), any()); + verify(taxCategoryService, times(1)).fetchTaxCategory(any()); + verifyNoMoreInteractions(taxCategoryService); + } + + @Test + void + sync_WithErrorUpdatingAndTryingToRecoverWithEmptyResponse_ShouldApplyErrorCallbackAndIncrementFailed() { + final List errors = new ArrayList<>(); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) .errorCallback((exception, draft, entry, actions) -> errors.add(exception.getMessage())) .build(); - final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of("someName", emptyList(), "changed") - .key("someKey").build(); - final TaxCategory taxCategory = mock(TaxCategory.class); - - when(taxCategory.getKey()).thenReturn("someKey"); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) - .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); - when(taxCategoryService.updateTaxCategory(any(), any())).thenReturn(supplyAsync(() -> { - throw new ConcurrentModificationException(); - })); - when(taxCategoryService.fetchTaxCategory(any())).thenReturn(completedFuture(Optional.empty())); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(result.getUpdated().get()).isEqualTo(0), - () -> assertThat(result.getFailed().get()).isEqualTo(1), - () -> assertThat(errors).hasSize(1), - () -> assertThat(errors).hasOnlyOneElementSatisfying(msg -> assertThat(msg) - .contains("Not found when attempting to fetch while retrying after concurrency modification.")) - ); - verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); - verify(taxCategoryService, times(1)).updateTaxCategory(any(), any()); - verify(taxCategoryService, times(1)).fetchTaxCategory(any()); - verifyNoMoreInteractions(taxCategoryService); - } - - @Test - void sync_WithNoError_ShouldApplyBeforeUpdateCallbackAndIncrementUpdated() { - final AtomicBoolean callbackApplied = new AtomicBoolean(false); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) - .beforeUpdateCallback((actions, draft, old) -> { - callbackApplied.set(true); - return actions; - }) + final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of("someName", emptyList(), "changed").key("someKey").build(); + final TaxCategory taxCategory = mock(TaxCategory.class); + + when(taxCategory.getKey()).thenReturn("someKey"); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); + when(taxCategoryService.updateTaxCategory(any(), any())) + .thenReturn( + supplyAsync( + () -> { + throw new ConcurrentModificationException(); + })); + when(taxCategoryService.fetchTaxCategory(any())).thenReturn(completedFuture(Optional.empty())); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(result.getUpdated().get()).isEqualTo(0), + () -> assertThat(result.getFailed().get()).isEqualTo(1), + () -> assertThat(errors).hasSize(1), + () -> + assertThat(errors) + .hasOnlyOneElementSatisfying( + msg -> + assertThat(msg) + .contains( + "Not found when attempting to fetch while retrying after concurrency modification."))); + verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); + verify(taxCategoryService, times(1)).updateTaxCategory(any(), any()); + verify(taxCategoryService, times(1)).fetchTaxCategory(any()); + verifyNoMoreInteractions(taxCategoryService); + } + + @Test + void sync_WithNoError_ShouldApplyBeforeUpdateCallbackAndIncrementUpdated() { + final AtomicBoolean callbackApplied = new AtomicBoolean(false); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + .beforeUpdateCallback( + (actions, draft, old) -> { + callbackApplied.set(true); + return actions; + }) + .build(); + final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of("someName", emptyList(), "changed").key("someKey").build(); + final TaxCategory taxCategory = mock(TaxCategory.class); + + when(taxCategory.getId()).thenReturn("id"); + when(taxCategory.getKey()).thenReturn("someKey"); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); + when(taxCategoryService.updateTaxCategory(any(), any())) + .thenReturn(completedFuture(taxCategory)); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(result.getUpdated().get()).isEqualTo(1), + () -> assertThat(result.getFailed().get()).isEqualTo(0), + () -> assertThat(callbackApplied.get()).isTrue()); + verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); + verify(taxCategoryService, times(1)).updateTaxCategory(any(), any()); + verifyNoMoreInteractions(taxCategoryService); + } + + @Test + void sync_WithFilteredActions_ShouldApplyBeforeUpdateCallbackAndNotIncrementUpdated() { + final AtomicBoolean callbackApplied = new AtomicBoolean(false); + final TaxCategorySyncOptions options = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + .beforeUpdateCallback( + (actions, draft, old) -> { + callbackApplied.set(true); + return emptyList(); + }) .build(); - final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of("someName", emptyList(), "changed") - .key("someKey").build(); - final TaxCategory taxCategory = mock(TaxCategory.class); - - when(taxCategory.getId()).thenReturn("id"); - when(taxCategory.getKey()).thenReturn("someKey"); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) - .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); - when(taxCategoryService.updateTaxCategory(any(), any())).thenReturn(completedFuture(taxCategory)); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(result.getUpdated().get()).isEqualTo(1), - () -> assertThat(result.getFailed().get()).isEqualTo(0), - () -> assertThat(callbackApplied.get()).isTrue() - ); - verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); - verify(taxCategoryService, times(1)).updateTaxCategory(any(), any()); - verifyNoMoreInteractions(taxCategoryService); - } - - @Test - void sync_WithFilteredActions_ShouldApplyBeforeUpdateCallbackAndNotIncrementUpdated() { - final AtomicBoolean callbackApplied = new AtomicBoolean(false); - final TaxCategorySyncOptions options = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) - .beforeUpdateCallback((actions, draft, old) -> { - callbackApplied.set(true); - return emptyList(); - }) + final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of("someName", emptyList(), "changed").key("someKey").build(); + final TaxCategory taxCategory = mock(TaxCategory.class); + + when(taxCategory.getId()).thenReturn("id"); + when(taxCategory.getKey()).thenReturn("someKey"); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(result.getUpdated().get()).isEqualTo(0), + () -> assertThat(result.getFailed().get()).isEqualTo(0), + () -> assertThat(callbackApplied.get()).isTrue()); + verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); + verifyNoMoreInteractions(taxCategoryService); + } + + @Test + void sync_WithDuplicatedState_ShouldNotBuildActionAndTriggerErrorCallback() { + final String name = "DuplicatedName"; + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of( + name, + asList( + // replace + TaxRateDraftBuilder.of(name, 2.0, false, CountryCode.FR).state("NYON").build(), + TaxRateDraftBuilder.of(name, 2.0, false, CountryCode.FR).state("PARIS").build(), + TaxRateDraftBuilder.of(name, 3.0, false, CountryCode.DE) + .state("BERLIN") + .build(), + TaxRateDraftBuilder.of(name, 3.0, false, CountryCode.DE) + .state("BERLIN") + .build()), + "desc") + .key("someKey") .build(); - final TaxCategorySync sync = new TaxCategorySync(options, taxCategoryService); - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of("someName", emptyList(), "changed") - .key("someKey").build(); - final TaxCategory taxCategory = mock(TaxCategory.class); - - when(taxCategory.getId()).thenReturn("id"); - when(taxCategory.getKey()).thenReturn("someKey"); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) - .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(result.getUpdated().get()).isEqualTo(0), - () -> assertThat(result.getFailed().get()).isEqualTo(0), - () -> assertThat(callbackApplied.get()).isTrue() - ); - verify(taxCategoryService, times(1)).fetchMatchingTaxCategoriesByKeys(any()); - verifyNoMoreInteractions(taxCategoryService); - } - - @Test - void sync_WithDuplicatedState_ShouldNotBuildActionAndTriggerErrorCallback() { - final String name = "DuplicatedName"; - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of(name, asList( - // replace - TaxRateDraftBuilder.of(name, 2.0, false, CountryCode.FR).state("NYON").build(), - TaxRateDraftBuilder.of(name, 2.0, false, CountryCode.FR).state("PARIS").build(), - TaxRateDraftBuilder.of(name, 3.0, false, CountryCode.DE).state("BERLIN").build(), - TaxRateDraftBuilder.of(name, 3.0, false, CountryCode.DE).state("BERLIN").build() - ), "desc").key("someKey").build(); - - final AtomicReference callback = new AtomicReference<>(null); - final TaxCategorySyncOptions syncOptions = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, taxDraft, entry, actions) -> callback.set(exception.getMessage())) - .build(); - final TaxCategorySync sync = new TaxCategorySync(syncOptions, taxCategoryService); - final TaxCategory taxCategory = mock(TaxCategory.class); - - when(taxCategory.getId()).thenReturn("id"); - when(taxCategory.getKey()).thenReturn("someKey"); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) - .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); - when(taxCategoryService.updateTaxCategory(any(), any())).thenReturn(completedFuture(taxCategory)); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(result.getUpdated().get()).isEqualTo(0), - () -> assertThat(result.getFailed().get()).isEqualTo(1), - () -> assertThat(callback.get()) - .contains(format("Tax rate drafts have duplicated country codes and states. Duplicated " + + final AtomicReference callback = new AtomicReference<>(null); + final TaxCategorySyncOptions syncOptions = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, taxDraft, entry, actions) -> callback.set(exception.getMessage())) + .build(); + final TaxCategorySync sync = new TaxCategorySync(syncOptions, taxCategoryService); + final TaxCategory taxCategory = mock(TaxCategory.class); + + when(taxCategory.getId()).thenReturn("id"); + when(taxCategory.getKey()).thenReturn("someKey"); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); + when(taxCategoryService.updateTaxCategory(any(), any())) + .thenReturn(completedFuture(taxCategory)); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(result.getUpdated().get()).isEqualTo(0), + () -> assertThat(result.getFailed().get()).isEqualTo(1), + () -> + assertThat(callback.get()) + .contains( + format( + "Tax rate drafts have duplicated country codes and states. Duplicated " + "tax rate country code: '%s'. state : '%s'. Tax rate country codes and states are " - + "expected to be unique inside their tax category.", CountryCode.DE, "BERLIN")) - ); - - } - - @Test - void sync_WithDuplicatedCountryCode_ShouldNotBuildActionAndTriggerErrorCallback() { - final String name = "DuplicatedName"; - final TaxCategoryDraft draft = TaxCategoryDraftBuilder.of(name, asList( - // replace - TaxRateDraftBuilder.of(name, 2.0, false, CountryCode.FR).build(), - TaxRateDraftBuilder.of(name, 2.0, false, CountryCode.FR).build() - - ), "desc").key("someKey").build(); - - final AtomicReference callback = new AtomicReference<>(null); - final TaxCategorySyncOptions syncOptions = TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, taxDraft, entry, actions) -> callback.set(exception.getMessage())) - .build(); - final TaxCategorySync sync = new TaxCategorySync(syncOptions, taxCategoryService); - final TaxCategory taxCategory = mock(TaxCategory.class); - - when(taxCategory.getId()).thenReturn("id"); - when(taxCategory.getKey()).thenReturn("someKey"); - - when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) - .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); - when(taxCategoryService.updateTaxCategory(any(), any())).thenReturn(completedFuture(taxCategory)); - - final TaxCategorySyncStatistics result = sync.sync(singletonList(draft)).toCompletableFuture().join(); - - assertAll( - () -> assertThat(result.getProcessed().get()).isEqualTo(1), - () -> assertThat(result.getUpdated().get()).isEqualTo(0), - () -> assertThat(result.getFailed().get()).isEqualTo(1), - () -> assertThat(callback.get()) - .contains(format("Tax rate drafts have duplicated country codes. Duplicated " - + "tax rate country code: '%s'. Tax rate country codes and states are " - + "expected to be unique inside their tax category.", CountryCode.FR)) - ); + + "expected to be unique inside their tax category.", + CountryCode.DE, "BERLIN"))); + } + + @Test + void sync_WithDuplicatedCountryCode_ShouldNotBuildActionAndTriggerErrorCallback() { + final String name = "DuplicatedName"; + final TaxCategoryDraft draft = + TaxCategoryDraftBuilder.of( + name, + asList( + // replace + TaxRateDraftBuilder.of(name, 2.0, false, CountryCode.FR).build(), + TaxRateDraftBuilder.of(name, 2.0, false, CountryCode.FR).build()), + "desc") + .key("someKey") + .build(); - } + final AtomicReference callback = new AtomicReference<>(null); + final TaxCategorySyncOptions syncOptions = + TaxCategorySyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, taxDraft, entry, actions) -> callback.set(exception.getMessage())) + .build(); + final TaxCategorySync sync = new TaxCategorySync(syncOptions, taxCategoryService); + final TaxCategory taxCategory = mock(TaxCategory.class); + + when(taxCategory.getId()).thenReturn("id"); + when(taxCategory.getKey()).thenReturn("someKey"); + + when(taxCategoryService.fetchMatchingTaxCategoriesByKeys(any())) + .thenReturn(completedFuture(new HashSet<>(singletonList(taxCategory)))); + when(taxCategoryService.updateTaxCategory(any(), any())) + .thenReturn(completedFuture(taxCategory)); + + final TaxCategorySyncStatistics result = + sync.sync(singletonList(draft)).toCompletableFuture().join(); + + assertAll( + () -> assertThat(result.getProcessed().get()).isEqualTo(1), + () -> assertThat(result.getUpdated().get()).isEqualTo(0), + () -> assertThat(result.getFailed().get()).isEqualTo(1), + () -> + assertThat(callback.get()) + .contains( + format( + "Tax rate drafts have duplicated country codes. Duplicated " + + "tax rate country code: '%s'. Tax rate country codes and states are " + + "expected to be unique inside their tax category.", + CountryCode.FR))); + } } diff --git a/src/test/java/com/commercetools/sync/taxcategories/helpers/TaxCategoryBatchValidatorTest.java b/src/test/java/com/commercetools/sync/taxcategories/helpers/TaxCategoryBatchValidatorTest.java index 2d8325ba05..3db4f51af3 100644 --- a/src/test/java/com/commercetools/sync/taxcategories/helpers/TaxCategoryBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/taxcategories/helpers/TaxCategoryBatchValidatorTest.java @@ -1,23 +1,5 @@ package com.commercetools.sync.taxcategories.helpers; -import com.commercetools.sync.taxcategories.TaxCategorySyncOptions; -import com.commercetools.sync.taxcategories.TaxCategorySyncOptionsBuilder; -import com.neovisionaries.i18n.CountryCode; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.taxcategories.TaxCategoryDraft; -import io.sphere.sdk.taxcategories.TaxCategoryDraftBuilder; -import io.sphere.sdk.taxcategories.TaxRateDraftBuilder; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - import static com.commercetools.sync.taxcategories.helpers.TaxCategoryBatchValidator.TAX_CATEGORY_DRAFT_IS_NULL; import static com.commercetools.sync.taxcategories.helpers.TaxCategoryBatchValidator.TAX_CATEGORY_DRAFT_KEY_NOT_SET; import static com.commercetools.sync.taxcategories.helpers.TaxCategoryBatchValidator.TAX_CATEGORY_DUPLICATED_COUNTRY; @@ -31,121 +13,168 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.commercetools.sync.taxcategories.TaxCategorySyncOptions; +import com.commercetools.sync.taxcategories.TaxCategorySyncOptionsBuilder; +import com.neovisionaries.i18n.CountryCode; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.taxcategories.TaxCategoryDraft; +import io.sphere.sdk.taxcategories.TaxCategoryDraftBuilder; +import io.sphere.sdk.taxcategories.TaxRateDraftBuilder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class TaxCategoryBatchValidatorTest { - private List errorCallBackMessages; - private TaxCategorySyncOptions syncOptions; - private TaxCategorySyncStatistics syncStatistics; - - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = TaxCategorySyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - }) + private List errorCallBackMessages; + private TaxCategorySyncOptions syncOptions; + private TaxCategorySyncStatistics syncStatistics; + + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); + syncOptions = + TaxCategorySyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + }) .build(); - syncStatistics = new TaxCategorySyncStatistics(); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(emptyList()); - - assertThat(validDrafts).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullTaxCategoryDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.singletonList(null)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(TAX_CATEGORY_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithTaxCategoryDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final TaxCategoryDraft taxCategoryDraft = mock(TaxCategoryDraft.class); - final Set validDrafts = getValidDrafts(Collections.singletonList(taxCategoryDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, taxCategoryDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithTaxCategoryDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { - final TaxCategoryDraft taxCategoryDraft = mock(TaxCategoryDraft.class); - when(taxCategoryDraft.getKey()).thenReturn(EMPTY); - final Set validDrafts = getValidDrafts(Collections.singletonList(taxCategoryDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, taxCategoryDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithMixOfValidAndInvalidDrafts_ShouldValidateCorrectly() { - final TaxCategoryDraft validDraft = TaxCategoryDraftBuilder - .of("foo", singletonList( - TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR).state("PARIS").build() - ), "desc") - .key("foo").build(); - - final TaxCategoryDraft withEmptyKey = TaxCategoryDraftBuilder - .of("foo", emptyList(), null) - .key("") + syncStatistics = new TaxCategorySyncStatistics(); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(emptyList()); + + assertThat(validDrafts).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullTaxCategoryDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.singletonList(null)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(TAX_CATEGORY_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithTaxCategoryDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final TaxCategoryDraft taxCategoryDraft = mock(TaxCategoryDraft.class); + final Set validDrafts = + getValidDrafts(Collections.singletonList(taxCategoryDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, taxCategoryDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithTaxCategoryDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { + final TaxCategoryDraft taxCategoryDraft = mock(TaxCategoryDraft.class); + when(taxCategoryDraft.getKey()).thenReturn(EMPTY); + final Set validDrafts = + getValidDrafts(Collections.singletonList(taxCategoryDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, taxCategoryDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithMixOfValidAndInvalidDrafts_ShouldValidateCorrectly() { + final TaxCategoryDraft validDraft = + TaxCategoryDraftBuilder.of( + "foo", + singletonList( + TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR) + .state("PARIS") + .build()), + "desc") + .key("foo") + .build(); + + final TaxCategoryDraft withEmptyKey = + TaxCategoryDraftBuilder.of("foo", emptyList(), null).key("").build(); + + final TaxCategoryDraft withNullKey = + TaxCategoryDraftBuilder.of("foo", emptyList(), null).key(null).build(); + + final TaxCategoryDraft withDuplicatedState = + TaxCategoryDraftBuilder.of( + "foo", + asList( + TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR).state("NYON").build(), + TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR) + .state("PARIS") + .build(), + TaxRateDraftBuilder.of("foo", 3.0, false, CountryCode.DE) + .state("BERLIN") + .build(), + TaxRateDraftBuilder.of("foo", 3.0, false, CountryCode.DE) + .state("BERLIN") + .build()), + "desc") + .key("duplicatedState") .build(); - final TaxCategoryDraft withNullKey = TaxCategoryDraftBuilder - .of("foo", emptyList(), null) - .key(null) + final TaxCategoryDraft withDuplicatedCountry = + TaxCategoryDraftBuilder.of( + "foo", + asList( + TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR).build(), + TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR).build()), + "desc") + .key("duplicatedCountry") .build(); - final TaxCategoryDraft withDuplicatedState = TaxCategoryDraftBuilder.of("foo", asList( - TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR).state("NYON").build(), - TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR).state("PARIS").build(), - TaxRateDraftBuilder.of("foo", 3.0, false, CountryCode.DE).state("BERLIN").build(), - TaxRateDraftBuilder.of("foo", 3.0, false, CountryCode.DE).state("BERLIN").build() - ), "desc").key("duplicatedState").build(); - - final TaxCategoryDraft withDuplicatedCountry = TaxCategoryDraftBuilder.of("foo", asList( - TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR).build(), - TaxRateDraftBuilder.of("foo", 2.0, false, CountryCode.FR).build() - - ), "desc").key("duplicatedCountry").build(); - - final TaxCategoryBatchValidator batchValidator = new TaxCategoryBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = batchValidator.validateAndCollectReferencedKeys( - Arrays.asList(null, validDraft, withEmptyKey, withNullKey, withDuplicatedState, withDuplicatedCountry)); - - assertThat(pair.getLeft()).containsExactlyInAnyOrder(validDraft); - assertThat(pair.getRight()).containsExactlyInAnyOrder("foo"); - - assertThat(errorCallBackMessages).hasSize(5); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(TAX_CATEGORY_DRAFT_IS_NULL); - assertThat(errorCallBackMessages.get(1)) - .isEqualTo(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, "foo")); - assertThat(errorCallBackMessages.get(2)) - .isEqualTo(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, "foo")); - assertThat(errorCallBackMessages.get(3)) - .isEqualTo(format(TAX_CATEGORY_DUPLICATED_COUNTRY_AND_STATE, CountryCode.DE, "BERLIN")); - assertThat(errorCallBackMessages.get(4)) - .isEqualTo(format(TAX_CATEGORY_DUPLICATED_COUNTRY, CountryCode.FR)); - - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List taxCategoryDrafts) { - final TaxCategoryBatchValidator batchValidator = new TaxCategoryBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = - batchValidator.validateAndCollectReferencedKeys(taxCategoryDrafts); - return pair.getLeft(); - } + final TaxCategoryBatchValidator batchValidator = + new TaxCategoryBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + batchValidator.validateAndCollectReferencedKeys( + Arrays.asList( + null, + validDraft, + withEmptyKey, + withNullKey, + withDuplicatedState, + withDuplicatedCountry)); + + assertThat(pair.getLeft()).containsExactlyInAnyOrder(validDraft); + assertThat(pair.getRight()).containsExactlyInAnyOrder("foo"); + + assertThat(errorCallBackMessages).hasSize(5); + assertThat(errorCallBackMessages.get(0)).isEqualTo(TAX_CATEGORY_DRAFT_IS_NULL); + assertThat(errorCallBackMessages.get(1)) + .isEqualTo(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, "foo")); + assertThat(errorCallBackMessages.get(2)) + .isEqualTo(format(TAX_CATEGORY_DRAFT_KEY_NOT_SET, "foo")); + assertThat(errorCallBackMessages.get(3)) + .isEqualTo(format(TAX_CATEGORY_DUPLICATED_COUNTRY_AND_STATE, CountryCode.DE, "BERLIN")); + assertThat(errorCallBackMessages.get(4)) + .isEqualTo(format(TAX_CATEGORY_DUPLICATED_COUNTRY, CountryCode.FR)); + } + + @Nonnull + private Set getValidDrafts( + @Nonnull final List taxCategoryDrafts) { + final TaxCategoryBatchValidator batchValidator = + new TaxCategoryBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + batchValidator.validateAndCollectReferencedKeys(taxCategoryDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/taxcategories/helpers/TaxCategorySyncStatisticsTest.java b/src/test/java/com/commercetools/sync/taxcategories/helpers/TaxCategorySyncStatisticsTest.java index f6e87337ce..114b87c519 100644 --- a/src/test/java/com/commercetools/sync/taxcategories/helpers/TaxCategorySyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/taxcategories/helpers/TaxCategorySyncStatisticsTest.java @@ -1,30 +1,31 @@ package com.commercetools.sync.taxcategories.helpers; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.Random; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class TaxCategorySyncStatisticsTest { - @Test - void getReportMessage_WithRandomStats_ShouldGetCorrectMessage() { - Random random = new Random(); - - int created = random.nextInt(); - int updated = random.nextInt(); - int failed = random.nextInt(); - int processed = created + updated + failed; - - TaxCategorySyncStatistics statistics = new TaxCategorySyncStatistics(); - statistics.incrementCreated(created); - statistics.incrementUpdated(updated); - statistics.incrementFailed(failed); - statistics.incrementProcessed(processed); - - assertThat(statistics.getReportMessage()).isEqualTo("Summary: %s tax categories were processed in " - + "total (%s created, %s updated and %s failed to sync).", processed, created, updated, failed); - } - + @Test + void getReportMessage_WithRandomStats_ShouldGetCorrectMessage() { + Random random = new Random(); + + int created = random.nextInt(); + int updated = random.nextInt(); + int failed = random.nextInt(); + int processed = created + updated + failed; + + TaxCategorySyncStatistics statistics = new TaxCategorySyncStatistics(); + statistics.incrementCreated(created); + statistics.incrementUpdated(updated); + statistics.incrementFailed(failed); + statistics.incrementProcessed(processed); + + assertThat(statistics.getReportMessage()) + .isEqualTo( + "Summary: %s tax categories were processed in " + + "total (%s created, %s updated and %s failed to sync).", + processed, created, updated, failed); + } } diff --git a/src/test/java/com/commercetools/sync/taxcategories/utils/TaxCategorySyncUtilsTest.java b/src/test/java/com/commercetools/sync/taxcategories/utils/TaxCategorySyncUtilsTest.java index 0a5a2416ff..0cb003c973 100644 --- a/src/test/java/com/commercetools/sync/taxcategories/utils/TaxCategorySyncUtilsTest.java +++ b/src/test/java/com/commercetools/sync/taxcategories/utils/TaxCategorySyncUtilsTest.java @@ -1,61 +1,59 @@ package com.commercetools.sync.taxcategories.utils; +import static com.commercetools.sync.taxcategories.utils.TaxCategorySyncUtils.buildActions; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.taxcategories.TaxCategory; import io.sphere.sdk.taxcategories.TaxCategoryDraft; import io.sphere.sdk.taxcategories.TaxCategoryDraftBuilder; import io.sphere.sdk.taxcategories.commands.updateactions.ChangeName; import io.sphere.sdk.taxcategories.commands.updateactions.SetDescription; -import org.junit.jupiter.api.Test; - import java.util.Collections; import java.util.List; - -import static com.commercetools.sync.taxcategories.utils.TaxCategorySyncUtils.buildActions; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class TaxCategorySyncUtilsTest { - @Test - void buildActions_WithSameValues_ShouldNotBuildUpdateActions() { - final String name = "test name"; - final String description = "test description"; + @Test + void buildActions_WithSameValues_ShouldNotBuildUpdateActions() { + final String name = "test name"; + final String description = "test description"; - final TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getName()).thenReturn(name); - when(taxCategory.getKey()).thenReturn("tax-cat-key"); - when(taxCategory.getDescription()).thenReturn(description); + final TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getName()).thenReturn(name); + when(taxCategory.getKey()).thenReturn("tax-cat-key"); + when(taxCategory.getDescription()).thenReturn(description); - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder.of(name, Collections.emptyList(), description) + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of(name, Collections.emptyList(), description) .key("tax-cat-key") .build(); - final List> result = buildActions(taxCategory, taxCategoryDraft); + final List> result = buildActions(taxCategory, taxCategoryDraft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildActions_WithDifferentValues_ShouldBuildAllUpdateActions() { - final TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getKey()).thenReturn("tax-cat-key"); - when(taxCategory.getDescription()).thenReturn("description"); + @Test + void buildActions_WithDifferentValues_ShouldBuildAllUpdateActions() { + final TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getKey()).thenReturn("tax-cat-key"); + when(taxCategory.getDescription()).thenReturn("description"); - final TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("different name", null, "different description") + final TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of("different name", null, "different description") .key("tax-cat-key") .build(); - final List> result = buildActions(taxCategory, taxCategoryDraft); - - assertAll( - () -> assertThat(result).contains(ChangeName.of(taxCategoryDraft.getName())), - () -> assertThat(result).contains(SetDescription.of(taxCategoryDraft.getDescription())) - ); - } + final List> result = buildActions(taxCategory, taxCategoryDraft); + assertAll( + () -> assertThat(result).contains(ChangeName.of(taxCategoryDraft.getName())), + () -> assertThat(result).contains(SetDescription.of(taxCategoryDraft.getDescription()))); + } } diff --git a/src/test/java/com/commercetools/sync/taxcategories/utils/TaxCategoryUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/taxcategories/utils/TaxCategoryUpdateActionUtilsTest.java index 0c078cd6c1..f6c00df233 100644 --- a/src/test/java/com/commercetools/sync/taxcategories/utils/TaxCategoryUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/taxcategories/utils/TaxCategoryUpdateActionUtilsTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.taxcategories.utils; +import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildChangeNameAction; +import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildSetDescriptionAction; +import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildTaxRateUpdateActions; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.neovisionaries.i18n.CountryCode; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.taxcategories.SubRate; @@ -14,580 +24,569 @@ import io.sphere.sdk.taxcategories.commands.updateactions.RemoveTaxRate; import io.sphere.sdk.taxcategories.commands.updateactions.ReplaceTaxRate; import io.sphere.sdk.taxcategories.commands.updateactions.SetDescription; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildChangeNameAction; -import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildSetDescriptionAction; -import static com.commercetools.sync.taxcategories.utils.TaxCategoryUpdateActionUtils.buildTaxRateUpdateActions; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class TaxCategoryUpdateActionUtilsTest { - private TaxCategory taxCategory; - private TaxCategoryDraft newSameTaxCategoryDraft; - private TaxCategoryDraft newDifferentTaxCategoryDraft; - - @BeforeEach - void setup() { - final String name = "test name"; - final String key = "test key"; - final String description = "test description"; - - taxCategory = mock(TaxCategory.class); - when(taxCategory.getName()).thenReturn(name); - when(taxCategory.getKey()).thenReturn(key); - when(taxCategory.getDescription()).thenReturn(description); - - final String taxRateName1 = "taxRateName1"; - final String taxRateName2 = "taxRateName2"; - - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn(taxRateName1); - when(taxRate1.getId()).thenReturn("taxRateId1"); - when(taxRate1.getAmount()).thenReturn(1.0); - when(taxRate1.getCountry()).thenReturn(CountryCode.DE); - when(taxRate1.isIncludedInPrice()).thenReturn(false); - final TaxRate taxRate2 = mock(TaxRate.class); - when(taxRate2.getName()).thenReturn(taxRateName2); - when(taxRate2.getId()).thenReturn("taxRateId2"); - when(taxRate2.getAmount()).thenReturn(2.0); - when(taxRate2.getCountry()).thenReturn(CountryCode.US); - when(taxRate2.isIncludedInPrice()).thenReturn(false); - - final List oldTaxRates = asList(taxRate1, taxRate2); - - when(taxCategory.getTaxRates()).thenReturn(oldTaxRates); - - newSameTaxCategoryDraft = TaxCategoryDraftBuilder.of(name, emptyList(), description).key(key).build(); - - newDifferentTaxCategoryDraft = TaxCategoryDraftBuilder.of("changedName", emptyList(), "desc") - .key(key) - .build(); - } - - @Test - void buildChangeNameAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeNameAction(taxCategory, - newDifferentTaxCategoryDraft); - - assertThat(result).contains(ChangeName.of(newDifferentTaxCategoryDraft.getName())); - } - - @Test - void buildChangeNameAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeNameAction(taxCategory, newSameTaxCategoryDraft); - - assertThat(result).isEmpty(); - } - - @Test - void buildSetDescriptionAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetDescriptionAction(taxCategory, - newDifferentTaxCategoryDraft); - - assertThat(result).contains(SetDescription.of(newDifferentTaxCategoryDraft.getDescription())); - } - - @Test - void buildSetDescriptionAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetDescriptionAction(taxCategory, - newSameTaxCategoryDraft); - - assertThat(result).isEmpty(); - } - - @Test - void buildRatesUpdateActions_OnlyWithNewRate_ShouldBuildOnlyAddTaxRateAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - - TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("%5 DE", 0.05, false, CountryCode.DE) - .build(); - - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of(null, singletonList(taxRateDraft), null) + private TaxCategory taxCategory; + private TaxCategoryDraft newSameTaxCategoryDraft; + private TaxCategoryDraft newDifferentTaxCategoryDraft; + + @BeforeEach + void setup() { + final String name = "test name"; + final String key = "test key"; + final String description = "test description"; + + taxCategory = mock(TaxCategory.class); + when(taxCategory.getName()).thenReturn(name); + when(taxCategory.getKey()).thenReturn(key); + when(taxCategory.getDescription()).thenReturn(description); + + final String taxRateName1 = "taxRateName1"; + final String taxRateName2 = "taxRateName2"; + + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn(taxRateName1); + when(taxRate1.getId()).thenReturn("taxRateId1"); + when(taxRate1.getAmount()).thenReturn(1.0); + when(taxRate1.getCountry()).thenReturn(CountryCode.DE); + when(taxRate1.isIncludedInPrice()).thenReturn(false); + final TaxRate taxRate2 = mock(TaxRate.class); + when(taxRate2.getName()).thenReturn(taxRateName2); + when(taxRate2.getId()).thenReturn("taxRateId2"); + when(taxRate2.getAmount()).thenReturn(2.0); + when(taxRate2.getCountry()).thenReturn(CountryCode.US); + when(taxRate2.isIncludedInPrice()).thenReturn(false); + + final List oldTaxRates = asList(taxRate1, taxRate2); + + when(taxCategory.getTaxRates()).thenReturn(oldTaxRates); + + newSameTaxCategoryDraft = + TaxCategoryDraftBuilder.of(name, emptyList(), description).key(key).build(); + + newDifferentTaxCategoryDraft = + TaxCategoryDraftBuilder.of("changedName", emptyList(), "desc").key(key).build(); + } + + @Test + void buildChangeNameAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildChangeNameAction(taxCategory, newDifferentTaxCategoryDraft); + + assertThat(result).contains(ChangeName.of(newDifferentTaxCategoryDraft.getName())); + } + + @Test + void buildChangeNameAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = + buildChangeNameAction(taxCategory, newSameTaxCategoryDraft); + + assertThat(result).isEmpty(); + } + + @Test + void buildSetDescriptionAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = + buildSetDescriptionAction(taxCategory, newDifferentTaxCategoryDraft); + + assertThat(result).contains(SetDescription.of(newDifferentTaxCategoryDraft.getDescription())); + } + + @Test + void buildSetDescriptionAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = + buildSetDescriptionAction(taxCategory, newSameTaxCategoryDraft); + + assertThat(result).isEmpty(); + } + + @Test + void buildRatesUpdateActions_OnlyWithNewRate_ShouldBuildOnlyAddTaxRateAction() { + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + + TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("%5 DE", 0.05, false, CountryCode.DE).build(); + + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of(null, singletonList(taxRateDraft), null) .key("tax-category-key") .build(); - final List> result = - buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - - assertThat(result).isEqualTo(singletonList(AddTaxRate.of(taxRateDraft))); - } - - @Test - void buildRatesUpdateActions_OnlyUpdatedTaxRate_ShouldBuildOnlyReplaceTaxRateAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); - - final TaxRate taxRate = mock(TaxRate.class); - when(taxRate.getName()).thenReturn("19% DE"); - when(taxRate.getId()).thenReturn("taxRate-1"); - when(taxRate.getAmount()).thenReturn(0.19); - when(taxRate.getCountry()).thenReturn(CountryCode.DE); - when(taxRate.isIncludedInPrice()).thenReturn(false); - - when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate)); - - TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("%16 DE", 0.16, false, CountryCode.DE) - .build(); - - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", singletonList(taxRateDraft), "desc") - .key("tax-category-key") - .build(); + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - final List> result = - buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + assertThat(result).isEqualTo(singletonList(AddTaxRate.of(taxRateDraft))); + } - assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); - } + @Test + void buildRatesUpdateActions_OnlyUpdatedTaxRate_ShouldBuildOnlyReplaceTaxRateAction() { + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); - @Test - void buildRatesUpdateActions_withoutNewTaxRate_ShouldBuildOnlyRemoveTaxRateAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); + final TaxRate taxRate = mock(TaxRate.class); + when(taxRate.getName()).thenReturn("19% DE"); + when(taxRate.getId()).thenReturn("taxRate-1"); + when(taxRate.getAmount()).thenReturn(0.19); + when(taxRate.getCountry()).thenReturn(CountryCode.DE); + when(taxRate.isIncludedInPrice()).thenReturn(false); - final TaxRate taxRate = mock(TaxRate.class); - when(taxRate.getName()).thenReturn("19% DE"); - when(taxRate.getId()).thenReturn("taxRate-1"); - when(taxRate.getAmount()).thenReturn(0.19); - when(taxRate.getCountry()).thenReturn(CountryCode.DE); - when(taxRate.isIncludedInPrice()).thenReturn(false); + when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate)); - when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate)); + TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("%16 DE", 0.16, false, CountryCode.DE).build(); - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", emptyList(), "desc") + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of("name", singletonList(taxRateDraft), "desc") .key("tax-category-key") .build(); - final List> result = - buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - - assertThat(result).isEqualTo(singletonList(RemoveTaxRate.of("taxRate-1"))); - } - - @Test - void buildRatesUpdateActions_withDifferentTaxRate_ShouldBuildMixedActions() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); - - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn("11% US"); - when(taxRate1.getId()).thenReturn("taxRate-1"); - when(taxRate1.getAmount()).thenReturn(0.11); - when(taxRate1.getCountry()).thenReturn(CountryCode.US); - when(taxRate1.isIncludedInPrice()).thenReturn(false); - - final TaxRate taxRate2 = mock(TaxRate.class); - when(taxRate2.getName()).thenReturn("8% DE"); - when(taxRate2.getId()).thenReturn("taxRate-2"); - when(taxRate2.getAmount()).thenReturn(0.08); - when(taxRate2.getCountry()).thenReturn(CountryCode.AT); - when(taxRate2.isIncludedInPrice()).thenReturn(false); - - final TaxRate taxRate3 = mock(TaxRate.class); - when(taxRate3.getName()).thenReturn("21% ES"); - when(taxRate3.getId()).thenReturn("taxRate-3"); - when(taxRate3.getAmount()).thenReturn(0.21); - when(taxRate3.getCountry()).thenReturn(CountryCode.ES); - when(taxRate3.isIncludedInPrice()).thenReturn(false); - - when(taxCategory.getTaxRates()).thenReturn(asList(taxRate1, taxRate2, taxRate3)); - - //Update: Price is included. - TaxRateDraft taxRateDraft1 = TaxRateDraftBuilder - .of("11% US", 0.11, true, CountryCode.US) - .build(); - - // taxRate-2 is removed. - // new rate is added. - TaxRateDraft taxRateDraft4 = TaxRateDraftBuilder - .of("15% FR", 0.15, false, CountryCode.FR) - .build(); - - // same - TaxRateDraft taxRateDraft3 = TaxRateDraftBuilder - .of("21% ES", 0.21, false, CountryCode.ES) - .build(); - - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", asList(taxRateDraft1, taxRateDraft4, taxRateDraft3), "desc") + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + + assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); + } + + @Test + void buildRatesUpdateActions_withoutNewTaxRate_ShouldBuildOnlyRemoveTaxRateAction() { + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); + + final TaxRate taxRate = mock(TaxRate.class); + when(taxRate.getName()).thenReturn("19% DE"); + when(taxRate.getId()).thenReturn("taxRate-1"); + when(taxRate.getAmount()).thenReturn(0.19); + when(taxRate.getCountry()).thenReturn(CountryCode.DE); + when(taxRate.isIncludedInPrice()).thenReturn(false); + + when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate)); + + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of("name", emptyList(), "desc").key("tax-category-key").build(); + + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + + assertThat(result).isEqualTo(singletonList(RemoveTaxRate.of("taxRate-1"))); + } + + @Test + void buildRatesUpdateActions_withDifferentTaxRate_ShouldBuildMixedActions() { + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); + + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn("11% US"); + when(taxRate1.getId()).thenReturn("taxRate-1"); + when(taxRate1.getAmount()).thenReturn(0.11); + when(taxRate1.getCountry()).thenReturn(CountryCode.US); + when(taxRate1.isIncludedInPrice()).thenReturn(false); + + final TaxRate taxRate2 = mock(TaxRate.class); + when(taxRate2.getName()).thenReturn("8% DE"); + when(taxRate2.getId()).thenReturn("taxRate-2"); + when(taxRate2.getAmount()).thenReturn(0.08); + when(taxRate2.getCountry()).thenReturn(CountryCode.AT); + when(taxRate2.isIncludedInPrice()).thenReturn(false); + + final TaxRate taxRate3 = mock(TaxRate.class); + when(taxRate3.getName()).thenReturn("21% ES"); + when(taxRate3.getId()).thenReturn("taxRate-3"); + when(taxRate3.getAmount()).thenReturn(0.21); + when(taxRate3.getCountry()).thenReturn(CountryCode.ES); + when(taxRate3.isIncludedInPrice()).thenReturn(false); + + when(taxCategory.getTaxRates()).thenReturn(asList(taxRate1, taxRate2, taxRate3)); + + // Update: Price is included. + TaxRateDraft taxRateDraft1 = + TaxRateDraftBuilder.of("11% US", 0.11, true, CountryCode.US).build(); + + // taxRate-2 is removed. + // new rate is added. + TaxRateDraft taxRateDraft4 = + TaxRateDraftBuilder.of("15% FR", 0.15, false, CountryCode.FR).build(); + + // same + TaxRateDraft taxRateDraft3 = + TaxRateDraftBuilder.of("21% ES", 0.21, false, CountryCode.ES).build(); + + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "name", asList(taxRateDraft1, taxRateDraft4, taxRateDraft3), "desc") .key("tax-category-key") .build(); + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - final List> result = - buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - - assertThat(result).isEqualTo(asList( - ReplaceTaxRate.of("taxRate-1", taxRateDraft1), - RemoveTaxRate.of("taxRate-2"), - AddTaxRate.of(taxRateDraft4))); - } + assertThat(result) + .isEqualTo( + asList( + ReplaceTaxRate.of("taxRate-1", taxRateDraft1), + RemoveTaxRate.of("taxRate-2"), + AddTaxRate.of(taxRateDraft4))); + } - @Test - void buildTaxRatesUpdateActions_WithOnlyDifferentSubRates_ShouldReturnOnlyReplaceAction() { + @Test + void buildTaxRatesUpdateActions_WithOnlyDifferentSubRates_ShouldReturnOnlyReplaceAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn("11% US"); - when(taxRate1.getState()).thenReturn("state"); - when(taxRate1.getId()).thenReturn("taxRate-1"); - when(taxRate1.getAmount()).thenReturn(0.11); - when(taxRate1.getCountry()).thenReturn(CountryCode.US); - when(taxRate1.isIncludedInPrice()).thenReturn(false); + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn("11% US"); + when(taxRate1.getState()).thenReturn("state"); + when(taxRate1.getId()).thenReturn("taxRate-1"); + when(taxRate1.getAmount()).thenReturn(0.11); + when(taxRate1.getCountry()).thenReturn(CountryCode.US); + when(taxRate1.isIncludedInPrice()).thenReturn(false); - final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.07); - final SubRate oldSubRate2 = SubRate.of("subRate-2", 0.04); + final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.07); + final SubRate oldSubRate2 = SubRate.of("subRate-2", 0.04); - when(taxRate1.getSubRates()).thenReturn(asList(oldSubRate1, oldSubRate2)); - when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); + when(taxRate1.getSubRates()).thenReturn(asList(oldSubRate1, oldSubRate2)); + when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); - final SubRate subRate1 = SubRate.of("subRate-1", 0.06); - final SubRate subRate2 = SubRate.of("subRate-2", 0.05); + final SubRate subRate1 = SubRate.of("subRate-1", 0.06); + final SubRate subRate2 = SubRate.of("subRate-2", 0.05); - TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("11% US", 0.11, false, CountryCode.US) + TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("11% US", 0.11, false, CountryCode.US) .state("state") .subRates(asList(subRate1, subRate2)) .build(); - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", singletonList(taxRateDraft), "desc") + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of("name", singletonList(taxRateDraft), "desc") .key("tax-category-key") .build(); - final List> result = - buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); - } + assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); + } - @Test - void buildTaxRatesUpdateActions_WithMoreSubRates_ShouldReturnOnlyReplaceAction() { + @Test + void buildTaxRatesUpdateActions_WithMoreSubRates_ShouldReturnOnlyReplaceAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn("11% US"); - when(taxRate1.getState()).thenReturn("state"); - when(taxRate1.getId()).thenReturn("taxRate-1"); - when(taxRate1.getAmount()).thenReturn(0.11); - when(taxRate1.getCountry()).thenReturn(CountryCode.US); - when(taxRate1.isIncludedInPrice()).thenReturn(false); + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn("11% US"); + when(taxRate1.getState()).thenReturn("state"); + when(taxRate1.getId()).thenReturn("taxRate-1"); + when(taxRate1.getAmount()).thenReturn(0.11); + when(taxRate1.getCountry()).thenReturn(CountryCode.US); + when(taxRate1.isIncludedInPrice()).thenReturn(false); - final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); + final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); - when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); - when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); + when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); + when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); - final SubRate subRate1 = SubRate.of("subRate-1", 0.06); - final SubRate subRate2 = SubRate.of("subRate-2", 0.05); + final SubRate subRate1 = SubRate.of("subRate-1", 0.06); + final SubRate subRate2 = SubRate.of("subRate-2", 0.05); - TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("11% US", 0.11, false, CountryCode.US) + TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("11% US", 0.11, false, CountryCode.US) .state("state") .subRates(asList(subRate1, subRate2)) .build(); - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", singletonList(taxRateDraft), "desc") + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of("name", singletonList(taxRateDraft), "desc") .key("tax-category-key") .build(); - final List> result = buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); - } + assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); + } - @Test - void buildTaxRatesUpdateActions_WithSameTaxRateAndSubRates_ShouldNotBuildAnyAction() { + @Test + void buildTaxRatesUpdateActions_WithSameTaxRateAndSubRates_ShouldNotBuildAnyAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn("11% US"); - when(taxRate1.getState()).thenReturn("state"); - when(taxRate1.getId()).thenReturn("taxRate-1"); - when(taxRate1.getAmount()).thenReturn(0.11); - when(taxRate1.getCountry()).thenReturn(CountryCode.US); - when(taxRate1.isIncludedInPrice()).thenReturn(false); + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn("11% US"); + when(taxRate1.getState()).thenReturn("state"); + when(taxRate1.getId()).thenReturn("taxRate-1"); + when(taxRate1.getAmount()).thenReturn(0.11); + when(taxRate1.getCountry()).thenReturn(CountryCode.US); + when(taxRate1.isIncludedInPrice()).thenReturn(false); - final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); + final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); - when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); - when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); + when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); + when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); - final SubRate subRate1 = SubRate.of("subRate-1", 0.11); + final SubRate subRate1 = SubRate.of("subRate-1", 0.11); - TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("11% US", 0.11, false, CountryCode.US) + TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("11% US", 0.11, false, CountryCode.US) .state("state") .subRates(singletonList(subRate1)) .build(); - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", singletonList(taxRateDraft), "desc") + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of("name", singletonList(taxRateDraft), "desc") .key("tax-category-key") .build(); - final List> result = buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildTaxRatesUpdateActions_WithNullOldState_ShouldReturnOnlyReplaceAction() { + @Test + void buildTaxRatesUpdateActions_WithNullOldState_ShouldReturnOnlyReplaceAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn("11% US"); - when(taxRate1.getState()).thenReturn(null); - when(taxRate1.getId()).thenReturn("taxRate-1"); - when(taxRate1.getAmount()).thenReturn(0.11); - when(taxRate1.getCountry()).thenReturn(CountryCode.US); - when(taxRate1.isIncludedInPrice()).thenReturn(false); + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn("11% US"); + when(taxRate1.getState()).thenReturn(null); + when(taxRate1.getId()).thenReturn("taxRate-1"); + when(taxRate1.getAmount()).thenReturn(0.11); + when(taxRate1.getCountry()).thenReturn(CountryCode.US); + when(taxRate1.isIncludedInPrice()).thenReturn(false); - final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); + final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); - when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); - when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); + when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); + when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); - final SubRate subRate1 = SubRate.of("subRate-1", 0.06); - final SubRate subRate2 = SubRate.of("subRate-2", 0.05); + final SubRate subRate1 = SubRate.of("subRate-1", 0.06); + final SubRate subRate2 = SubRate.of("subRate-2", 0.05); - TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("11% US", 0.11, false, CountryCode.US) + TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("11% US", 0.11, false, CountryCode.US) .state("state") .subRates(asList(subRate1, subRate2)) .build(); - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", singletonList(taxRateDraft), "desc") + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of("name", singletonList(taxRateDraft), "desc") .key("tax-category-key") .build(); - final List> result = buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); - } + assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); + } - @Test - void buildTaxRatesUpdateActions_WithRemovedState_ShouldReturnOnlyReplaceAction() { + @Test + void buildTaxRatesUpdateActions_WithRemovedState_ShouldReturnOnlyReplaceAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn("11% US"); - when(taxRate1.getState()).thenReturn("state"); - when(taxRate1.getId()).thenReturn("taxRate-1"); - when(taxRate1.getAmount()).thenReturn(0.11); - when(taxRate1.getCountry()).thenReturn(CountryCode.US); - when(taxRate1.isIncludedInPrice()).thenReturn(false); + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn("11% US"); + when(taxRate1.getState()).thenReturn("state"); + when(taxRate1.getId()).thenReturn("taxRate-1"); + when(taxRate1.getAmount()).thenReturn(0.11); + when(taxRate1.getCountry()).thenReturn(CountryCode.US); + when(taxRate1.isIncludedInPrice()).thenReturn(false); - final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); + final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); - when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); - when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); + when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); + when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); - final SubRate subRate1 = SubRate.of("subRate-1", 0.06); - final SubRate subRate2 = SubRate.of("subRate-2", 0.05); + final SubRate subRate1 = SubRate.of("subRate-1", 0.06); + final SubRate subRate2 = SubRate.of("subRate-2", 0.05); - TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("11% US", 0.11, false, CountryCode.US) + TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("11% US", 0.11, false, CountryCode.US) .state(null) .subRates(asList(subRate1, subRate2)) .build(); - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", singletonList(taxRateDraft), "desc") + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of("name", singletonList(taxRateDraft), "desc") .key("tax-category-key") .build(); - final List> result = buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); - } + assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); + } - @Test - void buildTaxRatesUpdateActions_WithRemovedCountryCode_ShouldReturnOnlyRemoveAction() { + @Test + void buildTaxRatesUpdateActions_WithRemovedCountryCode_ShouldReturnOnlyRemoveAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn("11% US"); - when(taxRate1.getState()).thenReturn("state"); - when(taxRate1.getId()).thenReturn("taxRate-1"); - when(taxRate1.getAmount()).thenReturn(0.11); - when(taxRate1.getCountry()).thenReturn(CountryCode.US); - when(taxRate1.isIncludedInPrice()).thenReturn(false); + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn("11% US"); + when(taxRate1.getState()).thenReturn("state"); + when(taxRate1.getId()).thenReturn("taxRate-1"); + when(taxRate1.getAmount()).thenReturn(0.11); + when(taxRate1.getCountry()).thenReturn(CountryCode.US); + when(taxRate1.isIncludedInPrice()).thenReturn(false); - final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); + final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); - when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); - when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); + when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); + when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); - final SubRate subRate1 = SubRate.of("subRate-1", 0.06); - final SubRate subRate2 = SubRate.of("subRate-2", 0.05); + final SubRate subRate1 = SubRate.of("subRate-1", 0.06); + final SubRate subRate2 = SubRate.of("subRate-2", 0.05); - TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("11% US", 0.11, false, null) + TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("11% US", 0.11, false, null) .state(null) .subRates(asList(subRate1, subRate2)) .build(); - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", singletonList(taxRateDraft), "desc") + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of("name", singletonList(taxRateDraft), "desc") .key("tax-category-key") .build(); - final List> result = buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - assertThat(result).isEqualTo(singletonList(RemoveTaxRate.of("taxRate-1"))); - } + assertThat(result).isEqualTo(singletonList(RemoveTaxRate.of("taxRate-1"))); + } - @Test - void buildTaxRatesUpdateActions_WithDuplicatedTaxRateDrafts_ShouldReturnOnlyFirstDraftInAction() { + @Test + void buildTaxRatesUpdateActions_WithDuplicatedTaxRateDrafts_ShouldReturnOnlyFirstDraftInAction() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn("11% US"); - when(taxRate1.getState()).thenReturn("state"); - when(taxRate1.getId()).thenReturn("taxRate-1"); - when(taxRate1.getAmount()).thenReturn(0.11); - when(taxRate1.getCountry()).thenReturn(CountryCode.US); - when(taxRate1.isIncludedInPrice()).thenReturn(false); + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn("11% US"); + when(taxRate1.getState()).thenReturn("state"); + when(taxRate1.getId()).thenReturn("taxRate-1"); + when(taxRate1.getAmount()).thenReturn(0.11); + when(taxRate1.getCountry()).thenReturn(CountryCode.US); + when(taxRate1.isIncludedInPrice()).thenReturn(false); - final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); + final SubRate oldSubRate1 = SubRate.of("subRate-1", 0.11); - when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); - when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); + when(taxRate1.getSubRates()).thenReturn(singletonList(oldSubRate1)); + when(taxCategory.getTaxRates()).thenReturn(singletonList(taxRate1)); - final SubRate subRate1 = SubRate.of("subRate-1", 0.06); - final SubRate subRate2 = SubRate.of("subRate-2", 0.05); + final SubRate subRate1 = SubRate.of("subRate-1", 0.06); + final SubRate subRate2 = SubRate.of("subRate-2", 0.05); - TaxRateDraft taxRateDraft = TaxRateDraftBuilder - .of("11% US", 0.11, false, CountryCode.US) + TaxRateDraft taxRateDraft = + TaxRateDraftBuilder.of("11% US", 0.11, false, CountryCode.US) .state("state") .subRates(asList(subRate1, subRate2)) .build(); - TaxRateDraft duplicatedCountryCodeAndState = TaxRateDraftBuilder - .of("12% US", 0.12, false, CountryCode.US) - .state("state") - .build(); + TaxRateDraft duplicatedCountryCodeAndState = + TaxRateDraftBuilder.of("12% US", 0.12, false, CountryCode.US).state("state").build(); - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", asList(taxRateDraft, duplicatedCountryCodeAndState), "desc") + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "name", asList(taxRateDraft, duplicatedCountryCodeAndState), "desc") .key("tax-category-key") .build(); - final List> result = buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - - assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); - } - - @Test - void buildTaxRatesUpdateActions_WithNewTaxRateDraftsWithSameCountryAndDifferentStates_ShouldCreateCorrectActions() { - TaxCategory taxCategory = mock(TaxCategory.class); - when(taxCategory.getKey()).thenReturn("tax-category-key"); - when(taxCategory.getName()).thenReturn("name"); - when(taxCategory.getDescription()).thenReturn("desc"); - - final TaxRate taxRate1 = mock(TaxRate.class); - when(taxRate1.getName()).thenReturn("11% US"); - when(taxRate1.getState()).thenReturn("state-1"); - when(taxRate1.getId()).thenReturn("taxRate-1"); - when(taxRate1.getAmount()).thenReturn(0.11); - when(taxRate1.getCountry()).thenReturn(CountryCode.US); - when(taxRate1.isIncludedInPrice()).thenReturn(false); - - final TaxRate taxRate2 = mock(TaxRate.class); - when(taxRate2.getName()).thenReturn("12% US"); - when(taxRate2.getState()).thenReturn("state-2"); - when(taxRate2.getId()).thenReturn("taxRate-2"); - when(taxRate2.getAmount()).thenReturn(0.12); - when(taxRate2.getCountry()).thenReturn(CountryCode.US); - when(taxRate2.isIncludedInPrice()).thenReturn(false); - - final TaxRate taxRate4 = mock(TaxRate.class); - when(taxRate4.getName()).thenReturn("14% US"); - when(taxRate4.getState()).thenReturn("state-4"); - when(taxRate4.getId()).thenReturn("taxRate-4"); - when(taxRate4.getAmount()).thenReturn(0.14); - when(taxRate4.getCountry()).thenReturn(CountryCode.US); - when(taxRate4.isIncludedInPrice()).thenReturn(false); - - when(taxCategory.getTaxRates()).thenReturn(asList(taxRate1, taxRate2, taxRate4)); - - final SubRate subRate3 = SubRate.of("subRate-3", 0.13); - TaxRateDraft taxRateDraft1 = TaxRateDraftBuilder - .of("11% US", 0.11, false, CountryCode.US) - .state("state-1") - .build(); - - TaxRateDraft taxRateDraft2 = TaxRateDraftBuilder - .of("12% US", 0.12, false, CountryCode.US) - .state("state-2") - .build(); - - TaxRateDraft newTaxRateDraft = TaxRateDraftBuilder - .of("13% US", 0.13, false, CountryCode.US) + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + + assertThat(result).isEqualTo(singletonList(ReplaceTaxRate.of("taxRate-1", taxRateDraft))); + } + + @Test + void + buildTaxRatesUpdateActions_WithNewTaxRateDraftsWithSameCountryAndDifferentStates_ShouldCreateCorrectActions() { + TaxCategory taxCategory = mock(TaxCategory.class); + when(taxCategory.getKey()).thenReturn("tax-category-key"); + when(taxCategory.getName()).thenReturn("name"); + when(taxCategory.getDescription()).thenReturn("desc"); + + final TaxRate taxRate1 = mock(TaxRate.class); + when(taxRate1.getName()).thenReturn("11% US"); + when(taxRate1.getState()).thenReturn("state-1"); + when(taxRate1.getId()).thenReturn("taxRate-1"); + when(taxRate1.getAmount()).thenReturn(0.11); + when(taxRate1.getCountry()).thenReturn(CountryCode.US); + when(taxRate1.isIncludedInPrice()).thenReturn(false); + + final TaxRate taxRate2 = mock(TaxRate.class); + when(taxRate2.getName()).thenReturn("12% US"); + when(taxRate2.getState()).thenReturn("state-2"); + when(taxRate2.getId()).thenReturn("taxRate-2"); + when(taxRate2.getAmount()).thenReturn(0.12); + when(taxRate2.getCountry()).thenReturn(CountryCode.US); + when(taxRate2.isIncludedInPrice()).thenReturn(false); + + final TaxRate taxRate4 = mock(TaxRate.class); + when(taxRate4.getName()).thenReturn("14% US"); + when(taxRate4.getState()).thenReturn("state-4"); + when(taxRate4.getId()).thenReturn("taxRate-4"); + when(taxRate4.getAmount()).thenReturn(0.14); + when(taxRate4.getCountry()).thenReturn(CountryCode.US); + when(taxRate4.isIncludedInPrice()).thenReturn(false); + + when(taxCategory.getTaxRates()).thenReturn(asList(taxRate1, taxRate2, taxRate4)); + + final SubRate subRate3 = SubRate.of("subRate-3", 0.13); + TaxRateDraft taxRateDraft1 = + TaxRateDraftBuilder.of("11% US", 0.11, false, CountryCode.US).state("state-1").build(); + + TaxRateDraft taxRateDraft2 = + TaxRateDraftBuilder.of("12% US", 0.12, false, CountryCode.US).state("state-2").build(); + + TaxRateDraft newTaxRateDraft = + TaxRateDraftBuilder.of("13% US", 0.13, false, CountryCode.US) .state("state-3") .subRates(singletonList(subRate3)) .build(); - TaxCategoryDraft taxCategoryDraft = TaxCategoryDraftBuilder - .of("name", asList(taxRateDraft1, taxRateDraft2, newTaxRateDraft), "desc") + TaxCategoryDraft taxCategoryDraft = + TaxCategoryDraftBuilder.of( + "name", asList(taxRateDraft1, taxRateDraft2, newTaxRateDraft), "desc") .key("tax-category-key") .build(); - final List> result = buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); - - assertThat(result).isEqualTo(asList(RemoveTaxRate.of("taxRate-4"), AddTaxRate.of(newTaxRateDraft))); - } + final List> result = + buildTaxRateUpdateActions(taxCategory, taxCategoryDraft); + assertThat(result) + .isEqualTo(asList(RemoveTaxRate.of("taxRate-4"), AddTaxRate.of(newTaxRateDraft))); + } } diff --git a/src/test/java/com/commercetools/sync/types/TypeSyncOptionsBuilderTest.java b/src/test/java/com/commercetools/sync/types/TypeSyncOptionsBuilderTest.java index c502ee0f58..c7dc2721a7 100644 --- a/src/test/java/com/commercetools/sync/types/TypeSyncOptionsBuilderTest.java +++ b/src/test/java/com/commercetools/sync/types/TypeSyncOptionsBuilderTest.java @@ -1,5 +1,15 @@ package com.commercetools.sync.types; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.SyncException; import com.commercetools.sync.commons.utils.QuadConsumer; import com.commercetools.sync.commons.utils.TriConsumer; @@ -10,269 +20,243 @@ import io.sphere.sdk.types.TypeDraft; import io.sphere.sdk.types.TypeDraftBuilder; import io.sphere.sdk.types.commands.updateactions.ChangeName; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; import java.util.function.Function; - -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class TypeSyncOptionsBuilderTest { - private static final SphereClient CTP_CLIENT = mock(SphereClient.class); - private TypeSyncOptionsBuilder typeSyncOptionsBuilder = TypeSyncOptionsBuilder.of(CTP_CLIENT); - - @Test - void of_WithClient_ShouldCreateTypeSyncOptionsBuilder() { - final TypeSyncOptionsBuilder builder = TypeSyncOptionsBuilder.of(CTP_CLIENT); - assertThat(builder).isNotNull(); - } - - @Test - void build_WithClient_ShouldBuildSyncOptions() { - final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); - assertThat(typeSyncOptions).isNotNull(); - assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNull(); - assertThat(typeSyncOptions.getBeforeCreateCallback()).isNull(); - assertThat(typeSyncOptions.getErrorCallback()).isNull(); - assertThat(typeSyncOptions.getWarningCallback()).isNull(); - assertThat(typeSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); - assertThat(typeSyncOptions.getBatchSize()).isEqualTo(TypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - assertThat(typeSyncOptions.getCacheSize()).isEqualTo(10_000); - } - - @Test - void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { - final TriFunction>, TypeDraft, Type, List>> - beforeUpdateCallback = (updateActions, newType, oldType) -> emptyList(); - - typeSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); - - final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); - assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNotNull(); - } - - @Test - void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { - typeSyncOptionsBuilder.beforeCreateCallback((newType) -> null); - - final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); - assertThat(typeSyncOptions.getBeforeCreateCallback()).isNotNull(); - } - - @Test - void errorCallBack_WithCallBack_ShouldSetCallBack() { - final QuadConsumer, Optional, List>> - mockErrorCallBack = (exception, newResource, oldResource, updateActions) -> { - }; - typeSyncOptionsBuilder.errorCallback(mockErrorCallBack); - - final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); - assertThat(typeSyncOptions.getErrorCallback()).isNotNull(); - } - - @Test - void warningCallBack_WithCallBack_ShouldSetCallBack() { - final TriConsumer, Optional> mockWarningCallBack = - (exception, newResource, oldResource) -> { - }; - typeSyncOptionsBuilder.warningCallback(mockWarningCallBack); - - final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); - assertThat(typeSyncOptions.getWarningCallback()).isNotNull(); - } - - @Test - void getThis_ShouldReturnCorrectInstance() { - final TypeSyncOptionsBuilder instance = typeSyncOptionsBuilder.getThis(); - assertThat(instance).isNotNull(); - assertThat(instance).isInstanceOf(TypeSyncOptionsBuilder.class); - assertThat(instance).isEqualTo(typeSyncOptionsBuilder); - } - - @Test - void typeSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(30) - .beforeCreateCallback((newType) -> null) - .beforeUpdateCallback((updateActions, newType, oldType) -> emptyList()) - .build(); - assertThat(typeSyncOptions).isNotNull(); - } - - @Test - void batchSize_WithPositiveValue_ShouldSetBatchSize() { - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(10) - .build(); - assertThat(typeSyncOptions.getBatchSize()).isEqualTo(10); - } - - @Test - void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final TypeSyncOptions typeSyncOptionsWithZeroBatchSize = TypeSyncOptionsBuilder.of(CTP_CLIENT) - .batchSize(0) - .build(); - assertThat(typeSyncOptionsWithZeroBatchSize.getBatchSize()) - .isEqualTo(TypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - - final TypeSyncOptions typeSyncOptionsWithNegativeBatchSize = TypeSyncOptionsBuilder - .of(CTP_CLIENT) - .batchSize(-100) - .build(); - assertThat(typeSyncOptionsWithNegativeBatchSize.getBatchSize()) - .isEqualTo(TypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); - } - - @Test - void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT) - .build(); - assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - - final List> filteredList = - typeSyncOptions.applyBeforeUpdateCallback(updateActions, mock(TypeDraft.class), mock(Type.class)); - - assertThat(filteredList).isSameAs(updateActions); - } - - @Test - void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { - final TriFunction>, TypeDraft, Type, List>> - beforeUpdateCallback = (updateActions, newType, oldType) -> null; - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = - typeSyncOptions.applyBeforeUpdateCallback(updateActions, mock(TypeDraft.class), mock(Type.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - private interface MockTriFunction extends - TriFunction>, TypeDraft, Type, List>> { - } - - @Test - void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { - final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); - - final TypeSyncOptions typeSyncOptions = - TypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback(beforeUpdateCallback) - .build(); - - assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = emptyList(); - final List> filteredList = - typeSyncOptions.applyBeforeUpdateCallback(updateActions, mock(TypeDraft.class), mock(Type.class)); - - assertThat(filteredList).isEmpty(); - verify(beforeUpdateCallback, never()).apply(any(), any(), any()); - } - - @Test - void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { - final TriFunction>, TypeDraft, Type, List>> - beforeUpdateCallback = (updateActions, newType, oldType) -> emptyList(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeUpdateCallback( - beforeUpdateCallback) - .build(); - assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNotNull(); - - final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); - final List> filteredList = - typeSyncOptions.applyBeforeUpdateCallback(updateActions, mock(TypeDraft.class), mock(Type.class)); - assertThat(filteredList).isNotEqualTo(updateActions); - assertThat(filteredList).isEmpty(); - } - - @Test - void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { - final Function draftFunction = - typeDraft -> TypeDraftBuilder.of(typeDraft).key(typeDraft.getKey() + "_filteredKey").build(); - - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - - assertThat(typeSyncOptions.getBeforeCreateCallback()).isNotNull(); - - final TypeDraft resourceDraft = mock(TypeDraft.class); - when(resourceDraft.getKey()).thenReturn("myKey"); - - - final Optional filteredDraft = typeSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isNotEmpty(); - assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filteredKey"); - } - - @Test - void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT).build(); - assertThat(typeSyncOptions.getBeforeCreateCallback()).isNull(); - - final TypeDraft resourceDraft = mock(TypeDraft.class); - final Optional filteredDraft = typeSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).containsSame(resourceDraft); - } - - @Test - void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { - final Function draftFunction = typeDraft -> null; - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT) - .beforeCreateCallback(draftFunction) - .build(); - assertThat(typeSyncOptions.getBeforeCreateCallback()).isNotNull(); - - final TypeDraft resourceDraft = mock(TypeDraft.class); - final Optional filteredDraft = typeSyncOptions.applyBeforeCreateCallback(resourceDraft); - - assertThat(filteredDraft).isEmpty(); - } - - @Test - void cacheSize_WithPositiveValue_ShouldSetCacheSize() { - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(10) - .build(); - assertThat(typeSyncOptions.getCacheSize()).isEqualTo(10); - } - - @Test - void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { - final TypeSyncOptions typeSyncOptionsWithZeroCacheSize = TypeSyncOptionsBuilder.of(CTP_CLIENT) - .cacheSize(0) - .build(); - assertThat(typeSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); - - final TypeSyncOptions typeSyncOptionsWithNegativeCacheSize = TypeSyncOptionsBuilder - .of(CTP_CLIENT) - .cacheSize(-100) + private static final SphereClient CTP_CLIENT = mock(SphereClient.class); + private TypeSyncOptionsBuilder typeSyncOptionsBuilder = TypeSyncOptionsBuilder.of(CTP_CLIENT); + + @Test + void of_WithClient_ShouldCreateTypeSyncOptionsBuilder() { + final TypeSyncOptionsBuilder builder = TypeSyncOptionsBuilder.of(CTP_CLIENT); + assertThat(builder).isNotNull(); + } + + @Test + void build_WithClient_ShouldBuildSyncOptions() { + final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); + assertThat(typeSyncOptions).isNotNull(); + assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNull(); + assertThat(typeSyncOptions.getBeforeCreateCallback()).isNull(); + assertThat(typeSyncOptions.getErrorCallback()).isNull(); + assertThat(typeSyncOptions.getWarningCallback()).isNull(); + assertThat(typeSyncOptions.getCtpClient()).isEqualTo(CTP_CLIENT); + assertThat(typeSyncOptions.getBatchSize()).isEqualTo(TypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + assertThat(typeSyncOptions.getCacheSize()).isEqualTo(10_000); + } + + @Test + void beforeUpdateCallback_WithFilterAsCallback_ShouldSetCallback() { + final TriFunction>, TypeDraft, Type, List>> + beforeUpdateCallback = (updateActions, newType, oldType) -> emptyList(); + + typeSyncOptionsBuilder.beforeUpdateCallback(beforeUpdateCallback); + + final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); + assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNotNull(); + } + + @Test + void beforeCreateCallback_WithFilterAsCallback_ShouldSetCallback() { + typeSyncOptionsBuilder.beforeCreateCallback((newType) -> null); + + final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); + assertThat(typeSyncOptions.getBeforeCreateCallback()).isNotNull(); + } + + @Test + void errorCallBack_WithCallBack_ShouldSetCallBack() { + final QuadConsumer, Optional, List>> + mockErrorCallBack = (exception, newResource, oldResource, updateActions) -> {}; + typeSyncOptionsBuilder.errorCallback(mockErrorCallBack); + + final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); + assertThat(typeSyncOptions.getErrorCallback()).isNotNull(); + } + + @Test + void warningCallBack_WithCallBack_ShouldSetCallBack() { + final TriConsumer, Optional> mockWarningCallBack = + (exception, newResource, oldResource) -> {}; + typeSyncOptionsBuilder.warningCallback(mockWarningCallBack); + + final TypeSyncOptions typeSyncOptions = typeSyncOptionsBuilder.build(); + assertThat(typeSyncOptions.getWarningCallback()).isNotNull(); + } + + @Test + void getThis_ShouldReturnCorrectInstance() { + final TypeSyncOptionsBuilder instance = typeSyncOptionsBuilder.getThis(); + assertThat(instance).isNotNull(); + assertThat(instance).isInstanceOf(TypeSyncOptionsBuilder.class); + assertThat(instance).isEqualTo(typeSyncOptionsBuilder); + } + + @Test + void typeSyncOptionsBuilderSetters_ShouldBeCallableAfterBaseSyncOptionsBuildSetters() { + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_CLIENT) + .batchSize(30) + .beforeCreateCallback((newType) -> null) + .beforeUpdateCallback((updateActions, newType, oldType) -> emptyList()) .build(); - assertThat(typeSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); - } - - + assertThat(typeSyncOptions).isNotNull(); + } + + @Test + void batchSize_WithPositiveValue_ShouldSetBatchSize() { + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_CLIENT).batchSize(10).build(); + assertThat(typeSyncOptions.getBatchSize()).isEqualTo(10); + } + + @Test + void batchSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final TypeSyncOptions typeSyncOptionsWithZeroBatchSize = + TypeSyncOptionsBuilder.of(CTP_CLIENT).batchSize(0).build(); + assertThat(typeSyncOptionsWithZeroBatchSize.getBatchSize()) + .isEqualTo(TypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + + final TypeSyncOptions typeSyncOptionsWithNegativeBatchSize = + TypeSyncOptionsBuilder.of(CTP_CLIENT).batchSize(-100).build(); + assertThat(typeSyncOptionsWithNegativeBatchSize.getBatchSize()) + .isEqualTo(TypeSyncOptionsBuilder.BATCH_SIZE_DEFAULT); + } + + @Test + void applyBeforeUpdateCallBack_WithNullCallback_ShouldReturnIdenticalList() { + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNull(); + + final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); + + final List> filteredList = + typeSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(TypeDraft.class), mock(Type.class)); + + assertThat(filteredList).isSameAs(updateActions); + } + + @Test + void applyBeforeUpdateCallBack_WithNullReturnCallback_ShouldReturnEmptyList() { + final TriFunction>, TypeDraft, Type, List>> + beforeUpdateCallback = (updateActions, newType, oldType) -> null; + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_CLIENT).beforeUpdateCallback(beforeUpdateCallback).build(); + assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + typeSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(TypeDraft.class), mock(Type.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + private interface MockTriFunction + extends TriFunction>, TypeDraft, Type, List>> {} + + @Test + void applyBeforeUpdateCallBack_WithEmptyUpdateActions_ShouldNotApplyBeforeUpdateCallback() { + final MockTriFunction beforeUpdateCallback = mock(MockTriFunction.class); + + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_CLIENT).beforeUpdateCallback(beforeUpdateCallback).build(); + + assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = emptyList(); + final List> filteredList = + typeSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(TypeDraft.class), mock(Type.class)); + + assertThat(filteredList).isEmpty(); + verify(beforeUpdateCallback, never()).apply(any(), any(), any()); + } + + @Test + void applyBeforeUpdateCallBack_WithCallback_ShouldReturnFilteredList() { + final TriFunction>, TypeDraft, Type, List>> + beforeUpdateCallback = (updateActions, newType, oldType) -> emptyList(); + + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_CLIENT).beforeUpdateCallback(beforeUpdateCallback).build(); + assertThat(typeSyncOptions.getBeforeUpdateCallback()).isNotNull(); + + final List> updateActions = singletonList(ChangeName.of(ofEnglish("name"))); + final List> filteredList = + typeSyncOptions.applyBeforeUpdateCallback( + updateActions, mock(TypeDraft.class), mock(Type.class)); + assertThat(filteredList).isNotEqualTo(updateActions); + assertThat(filteredList).isEmpty(); + } + + @Test + void applyBeforeCreateCallBack_WithCallback_ShouldReturnFilteredDraft() { + final Function draftFunction = + typeDraft -> + TypeDraftBuilder.of(typeDraft).key(typeDraft.getKey() + "_filteredKey").build(); + + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + + assertThat(typeSyncOptions.getBeforeCreateCallback()).isNotNull(); + + final TypeDraft resourceDraft = mock(TypeDraft.class); + when(resourceDraft.getKey()).thenReturn("myKey"); + + final Optional filteredDraft = + typeSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isNotEmpty(); + assertThat(filteredDraft.get().getKey()).isEqualTo("myKey_filteredKey"); + } + + @Test + void applyBeforeCreateCallBack_WithNullCallback_ShouldReturnIdenticalDraftInOptional() { + final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder.of(CTP_CLIENT).build(); + assertThat(typeSyncOptions.getBeforeCreateCallback()).isNull(); + + final TypeDraft resourceDraft = mock(TypeDraft.class); + final Optional filteredDraft = + typeSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).containsSame(resourceDraft); + } + + @Test + void applyBeforeCreateCallBack_WithCallbackReturningNull_ShouldReturnEmptyOptional() { + final Function draftFunction = typeDraft -> null; + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_CLIENT).beforeCreateCallback(draftFunction).build(); + assertThat(typeSyncOptions.getBeforeCreateCallback()).isNotNull(); + + final TypeDraft resourceDraft = mock(TypeDraft.class); + final Optional filteredDraft = + typeSyncOptions.applyBeforeCreateCallback(resourceDraft); + + assertThat(filteredDraft).isEmpty(); + } + + @Test + void cacheSize_WithPositiveValue_ShouldSetCacheSize() { + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(10).build(); + assertThat(typeSyncOptions.getCacheSize()).isEqualTo(10); + } + + @Test + void cacheSize_WithZeroOrNegativeValue_ShouldFallBackToDefaultValue() { + final TypeSyncOptions typeSyncOptionsWithZeroCacheSize = + TypeSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(0).build(); + assertThat(typeSyncOptionsWithZeroCacheSize.getCacheSize()).isEqualTo(10_000); + + final TypeSyncOptions typeSyncOptionsWithNegativeCacheSize = + TypeSyncOptionsBuilder.of(CTP_CLIENT).cacheSize(-100).build(); + assertThat(typeSyncOptionsWithNegativeCacheSize.getCacheSize()).isEqualTo(10_000); + } } diff --git a/src/test/java/com/commercetools/sync/types/TypeSyncTest.java b/src/test/java/com/commercetools/sync/types/TypeSyncTest.java index 626aaaf938..e0b7a3c82e 100644 --- a/src/test/java/com/commercetools/sync/types/TypeSyncTest.java +++ b/src/test/java/com/commercetools/sync/types/TypeSyncTest.java @@ -1,20 +1,5 @@ package com.commercetools.sync.types; -import com.commercetools.sync.services.TypeService; -import com.commercetools.sync.types.helpers.TypeSyncStatistics; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.models.SphereException; -import io.sphere.sdk.types.ResourceTypeIdsSetBuilder; -import io.sphere.sdk.types.Type; -import io.sphere.sdk.types.TypeDraft; -import io.sphere.sdk.types.TypeDraftBuilder; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletionException; - import static com.commercetools.sync.commons.asserts.statistics.AssertionsForStatistics.assertThat; import static io.sphere.sdk.models.LocalizedString.ofEnglish; import static java.util.Collections.emptyList; @@ -32,112 +17,134 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.commercetools.sync.services.TypeService; +import com.commercetools.sync.types.helpers.TypeSyncStatistics; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.models.SphereException; +import io.sphere.sdk.types.ResourceTypeIdsSetBuilder; +import io.sphere.sdk.types.Type; +import io.sphere.sdk.types.TypeDraft; +import io.sphere.sdk.types.TypeDraftBuilder; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletionException; +import org.junit.jupiter.api.Test; + class TypeSyncTest { - @Test - void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { - // preparation - - final TypeDraft newTypeDraft = TypeDraftBuilder.of( - "foo", - ofEnglish("name"), - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(ofEnglish("desc")) - .fieldDefinitions(emptyList()) - .build(); - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - - final TypeSyncOptions syncOptions = TypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + @Test + void sync_WithErrorFetchingExistingKeys_ShouldExecuteCallbackOnErrorAndIncreaseFailedCounter() { + // preparation + + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + "foo", ofEnglish("name"), ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(ofEnglish("desc")) + .fieldDefinitions(emptyList()) + .build(); + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + + final TypeSyncOptions syncOptions = + TypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - - final TypeService mockTypeService = mock(TypeService.class); - - when(mockTypeService.fetchMatchingTypesByKeys(singleton(newTypeDraft.getKey()))) - .thenReturn(supplyAsync(() -> { throw new SphereException(); })); - - final TypeSync typeSync = new TypeSync(syncOptions, mockTypeService); - - // test - final TypeSyncStatistics typeSyncStatistics = typeSync - .sync(singletonList(newTypeDraft)) - .toCompletableFuture().join(); - - // assertions - assertThat(errorMessages) - .hasSize(1) - .hasOnlyOneElementSatisfying(message -> - assertThat(message).isEqualTo("Failed to fetch existing types with keys: '[foo]'.") - ); - - assertThat(exceptions) - .hasSize(1) - .hasOnlyOneElementSatisfying(throwable -> { - assertThat(throwable).isExactlyInstanceOf(CompletionException.class); - assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); + final TypeService mockTypeService = mock(TypeService.class); + + when(mockTypeService.fetchMatchingTypesByKeys(singleton(newTypeDraft.getKey()))) + .thenReturn( + supplyAsync( + () -> { + throw new SphereException(); + })); + + final TypeSync typeSync = new TypeSync(syncOptions, mockTypeService); + + // test + final TypeSyncStatistics typeSyncStatistics = + typeSync.sync(singletonList(newTypeDraft)).toCompletableFuture().join(); + + // assertions + assertThat(errorMessages) + .hasSize(1) + .hasOnlyOneElementSatisfying( + message -> + assertThat(message) + .isEqualTo("Failed to fetch existing types with keys: '[foo]'.")); + + assertThat(exceptions) + .hasSize(1) + .hasOnlyOneElementSatisfying( + throwable -> { + assertThat(throwable).isExactlyInstanceOf(CompletionException.class); + assertThat(throwable).hasCauseExactlyInstanceOf(SphereException.class); }); - assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); - } - - @Test - void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { - // preparation - final TypeDraft newTypeDraft = TypeDraftBuilder - .of("newType", ofEnglish( "typeName"), ResourceTypeIdsSetBuilder.of().addChannels()) - .build(); + assertThat(typeSyncStatistics).hasValues(1, 0, 0, 1); + } - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(mock(SphereClient.class)) + @Test + void sync_WithOnlyDraftsToCreate_ShouldCallBeforeCreateCallback() { + // preparation + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + "newType", ofEnglish("typeName"), ResourceTypeIdsSetBuilder.of().addChannels()) .build(); - final TypeService typeService = mock(TypeService.class); - when(typeService.fetchMatchingTypesByKeys(anySet())).thenReturn(completedFuture(emptySet())); - when(typeService.createType(any())).thenReturn(completedFuture(Optional.empty())); + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final TypeSyncOptions spyTypeSyncOptions = spy(typeSyncOptions); + final TypeService typeService = mock(TypeService.class); + when(typeService.fetchMatchingTypesByKeys(anySet())).thenReturn(completedFuture(emptySet())); + when(typeService.createType(any())).thenReturn(completedFuture(Optional.empty())); - // test - new TypeSync(spyTypeSyncOptions, typeService) - .sync(singletonList(newTypeDraft)).toCompletableFuture().join(); + final TypeSyncOptions spyTypeSyncOptions = spy(typeSyncOptions); - // assertion - verify(spyTypeSyncOptions).applyBeforeCreateCallback(newTypeDraft); - verify(spyTypeSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); - } + // test + new TypeSync(spyTypeSyncOptions, typeService) + .sync(singletonList(newTypeDraft)) + .toCompletableFuture() + .join(); - @Test - void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { - // preparation - final TypeDraft newTypeDraft = TypeDraftBuilder - .of("newType", ofEnglish( "typeName"), ResourceTypeIdsSetBuilder.of().addChannels()) - .build(); + // assertion + verify(spyTypeSyncOptions).applyBeforeCreateCallback(newTypeDraft); + verify(spyTypeSyncOptions, never()).applyBeforeUpdateCallback(any(), any(), any()); + } - final TypeSyncOptions typeSyncOptions = TypeSyncOptionsBuilder - .of(mock(SphereClient.class)) + @Test + void sync_WithOnlyDraftsToUpdate_ShouldOnlyCallBeforeUpdateCallback() { + // preparation + final TypeDraft newTypeDraft = + TypeDraftBuilder.of( + "newType", ofEnglish("typeName"), ResourceTypeIdsSetBuilder.of().addChannels()) .build(); - final Type mockedExistingType = mock(Type.class); - when(mockedExistingType.getKey()).thenReturn(newTypeDraft.getKey()); + final TypeSyncOptions typeSyncOptions = + TypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final TypeService typeService = mock(TypeService.class); - when(typeService.fetchMatchingTypesByKeys(anySet())).thenReturn(completedFuture(singleton(mockedExistingType))); - when(typeService.updateType(any(), any())).thenReturn(completedFuture(mockedExistingType)); + final Type mockedExistingType = mock(Type.class); + when(mockedExistingType.getKey()).thenReturn(newTypeDraft.getKey()); - final TypeSyncOptions spyTypeSyncOptions = spy(typeSyncOptions); + final TypeService typeService = mock(TypeService.class); + when(typeService.fetchMatchingTypesByKeys(anySet())) + .thenReturn(completedFuture(singleton(mockedExistingType))); + when(typeService.updateType(any(), any())).thenReturn(completedFuture(mockedExistingType)); - // test - new TypeSync(spyTypeSyncOptions, typeService) - .sync(singletonList(newTypeDraft)).toCompletableFuture().join(); + final TypeSyncOptions spyTypeSyncOptions = spy(typeSyncOptions); - // assertion - verify(spyTypeSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); - verify(spyTypeSyncOptions, never()).applyBeforeCreateCallback(newTypeDraft); - } + // test + new TypeSync(spyTypeSyncOptions, typeService) + .sync(singletonList(newTypeDraft)) + .toCompletableFuture() + .join(); + // assertion + verify(spyTypeSyncOptions).applyBeforeUpdateCallback(any(), any(), any()); + verify(spyTypeSyncOptions, never()).applyBeforeCreateCallback(newTypeDraft); + } } diff --git a/src/test/java/com/commercetools/sync/types/helpers/TypeBatchValidatorTest.java b/src/test/java/com/commercetools/sync/types/helpers/TypeBatchValidatorTest.java index b5a4a7715b..2d22fec6c2 100644 --- a/src/test/java/com/commercetools/sync/types/helpers/TypeBatchValidatorTest.java +++ b/src/test/java/com/commercetools/sync/types/helpers/TypeBatchValidatorTest.java @@ -1,21 +1,5 @@ package com.commercetools.sync.types.helpers; -import com.commercetools.sync.types.TypeSyncOptions; -import com.commercetools.sync.types.TypeSyncOptionsBuilder; -import io.sphere.sdk.client.SphereClient; -import io.sphere.sdk.types.TypeDraft; -import io.sphere.sdk.types.TypeDraftBuilder; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - import static com.commercetools.sync.types.helpers.TypeBatchValidator.TYPE_DRAFT_IS_NULL; import static com.commercetools.sync.types.helpers.TypeBatchValidator.TYPE_DRAFT_KEY_NOT_SET; import static io.sphere.sdk.models.LocalizedString.ofEnglish; @@ -27,99 +11,114 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class TypeBatchValidatorTest { - private List errorCallBackMessages; - private TypeSyncOptions syncOptions; - private TypeSyncStatistics syncStatistics; - - @BeforeEach - void setup() { - errorCallBackMessages = new ArrayList<>(); - final SphereClient ctpClient = mock(SphereClient.class); - syncOptions = TypeSyncOptionsBuilder - .of(ctpClient) - .errorCallback((exception, oldResource, newResource, actions) -> { - errorCallBackMessages.add(exception.getMessage()); - }) - .build(); - syncStatistics = new TypeSyncStatistics(); - } - - @Test - void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { - final Set validDrafts = getValidDrafts(emptyList()); - - assertThat(validDrafts).isEmpty(); - assertThat(errorCallBackMessages).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithNullTypeDraft_ShouldHaveValidationErrorAndEmptyResult() { - final Set validDrafts = getValidDrafts(Collections.singletonList(null)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)).isEqualTo(TYPE_DRAFT_IS_NULL); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithTypeDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { - final TypeDraft typeDraft = mock(TypeDraft.class); - final Set validDrafts = getValidDrafts(Collections.singletonList(typeDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(TYPE_DRAFT_KEY_NOT_SET, typeDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithTypeDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { - final TypeDraft typeDraft = mock(TypeDraft.class); - when(typeDraft.getKey()).thenReturn(EMPTY); - final Set validDrafts = getValidDrafts(Collections.singletonList(typeDraft)); - - assertThat(errorCallBackMessages).hasSize(1); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(format(TYPE_DRAFT_KEY_NOT_SET, typeDraft.getName())); - assertThat(validDrafts).isEmpty(); - } - - @Test - void validateAndCollectReferencedKeys_WithMixOfValidAndInvalidDrafts_ShouldValidateCorrectly() { - final TypeDraft typeDraft = TypeDraftBuilder - .of("foo", ofEnglish("name"), emptySet()) - .build(); - - final TypeDraft typeDraftWithEmptyKey = TypeDraftBuilder - .of("", ofEnglish("name"), emptySet()) - .build(); +import com.commercetools.sync.types.TypeSyncOptions; +import com.commercetools.sync.types.TypeSyncOptionsBuilder; +import io.sphere.sdk.client.SphereClient; +import io.sphere.sdk.types.TypeDraft; +import io.sphere.sdk.types.TypeDraftBuilder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; - final TypeDraft typeDraftWithNullKey = TypeDraftBuilder - .of(null, ofEnglish("name"), emptySet()) +class TypeBatchValidatorTest { + private List errorCallBackMessages; + private TypeSyncOptions syncOptions; + private TypeSyncStatistics syncStatistics; + + @BeforeEach + void setup() { + errorCallBackMessages = new ArrayList<>(); + final SphereClient ctpClient = mock(SphereClient.class); + syncOptions = + TypeSyncOptionsBuilder.of(ctpClient) + .errorCallback( + (exception, oldResource, newResource, actions) -> { + errorCallBackMessages.add(exception.getMessage()); + }) .build(); - - final TypeBatchValidator batchValidator = new TypeBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = batchValidator.validateAndCollectReferencedKeys( + syncStatistics = new TypeSyncStatistics(); + } + + @Test + void validateAndCollectReferencedKeys_WithEmptyDraft_ShouldHaveEmptyResult() { + final Set validDrafts = getValidDrafts(emptyList()); + + assertThat(validDrafts).isEmpty(); + assertThat(errorCallBackMessages).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithNullTypeDraft_ShouldHaveValidationErrorAndEmptyResult() { + final Set validDrafts = getValidDrafts(Collections.singletonList(null)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)).isEqualTo(TYPE_DRAFT_IS_NULL); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithTypeDraftWithNullKey_ShouldHaveValidationErrorAndEmptyResult() { + final TypeDraft typeDraft = mock(TypeDraft.class); + final Set validDrafts = getValidDrafts(Collections.singletonList(typeDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(TYPE_DRAFT_KEY_NOT_SET, typeDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void + validateAndCollectReferencedKeys_WithTypeDraftWithEmptyKey_ShouldHaveValidationErrorAndEmptyResult() { + final TypeDraft typeDraft = mock(TypeDraft.class); + when(typeDraft.getKey()).thenReturn(EMPTY); + final Set validDrafts = getValidDrafts(Collections.singletonList(typeDraft)); + + assertThat(errorCallBackMessages).hasSize(1); + assertThat(errorCallBackMessages.get(0)) + .isEqualTo(format(TYPE_DRAFT_KEY_NOT_SET, typeDraft.getName())); + assertThat(validDrafts).isEmpty(); + } + + @Test + void validateAndCollectReferencedKeys_WithMixOfValidAndInvalidDrafts_ShouldValidateCorrectly() { + final TypeDraft typeDraft = TypeDraftBuilder.of("foo", ofEnglish("name"), emptySet()).build(); + + final TypeDraft typeDraftWithEmptyKey = + TypeDraftBuilder.of("", ofEnglish("name"), emptySet()).build(); + + final TypeDraft typeDraftWithNullKey = + TypeDraftBuilder.of(null, ofEnglish("name"), emptySet()).build(); + + final TypeBatchValidator batchValidator = new TypeBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + batchValidator.validateAndCollectReferencedKeys( Arrays.asList(null, typeDraft, typeDraftWithEmptyKey, typeDraftWithNullKey)); - assertThat(pair.getLeft()).containsExactlyInAnyOrder(typeDraft); - assertThat(pair.getRight()).containsExactlyInAnyOrder("foo"); - - assertThat(errorCallBackMessages).hasSize(3); - assertThat(errorCallBackMessages.get(0)) - .isEqualTo(TYPE_DRAFT_IS_NULL); - assertThat(errorCallBackMessages.get(1)) - .isEqualTo(format(TYPE_DRAFT_KEY_NOT_SET, typeDraftWithEmptyKey.getName())); - assertThat(errorCallBackMessages.get(2)) - .isEqualTo(format(TYPE_DRAFT_KEY_NOT_SET, typeDraftWithNullKey.getName())); - } - - @Nonnull - private Set getValidDrafts(@Nonnull final List typeDrafts) { - final TypeBatchValidator batchValidator = new TypeBatchValidator(syncOptions, syncStatistics); - final ImmutablePair, Set> pair = - batchValidator.validateAndCollectReferencedKeys(typeDrafts); - return pair.getLeft(); - } + assertThat(pair.getLeft()).containsExactlyInAnyOrder(typeDraft); + assertThat(pair.getRight()).containsExactlyInAnyOrder("foo"); + + assertThat(errorCallBackMessages).hasSize(3); + assertThat(errorCallBackMessages.get(0)).isEqualTo(TYPE_DRAFT_IS_NULL); + assertThat(errorCallBackMessages.get(1)) + .isEqualTo(format(TYPE_DRAFT_KEY_NOT_SET, typeDraftWithEmptyKey.getName())); + assertThat(errorCallBackMessages.get(2)) + .isEqualTo(format(TYPE_DRAFT_KEY_NOT_SET, typeDraftWithNullKey.getName())); + } + + @Nonnull + private Set getValidDrafts(@Nonnull final List typeDrafts) { + final TypeBatchValidator batchValidator = new TypeBatchValidator(syncOptions, syncStatistics); + final ImmutablePair, Set> pair = + batchValidator.validateAndCollectReferencedKeys(typeDrafts); + return pair.getLeft(); + } } diff --git a/src/test/java/com/commercetools/sync/types/helpers/TypeSyncStatisticsTest.java b/src/test/java/com/commercetools/sync/types/helpers/TypeSyncStatisticsTest.java index 6d5454d87d..56b18210fd 100644 --- a/src/test/java/com/commercetools/sync/types/helpers/TypeSyncStatisticsTest.java +++ b/src/test/java/com/commercetools/sync/types/helpers/TypeSyncStatisticsTest.java @@ -1,28 +1,28 @@ package com.commercetools.sync.types.helpers; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class TypeSyncStatisticsTest { - private TypeSyncStatistics typeSyncStatistics; + private TypeSyncStatistics typeSyncStatistics; - @BeforeEach - void setup() { - typeSyncStatistics = new TypeSyncStatistics(); - } + @BeforeEach + void setup() { + typeSyncStatistics = new TypeSyncStatistics(); + } - @Test - void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { - typeSyncStatistics.incrementCreated(1); - typeSyncStatistics.incrementFailed(2); - typeSyncStatistics.incrementUpdated(3); - typeSyncStatistics.incrementProcessed(6); + @Test + void getReportMessage_WithIncrementedStats_ShouldGetCorrectMessage() { + typeSyncStatistics.incrementCreated(1); + typeSyncStatistics.incrementFailed(2); + typeSyncStatistics.incrementUpdated(3); + typeSyncStatistics.incrementProcessed(6); - assertThat(typeSyncStatistics.getReportMessage()) - .isEqualTo("Summary: 6 types were processed in total " + assertThat(typeSyncStatistics.getReportMessage()) + .isEqualTo( + "Summary: 6 types were processed in total " + "(1 created, 3 updated and 2 failed to sync)."); - } + } } diff --git a/src/test/java/com/commercetools/sync/types/utils/BuildFieldDefinitionUpdateActionsTest.java b/src/test/java/com/commercetools/sync/types/utils/BuildFieldDefinitionUpdateActionsTest.java index adf6aecb98..ddec093b4d 100644 --- a/src/test/java/com/commercetools/sync/types/utils/BuildFieldDefinitionUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/types/utils/BuildFieldDefinitionUpdateActionsTest.java @@ -1,5 +1,17 @@ package com.commercetools.sync.types.utils; +import static com.commercetools.sync.types.utils.FieldDefinitionFixtures.*; +import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildFieldDefinitionsUpdateActions; +import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.commercetools.sync.commons.exceptions.BuildUpdateActionException; import com.commercetools.sync.commons.exceptions.DuplicateNameException; import com.commercetools.sync.types.TypeSyncOptions; @@ -27,707 +39,624 @@ import io.sphere.sdk.types.commands.updateactions.ChangeFieldDefinitionOrder; import io.sphere.sdk.types.commands.updateactions.ChangeLocalizedEnumValueOrder; import io.sphere.sdk.types.commands.updateactions.RemoveFieldDefinition; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; - -import static com.commercetools.sync.types.utils.FieldDefinitionFixtures.*; -import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildFieldDefinitionsUpdateActions; -import static io.sphere.sdk.json.SphereJsonUtils.readObjectFromResource; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; class BuildFieldDefinitionUpdateActionsTest { - private static final String TYPE_KEY = "key"; - private static final LocalizedString TYPE_NAME = ofEnglish("name"); - private static final LocalizedString TYPE_DESCRIPTION = ofEnglish("description"); - - private static final TypeDraft TYPE_DRAFT = TypeDraftBuilder.of( - TYPE_KEY, - TYPE_NAME, - ResourceTypeIdsSetBuilder.of().addCategories().build()) - .description(TYPE_DESCRIPTION) - .build(); + private static final String TYPE_KEY = "key"; + private static final LocalizedString TYPE_NAME = ofEnglish("name"); + private static final LocalizedString TYPE_DESCRIPTION = ofEnglish("description"); - private static final TypeSyncOptions SYNC_OPTIONS = TypeSyncOptionsBuilder - .of(mock(SphereClient.class)) - .build(); + private static final TypeDraft TYPE_DRAFT = + TypeDraftBuilder.of( + TYPE_KEY, TYPE_NAME, ResourceTypeIdsSetBuilder.of().addCategories().build()) + .description(TYPE_DESCRIPTION) + .build(); - @Test - void buildFieldDefinitionsUpdateActions_WithNullNewFieldDefAndExistingFieldDefs_ShouldBuild3RemoveActions() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + private static final TypeSyncOptions SYNC_OPTIONS = + TypeSyncOptionsBuilder.of(mock(SphereClient.class)).build(); - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - TYPE_DRAFT, - SYNC_OPTIONS - ); + @Test + void + buildFieldDefinitionsUpdateActions_WithNullNewFieldDefAndExistingFieldDefs_ShouldBuild3RemoveActions() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - assertThat(updateActions).containsExactly( + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, TYPE_DRAFT, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveFieldDefinition.of(FIELD_A), RemoveFieldDefinition.of(FIELD_B), - RemoveFieldDefinition.of(FIELD_C) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithNullNewFieldDefsAndNoOldFieldDefs_ShouldNotBuildActions() { - final Type oldType = mock(Type.class); - when(oldType.getFieldDefinitions()).thenReturn(emptyList()); - when(oldType.getKey()).thenReturn("type_key_1"); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - TYPE_DRAFT, - SYNC_OPTIONS - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithNewFieldDefsAndNoOldFieldDefinitions_ShouldBuild3AddActions() { - final Type oldType = mock(Type.class); - when(oldType.getFieldDefinitions()).thenReturn(emptyList()); - when(oldType.getKey()).thenReturn("type_key_1"); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_ABC, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( + RemoveFieldDefinition.of(FIELD_C)); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithNullNewFieldDefsAndNoOldFieldDefs_ShouldNotBuildActions() { + final Type oldType = mock(Type.class); + when(oldType.getFieldDefinitions()).thenReturn(emptyList()); + when(oldType.getKey()).thenReturn("type_key_1"); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, TYPE_DRAFT, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithNewFieldDefsAndNoOldFieldDefinitions_ShouldBuild3AddActions() { + final Type oldType = mock(Type.class); + when(oldType.getFieldDefinitions()).thenReturn(emptyList()); + when(oldType.getKey()).thenReturn("type_key_1"); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_ABC, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( AddFieldDefinition.of(FIELD_DEFINITION_A), AddFieldDefinition.of(FIELD_DEFINITION_B), - AddFieldDefinition.of(FIELD_DEFINITION_C) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithIdenticalFieldDefinitions_ShouldNotBuildUpdateActions() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_ABC, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithDuplicateFieldDefNames_ShouldNotBuildActionsAndTriggerErrorCb() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_ABB, - TypeDraft.class - ); - - final List errorMessages = new ArrayList<>(); - final List exceptions = new ArrayList<>(); - final TypeSyncOptions syncOptions = TypeSyncOptionsBuilder.of(mock(SphereClient.class)) - .errorCallback((exception, oldResource, newResource, updateActions) -> { - errorMessages.add(exception.getMessage()); - exceptions.add(exception.getCause()); - }) + AddFieldDefinition.of(FIELD_DEFINITION_C)); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithIdenticalFieldDefinitions_ShouldNotBuildUpdateActions() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_ABC, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithDuplicateFieldDefNames_ShouldNotBuildActionsAndTriggerErrorCb() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_ABB, TypeDraft.class); + + final List errorMessages = new ArrayList<>(); + final List exceptions = new ArrayList<>(); + final TypeSyncOptions syncOptions = + TypeSyncOptionsBuilder.of(mock(SphereClient.class)) + .errorCallback( + (exception, oldResource, newResource, updateActions) -> { + errorMessages.add(exception.getMessage()); + exceptions.add(exception.getCause()); + }) .build(); - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - syncOptions - ); - - assertThat(updateActions).isEmpty(); - assertThat(errorMessages).hasSize(1); - assertThat(errorMessages.get(0)).matches("Failed to build update actions for the field definitions of the " - + "type with the key 'key'. Reason: .*DuplicateNameException: Field definitions " - + "have duplicated names. Duplicated field definition name: 'b'. Field definitions names are " - + "expected to be unique inside their type."); - assertThat(exceptions).hasSize(1); - assertThat(exceptions.get(0)).isExactlyInstanceOf(BuildUpdateActionException.class); - assertThat(exceptions.get(0).getMessage()).contains("Field definitions have duplicated names. " + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, syncOptions); + + assertThat(updateActions).isEmpty(); + assertThat(errorMessages).hasSize(1); + assertThat(errorMessages.get(0)) + .matches( + "Failed to build update actions for the field definitions of the " + + "type with the key 'key'. Reason: .*DuplicateNameException: Field definitions " + + "have duplicated names. Duplicated field definition name: 'b'. Field definitions names are " + + "expected to be unique inside their type."); + assertThat(exceptions).hasSize(1); + assertThat(exceptions.get(0)).isExactlyInstanceOf(BuildUpdateActionException.class); + assertThat(exceptions.get(0).getMessage()) + .contains( + "Field definitions have duplicated names. " + "Duplicated field definition name: 'b'. Field definitions names are expected to be unique " + "inside their type."); - assertThat(exceptions.get(0).getCause()).isExactlyInstanceOf(DuplicateNameException.class); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithOneMissingFieldDefinition_ShouldBuildRemoveFieldDefAction() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_AB, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly(RemoveFieldDefinition.of("c")); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithOneExtraFieldDef_ShouldBuildAddFieldDefinitionAction() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_ABCD, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( - AddFieldDefinition.of(FIELD_DEFINITION_D) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithOneFieldDefSwitch_ShouldBuildRemoveAndAddFieldDefActions() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_ABD, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( - RemoveFieldDefinition.of(FIELD_C), - AddFieldDefinition.of(FIELD_DEFINITION_D) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithDifferent_ShouldBuildChangeFieldDefinitionOrderAction() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_CAB, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - - assertThat(updateActions).containsExactly( - ChangeFieldDefinitionOrder - .of(asList( - FIELD_DEFINITION_C.getName(), - FIELD_DEFINITION_A.getName(), - FIELD_DEFINITION_B.getName() - )) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithRemovedAndDiffOrder_ShouldBuildChangeOrderAndRemoveActions() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_CB, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( + assertThat(exceptions.get(0).getCause()).isExactlyInstanceOf(DuplicateNameException.class); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithOneMissingFieldDefinition_ShouldBuildRemoveFieldDefAction() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_AB, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions).containsExactly(RemoveFieldDefinition.of("c")); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithOneExtraFieldDef_ShouldBuildAddFieldDefinitionAction() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_ABCD, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions).containsExactly(AddFieldDefinition.of(FIELD_DEFINITION_D)); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithOneFieldDefSwitch_ShouldBuildRemoveAndAddFieldDefActions() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_ABD, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + RemoveFieldDefinition.of(FIELD_C), AddFieldDefinition.of(FIELD_DEFINITION_D)); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithDifferent_ShouldBuildChangeFieldDefinitionOrderAction() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_CAB, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + ChangeFieldDefinitionOrder.of( + asList( + FIELD_DEFINITION_C.getName(), + FIELD_DEFINITION_A.getName(), + FIELD_DEFINITION_B.getName()))); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithRemovedAndDiffOrder_ShouldBuildChangeOrderAndRemoveActions() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_CB, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveFieldDefinition.of(FIELD_A), - ChangeFieldDefinitionOrder - .of(asList( - FIELD_DEFINITION_C.getName(), - FIELD_DEFINITION_B.getName() - ) - ) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_ACBD, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( + ChangeFieldDefinitionOrder.of( + asList(FIELD_DEFINITION_C.getName(), FIELD_DEFINITION_B.getName()))); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_ACBD, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( AddFieldDefinition.of(FIELD_DEFINITION_D), - ChangeFieldDefinitionOrder - .of(asList( - FIELD_DEFINITION_A.getName(), - FIELD_DEFINITION_C.getName(), - FIELD_DEFINITION_B.getName(), - FIELD_DEFINITION_D.getName() - ) - ) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithAddedFieldDefInBetween_ShouldBuildChangeOrderAndAddActions() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_ADBC, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( + ChangeFieldDefinitionOrder.of( + asList( + FIELD_DEFINITION_A.getName(), + FIELD_DEFINITION_C.getName(), + FIELD_DEFINITION_B.getName(), + FIELD_DEFINITION_D.getName()))); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithAddedFieldDefInBetween_ShouldBuildChangeOrderAndAddActions() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_ADBC, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( AddFieldDefinition.of(FIELD_DEFINITION_D), - ChangeFieldDefinitionOrder - .of(asList( - FIELD_DEFINITION_A.getName(), - FIELD_DEFINITION_D.getName(), - FIELD_DEFINITION_B.getName(), - FIELD_DEFINITION_C.getName() - ) - ) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithMixedFields_ShouldBuildAllThreeMoveFieldDefActions() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_CBD, - TypeDraft.class - ); - - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - assertThat(updateActions).containsExactly( + ChangeFieldDefinitionOrder.of( + asList( + FIELD_DEFINITION_A.getName(), + FIELD_DEFINITION_D.getName(), + FIELD_DEFINITION_B.getName(), + FIELD_DEFINITION_C.getName()))); + } + + @Test + void buildFieldDefinitionsUpdateActions_WithMixedFields_ShouldBuildAllThreeMoveFieldDefActions() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = readObjectFromResource(TYPE_WITH_FIELDS_CBD, TypeDraft.class); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + assertThat(updateActions) + .containsExactly( RemoveFieldDefinition.of(FIELD_A), AddFieldDefinition.of(FIELD_DEFINITION_D), - ChangeFieldDefinitionOrder - .of(asList( - FIELD_DEFINITION_C.getName(), - FIELD_DEFINITION_B.getName(), - FIELD_DEFINITION_D.getName() - ) - ) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithDifferentType_ShouldRemoveOldFieldDefAndAddNewFieldDef() { - final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); - - final TypeDraft newTypeDraft = readObjectFromResource( - TYPE_WITH_FIELDS_ABC_WITH_DIFFERENT_TYPE, - TypeDraft.class - ); - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - newTypeDraft, - SYNC_OPTIONS - ); - - assertThat(updateActions).containsExactly( + ChangeFieldDefinitionOrder.of( + asList( + FIELD_DEFINITION_C.getName(), + FIELD_DEFINITION_B.getName(), + FIELD_DEFINITION_D.getName()))); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithDifferentType_ShouldRemoveOldFieldDefAndAddNewFieldDef() { + final Type oldType = readObjectFromResource(TYPE_WITH_FIELDS_ABC, Type.class); + + final TypeDraft newTypeDraft = + readObjectFromResource(TYPE_WITH_FIELDS_ABC_WITH_DIFFERENT_TYPE, TypeDraft.class); + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, newTypeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( RemoveFieldDefinition.of(FIELD_A), - AddFieldDefinition.of(FIELD_DEFINITION_A_LOCALIZED_TYPE) - ); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithNullNewFieldDef_ShouldSkipNullFieldDefs() { - // preparation - final Type oldType = mock(Type.class); - final FieldDefinition oldFieldDefinition = FieldDefinition.of( + AddFieldDefinition.of(FIELD_DEFINITION_A_LOCALIZED_TYPE)); + } + + @Test + void buildFieldDefinitionsUpdateActions_WithNullNewFieldDef_ShouldSkipNullFieldDefs() { + // preparation + final Type oldType = mock(Type.class); + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( LocalizedEnumFieldType.of(emptyList()), "field_1", ofEnglish("label1"), false, TextInputHint.SINGLE_LINE); + when(oldType.getFieldDefinitions()).thenReturn(singletonList(oldFieldDefinition)); - when(oldType.getFieldDefinitions()).thenReturn(singletonList(oldFieldDefinition)); - - final FieldDefinition newFieldDefinition = FieldDefinition.of( + final FieldDefinition newFieldDefinition = + FieldDefinition.of( LocalizedEnumFieldType.of(emptyList()), "field_1", ofEnglish("label2"), false, TextInputHint.SINGLE_LINE); - final TypeDraft typeDraft = TypeDraftBuilder - .of("key", ofEnglish("label"), emptySet()) + final TypeDraft typeDraft = + TypeDraftBuilder.of("key", ofEnglish("label"), emptySet()) .fieldDefinitions(asList(null, newFieldDefinition)) .build(); - // test - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - typeDraft, - SYNC_OPTIONS - ); - - // assertion - assertThat(updateActions).containsExactly( - ChangeFieldDefinitionLabel.of(newFieldDefinition.getName(), newFieldDefinition.getLabel())); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithDefWithNullName_ShouldBuildChangeFieldDefOrderAction() { - // preparation - final Type oldType = mock(Type.class); - final FieldDefinition oldFieldDefinition = FieldDefinition.of( + // test + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, typeDraft, SYNC_OPTIONS); + + // assertion + assertThat(updateActions) + .containsExactly( + ChangeFieldDefinitionLabel.of( + newFieldDefinition.getName(), newFieldDefinition.getLabel())); + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithDefWithNullName_ShouldBuildChangeFieldDefOrderAction() { + // preparation + final Type oldType = mock(Type.class); + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( LocalizedEnumFieldType.of(emptyList()), "field_1", ofEnglish("label1"), false, TextInputHint.SINGLE_LINE); + when(oldType.getFieldDefinitions()).thenReturn(singletonList(oldFieldDefinition)); - when(oldType.getFieldDefinitions()).thenReturn(singletonList(oldFieldDefinition)); - - final FieldDefinition newFieldDefinition = FieldDefinition.of( + final FieldDefinition newFieldDefinition = + FieldDefinition.of( LocalizedEnumFieldType.of(emptyList()), null, ofEnglish("label2"), false, TextInputHint.SINGLE_LINE); - final TypeDraft typeDraft = TypeDraftBuilder - .of("key", ofEnglish("label"), emptySet()) + final TypeDraft typeDraft = + TypeDraftBuilder.of("key", ofEnglish("label"), emptySet()) .fieldDefinitions(asList(null, newFieldDefinition)) .build(); - // test - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - typeDraft, - SYNC_OPTIONS - ); + // test + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, typeDraft, SYNC_OPTIONS); - // assertion - assertThat(updateActions).containsExactly( + // assertion + assertThat(updateActions) + .containsExactly( RemoveFieldDefinition.of(oldFieldDefinition.getName()), AddFieldDefinition.of(newFieldDefinition)); - } - - @Test - void buildFieldDefinitionsUpdateActions_WithDefWithNullType_ShouldBuildChangeFieldDefOrderAction() { - // preparation - final Type oldType = mock(Type.class); - final FieldDefinition oldFieldDefinition = FieldDefinition.of( + } + + @Test + void + buildFieldDefinitionsUpdateActions_WithDefWithNullType_ShouldBuildChangeFieldDefOrderAction() { + // preparation + final Type oldType = mock(Type.class); + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( LocalizedEnumFieldType.of(emptyList()), "field_1", ofEnglish("label1"), false, TextInputHint.SINGLE_LINE); + when(oldType.getFieldDefinitions()).thenReturn(singletonList(oldFieldDefinition)); - when(oldType.getFieldDefinitions()).thenReturn(singletonList(oldFieldDefinition)); + final FieldDefinition newFieldDefinition = + FieldDefinition.of(null, "field_1", ofEnglish("label2"), false, TextInputHint.SINGLE_LINE); - final FieldDefinition newFieldDefinition = FieldDefinition.of( - null, - "field_1", - ofEnglish("label2"), - false, - TextInputHint.SINGLE_LINE); - - final TypeDraft typeDraft = TypeDraftBuilder - .of("key", ofEnglish("label"), emptySet()) + final TypeDraft typeDraft = + TypeDraftBuilder.of("key", ofEnglish("label"), emptySet()) .fieldDefinitions(asList(null, newFieldDefinition)) .build(); - // test - final List> updateActions = buildFieldDefinitionsUpdateActions( - oldType, - typeDraft, - SYNC_OPTIONS - ); + // test + final List> updateActions = + buildFieldDefinitionsUpdateActions(oldType, typeDraft, SYNC_OPTIONS); - // assertion - assertThat(updateActions).isEmpty(); - } + // assertion + assertThat(updateActions).isEmpty(); + } - @Test - void buildFieldsUpdateActions_WithSetOfText_ShouldBuildActions() { - final FieldDefinition newDefinition = FieldDefinition - .of(SetFieldType.of(StringFieldType.of()), "a", ofEnglish("new_label"), true); + @Test + void buildFieldsUpdateActions_WithSetOfText_ShouldBuildActions() { + final FieldDefinition newDefinition = + FieldDefinition.of( + SetFieldType.of(StringFieldType.of()), "a", ofEnglish("new_label"), true); - final TypeDraft typeDraft = TypeDraftBuilder - .of("foo", ofEnglish("name"), emptySet()) + final TypeDraft typeDraft = + TypeDraftBuilder.of("foo", ofEnglish("name"), emptySet()) .fieldDefinitions(singletonList(newDefinition)) .build(); - final FieldDefinition oldDefinition = FieldDefinition - .of(SetFieldType.of(StringFieldType.of()), "a", ofEnglish("old_label"), true); + final FieldDefinition oldDefinition = + FieldDefinition.of( + SetFieldType.of(StringFieldType.of()), "a", ofEnglish("old_label"), true); - final Type type = mock(Type.class); - when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); + final Type type = mock(Type.class); + when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); - final List> updateActions = buildFieldDefinitionsUpdateActions(type, - typeDraft, SYNC_OPTIONS); + final List> updateActions = + buildFieldDefinitionsUpdateActions(type, typeDraft, SYNC_OPTIONS); - assertThat(updateActions) - .containsExactly(ChangeFieldDefinitionLabel.of(newDefinition.getName(), newDefinition.getLabel())); - } + assertThat(updateActions) + .containsExactly( + ChangeFieldDefinitionLabel.of(newDefinition.getName(), newDefinition.getLabel())); + } - @Test - void buildFieldsUpdateActions_WithSetOfSetOfText_ShouldBuildActions() { + @Test + void buildFieldsUpdateActions_WithSetOfSetOfText_ShouldBuildActions() { - final FieldDefinition newDefinition = FieldDefinition - .of(SetFieldType.of(SetFieldType.of(StringFieldType.of())), "a", ofEnglish("new_label"), true); + final FieldDefinition newDefinition = + FieldDefinition.of( + SetFieldType.of(SetFieldType.of(StringFieldType.of())), + "a", + ofEnglish("new_label"), + true); - final TypeDraft typeDraft = TypeDraftBuilder - .of("foo", ofEnglish("name"), emptySet()) + final TypeDraft typeDraft = + TypeDraftBuilder.of("foo", ofEnglish("name"), emptySet()) .fieldDefinitions(singletonList(newDefinition)) .build(); - final FieldDefinition oldDefinition = FieldDefinition - .of(SetFieldType.of(SetFieldType.of(StringFieldType.of())), "a", ofEnglish("old_label"), true); - - final Type type = mock(Type.class); - when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); - - final List> updateActions = buildFieldDefinitionsUpdateActions(type, - typeDraft, SYNC_OPTIONS); - - assertThat(updateActions) - .containsExactly(ChangeFieldDefinitionLabel.of(newDefinition.getName(), newDefinition.getLabel())); - - } - - @Test - void buildFieldsUpdateActions_WithSetOfEnumsChanges_ShouldBuildCorrectActions() { - // preparation - final FieldDefinition newDefinition = FieldDefinition - .of(SetFieldType.of( + final FieldDefinition oldDefinition = + FieldDefinition.of( + SetFieldType.of(SetFieldType.of(StringFieldType.of())), + "a", + ofEnglish("old_label"), + true); + + final Type type = mock(Type.class); + when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); + + final List> updateActions = + buildFieldDefinitionsUpdateActions(type, typeDraft, SYNC_OPTIONS); + + assertThat(updateActions) + .containsExactly( + ChangeFieldDefinitionLabel.of(newDefinition.getName(), newDefinition.getLabel())); + } + + @Test + void buildFieldsUpdateActions_WithSetOfEnumsChanges_ShouldBuildCorrectActions() { + // preparation + final FieldDefinition newDefinition = + FieldDefinition.of( + SetFieldType.of( EnumFieldType.of( asList( - EnumValue.of("a", "a"), - EnumValue.of("b", "b"), - EnumValue.of("c", "c") - ) - )), "a", ofEnglish("new_label"), true); - - final TypeDraft typeDraft = TypeDraftBuilder - .of("foo", ofEnglish("name"), emptySet()) + EnumValue.of("a", "a"), EnumValue.of("b", "b"), EnumValue.of("c", "c")))), + "a", + ofEnglish("new_label"), + true); + + final TypeDraft typeDraft = + TypeDraftBuilder.of("foo", ofEnglish("name"), emptySet()) .fieldDefinitions(singletonList(newDefinition)) .build(); - final FieldDefinition oldDefinition = FieldDefinition - .of(SetFieldType.of( - EnumFieldType.of(asList( - EnumValue.of("b", "b"), - EnumValue.of("a", "a") - ))), "a", ofEnglish("new_label"), true); + final FieldDefinition oldDefinition = + FieldDefinition.of( + SetFieldType.of( + EnumFieldType.of(asList(EnumValue.of("b", "b"), EnumValue.of("a", "a")))), + "a", + ofEnglish("new_label"), + true); - final Type type = mock(Type.class); - when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); + final Type type = mock(Type.class); + when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); - // test - final List> updateActions = buildFieldDefinitionsUpdateActions(type, - typeDraft, SYNC_OPTIONS); + // test + final List> updateActions = + buildFieldDefinitionsUpdateActions(type, typeDraft, SYNC_OPTIONS); - // assertion - assertThat(updateActions).containsExactly( + // assertion + assertThat(updateActions) + .containsExactly( AddEnumValue.of("a", EnumValue.of("c", "c")), - ChangeEnumValueOrder.of("a", asList("a", "b", "c")) - ); - - } - - @Test - void buildFieldsUpdateActions_WithSetOfIdenticalEnums_ShouldNotBuildActions() { - // preparation - final FieldDefinition newDefinition = FieldDefinition - .of(SetFieldType.of(EnumFieldType.of(emptyList())), "a", ofEnglish("new_label"), true); - - final TypeDraft typeDraft = TypeDraftBuilder - .of("foo", ofEnglish("name"), emptySet()) + ChangeEnumValueOrder.of("a", asList("a", "b", "c"))); + } + + @Test + void buildFieldsUpdateActions_WithSetOfIdenticalEnums_ShouldNotBuildActions() { + // preparation + final FieldDefinition newDefinition = + FieldDefinition.of( + SetFieldType.of(EnumFieldType.of(emptyList())), "a", ofEnglish("new_label"), true); + + final TypeDraft typeDraft = + TypeDraftBuilder.of("foo", ofEnglish("name"), emptySet()) .fieldDefinitions(singletonList(newDefinition)) .build(); - final FieldDefinition oldDefinition = FieldDefinition - .of(SetFieldType.of( - EnumFieldType.of(emptyList())), "a", ofEnglish("new_label"), true); + final FieldDefinition oldDefinition = + FieldDefinition.of( + SetFieldType.of(EnumFieldType.of(emptyList())), "a", ofEnglish("new_label"), true); - final Type type = mock(Type.class); - when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); + final Type type = mock(Type.class); + when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); - // test - final List> updateActions = buildFieldDefinitionsUpdateActions(type, - typeDraft, SYNC_OPTIONS); + // test + final List> updateActions = + buildFieldDefinitionsUpdateActions(type, typeDraft, SYNC_OPTIONS); - // assertion - assertThat(updateActions).isEmpty(); - } + // assertion + assertThat(updateActions).isEmpty(); + } - @Test - void buildFieldsUpdateActions_WithSetOfLEnumsChanges_ShouldBuildCorrectActions() { - // preparation - final SetFieldType newSetFieldType = SetFieldType.of( + @Test + void buildFieldsUpdateActions_WithSetOfLEnumsChanges_ShouldBuildCorrectActions() { + // preparation + final SetFieldType newSetFieldType = + SetFieldType.of( LocalizedEnumFieldType.of( asList( LocalizedEnumValue.of("a", ofEnglish("a")), LocalizedEnumValue.of("b", ofEnglish("b")), - LocalizedEnumValue.of("c", ofEnglish("c")) - ) - ) - ); + LocalizedEnumValue.of("c", ofEnglish("c"))))); - final FieldDefinition newDefinition = FieldDefinition - .of(newSetFieldType, "a", ofEnglish("new_label"), true); + final FieldDefinition newDefinition = + FieldDefinition.of(newSetFieldType, "a", ofEnglish("new_label"), true); - final TypeDraft typeDraft = TypeDraftBuilder - .of("foo", ofEnglish("name"), emptySet()) + final TypeDraft typeDraft = + TypeDraftBuilder.of("foo", ofEnglish("name"), emptySet()) .fieldDefinitions(singletonList(newDefinition)) .build(); - final SetFieldType oldSetFieldType = SetFieldType.of( + final SetFieldType oldSetFieldType = + SetFieldType.of( LocalizedEnumFieldType.of( asList( LocalizedEnumValue.of("b", ofEnglish("b")), - LocalizedEnumValue.of("a", ofEnglish("a")) - ))); - final FieldDefinition oldDefinition = FieldDefinition - .of(oldSetFieldType, "a", ofEnglish("new_label"), true); - - final Type type = mock(Type.class); - when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = buildFieldDefinitionsUpdateActions(type, - typeDraft, SYNC_OPTIONS); - - // assertion - assertThat(updateActions) - .containsExactly( - AddLocalizedEnumValue.of("a", LocalizedEnumValue.of("c", ofEnglish("c"))), - ChangeLocalizedEnumValueOrder.of("a", asList("a", "b", "c"))); - } - - @Test - void buildFieldsUpdateActions_WithSetOfIdenticalLEnums_ShouldNotBuildActions() { - // preparation - final SetFieldType newSetFieldType = SetFieldType.of(SetFieldType.of(LocalizedEnumFieldType.of(emptyList()))); - - final FieldDefinition newDefinition = FieldDefinition - .of(newSetFieldType, "a", ofEnglish("new_label"), true); - - final TypeDraft typeDraft = TypeDraftBuilder - .of("foo", ofEnglish("name"), emptySet()) + LocalizedEnumValue.of("a", ofEnglish("a"))))); + final FieldDefinition oldDefinition = + FieldDefinition.of(oldSetFieldType, "a", ofEnglish("new_label"), true); + + final Type type = mock(Type.class); + when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildFieldDefinitionsUpdateActions(type, typeDraft, SYNC_OPTIONS); + + // assertion + assertThat(updateActions) + .containsExactly( + AddLocalizedEnumValue.of("a", LocalizedEnumValue.of("c", ofEnglish("c"))), + ChangeLocalizedEnumValueOrder.of("a", asList("a", "b", "c"))); + } + + @Test + void buildFieldsUpdateActions_WithSetOfIdenticalLEnums_ShouldNotBuildActions() { + // preparation + final SetFieldType newSetFieldType = + SetFieldType.of(SetFieldType.of(LocalizedEnumFieldType.of(emptyList()))); + + final FieldDefinition newDefinition = + FieldDefinition.of(newSetFieldType, "a", ofEnglish("new_label"), true); + + final TypeDraft typeDraft = + TypeDraftBuilder.of("foo", ofEnglish("name"), emptySet()) .fieldDefinitions(singletonList(newDefinition)) .build(); - final SetFieldType oldSetFieldType = SetFieldType.of(SetFieldType.of(LocalizedEnumFieldType.of(emptyList()))); - final FieldDefinition oldDefinition = FieldDefinition - .of(oldSetFieldType, "a", ofEnglish("new_label"), true); + final SetFieldType oldSetFieldType = + SetFieldType.of(SetFieldType.of(LocalizedEnumFieldType.of(emptyList()))); + final FieldDefinition oldDefinition = + FieldDefinition.of(oldSetFieldType, "a", ofEnglish("new_label"), true); - final Type type = mock(Type.class); - when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); + final Type type = mock(Type.class); + when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); - // test - final List> updateActions = buildFieldDefinitionsUpdateActions(type, - typeDraft, SYNC_OPTIONS); + // test + final List> updateActions = + buildFieldDefinitionsUpdateActions(type, typeDraft, SYNC_OPTIONS); - // assertion - assertThat(updateActions).isEmpty(); - } + // assertion + assertThat(updateActions).isEmpty(); + } - @Test - void buildFieldsUpdateActions_WithSetOfLEnumsChangesAndDefinitionLabelChange_ShouldBuildCorrectActions() { - // preparation - final SetFieldType newSetFieldType = SetFieldType.of( + @Test + void + buildFieldsUpdateActions_WithSetOfLEnumsChangesAndDefinitionLabelChange_ShouldBuildCorrectActions() { + // preparation + final SetFieldType newSetFieldType = + SetFieldType.of( LocalizedEnumFieldType.of( asList( LocalizedEnumValue.of("a", ofEnglish("a")), LocalizedEnumValue.of("b", ofEnglish("newB")), - LocalizedEnumValue.of("c", ofEnglish("c")) - ))); + LocalizedEnumValue.of("c", ofEnglish("c"))))); - final FieldDefinition newDefinition = FieldDefinition - .of(newSetFieldType, "a", ofEnglish("new_label"), true); + final FieldDefinition newDefinition = + FieldDefinition.of(newSetFieldType, "a", ofEnglish("new_label"), true); - final TypeDraft typeDraft = TypeDraftBuilder - .of("foo", ofEnglish("name"), emptySet()) + final TypeDraft typeDraft = + TypeDraftBuilder.of("foo", ofEnglish("name"), emptySet()) .fieldDefinitions(singletonList(newDefinition)) .build(); - final SetFieldType oldSetFieldType = SetFieldType.of( + final SetFieldType oldSetFieldType = + SetFieldType.of( LocalizedEnumFieldType.of( asList( LocalizedEnumValue.of("b", ofEnglish("b")), - LocalizedEnumValue.of("a", ofEnglish("a")) - ) - )); - final FieldDefinition oldDefinition = FieldDefinition - .of(oldSetFieldType, "a", ofEnglish("old_label"), true); - - final Type type = mock(Type.class); - when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); - - // test - final List> updateActions = buildFieldDefinitionsUpdateActions(type, - typeDraft, SYNC_OPTIONS); - - // assertion - assertThat(updateActions) - .containsExactlyInAnyOrder( - AddLocalizedEnumValue.of("a", LocalizedEnumValue.of("c", ofEnglish("c"))), - ChangeLocalizedEnumValueOrder.of("a", asList("a", "b", "c")), - ChangeFieldDefinitionLabel.of("a", ofEnglish("new_label")) - ); - } + LocalizedEnumValue.of("a", ofEnglish("a"))))); + final FieldDefinition oldDefinition = + FieldDefinition.of(oldSetFieldType, "a", ofEnglish("old_label"), true); + + final Type type = mock(Type.class); + when(type.getFieldDefinitions()).thenReturn(singletonList(oldDefinition)); + + // test + final List> updateActions = + buildFieldDefinitionsUpdateActions(type, typeDraft, SYNC_OPTIONS); + + // assertion + assertThat(updateActions) + .containsExactlyInAnyOrder( + AddLocalizedEnumValue.of("a", LocalizedEnumValue.of("c", ofEnglish("c"))), + ChangeLocalizedEnumValueOrder.of("a", asList("a", "b", "c")), + ChangeFieldDefinitionLabel.of("a", ofEnglish("new_label"))); + } } diff --git a/src/test/java/com/commercetools/sync/types/utils/BuildLocalizedEnumUpdateActionsTest.java b/src/test/java/com/commercetools/sync/types/utils/BuildLocalizedEnumUpdateActionsTest.java index 918ef0665b..66e581bcd1 100644 --- a/src/test/java/com/commercetools/sync/types/utils/BuildLocalizedEnumUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/types/utils/BuildLocalizedEnumUpdateActionsTest.java @@ -1,14 +1,5 @@ package com.commercetools.sync.types.utils; -import com.commercetools.sync.commons.exceptions.DuplicateKeyException; -import io.sphere.sdk.commands.UpdateAction; -import io.sphere.sdk.types.Type; -import io.sphere.sdk.types.commands.updateactions.AddLocalizedEnumValue; -import io.sphere.sdk.types.commands.updateactions.ChangeLocalizedEnumValueOrder; -import org.junit.jupiter.api.Test; - -import java.util.List; - import static com.commercetools.sync.commons.utils.LocalizedEnumValueFixtures.*; import static com.commercetools.sync.types.utils.LocalizedEnumValueUpdateActionUtils.buildLocalizedEnumValuesUpdateActions; import static java.util.Arrays.asList; @@ -16,167 +7,149 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -class BuildLocalizedEnumUpdateActionsTest { - private static final String FIELD_NAME_1 = "field1"; +import com.commercetools.sync.commons.exceptions.DuplicateKeyException; +import io.sphere.sdk.commands.UpdateAction; +import io.sphere.sdk.types.Type; +import io.sphere.sdk.types.commands.updateactions.AddLocalizedEnumValue; +import io.sphere.sdk.types.commands.updateactions.ChangeLocalizedEnumValueOrder; +import java.util.List; +import org.junit.jupiter.api.Test; +class BuildLocalizedEnumUpdateActionsTest { + private static final String FIELD_NAME_1 = "field1"; - @Test - void buildLocalizedEnumUpdateActions_WithEmptyPlainEnumValuesAndNoOldEnumValues_ShouldNotBuildActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - emptyList(), - emptyList() - ); + @Test + void + buildLocalizedEnumUpdateActions_WithEmptyPlainEnumValuesAndNoOldEnumValues_ShouldNotBuildActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, emptyList(), emptyList()); - assertThat(updateActions).isEmpty(); - } + assertThat(updateActions).isEmpty(); + } - @Test - void buildLocalizedEnumUpdateActions_WithNewPlainEnumValuesAndNoOldPlainEnumValues_ShouldBuild3AddActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - emptyList(), - ENUM_VALUES_ABC - ); + @Test + void + buildLocalizedEnumUpdateActions_WithNewPlainEnumValuesAndNoOldPlainEnumValues_ShouldBuild3AddActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, emptyList(), ENUM_VALUES_ABC); - assertThat(updateActions).containsExactly( + assertThat(updateActions) + .containsExactly( AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_A), AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_B), - AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_C) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithIdenticalPlainEnum_ShouldNotBuildUpdateActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ABC - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildLocalizedEnumUpdateActions_WithOnePlainEnumValue_ShouldBuildAddEnumValueAction() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ABCD - ); - - assertThat(updateActions).containsExactly( - AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithOneEnumValueSwitch_ShouldBuildAddEnumValueActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ABD - ); - - // remove enum value actions not exists for type resources - assertThat(updateActions).containsExactly( - AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithDifferent_ShouldBuildChangeEnumValueOrderAction() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_CAB - ); - - assertThat(updateActions).containsExactly( - ChangeLocalizedEnumValueOrder.of(FIELD_NAME_1, - asList(ENUM_VALUE_C.getKey(), ENUM_VALUE_A.getKey(), ENUM_VALUE_B.getKey())) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_CB - ); - - // remove enum value actions not exists for type resources - assertThat(updateActions).containsExactly( - ChangeLocalizedEnumValueOrder.of(FIELD_NAME_1, asList(ENUM_VALUE_C.getKey(), ENUM_VALUE_B.getKey())) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ACBD - ); - - assertThat(updateActions).containsExactly( + AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_C)); + } + + @Test + void buildLocalizedEnumUpdateActions_WithIdenticalPlainEnum_ShouldNotBuildUpdateActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ABC); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildLocalizedEnumUpdateActions_WithOnePlainEnumValue_ShouldBuildAddEnumValueAction() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ABCD); + + assertThat(updateActions).containsExactly(AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D)); + } + + @Test + void buildLocalizedEnumUpdateActions_WithOneEnumValueSwitch_ShouldBuildAddEnumValueActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ABD); + + // remove enum value actions not exists for type resources + assertThat(updateActions).containsExactly(AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D)); + } + + @Test + void buildLocalizedEnumUpdateActions_WithDifferent_ShouldBuildChangeEnumValueOrderAction() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_CAB); + + assertThat(updateActions) + .containsExactly( + ChangeLocalizedEnumValueOrder.of( + FIELD_NAME_1, + asList(ENUM_VALUE_C.getKey(), ENUM_VALUE_A.getKey(), ENUM_VALUE_B.getKey()))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_CB); + + // remove enum value actions not exists for type resources + assertThat(updateActions) + .containsExactly( + ChangeLocalizedEnumValueOrder.of( + FIELD_NAME_1, asList(ENUM_VALUE_C.getKey(), ENUM_VALUE_B.getKey()))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ACBD); + + assertThat(updateActions) + .containsExactly( AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D), - ChangeLocalizedEnumValueOrder.of(FIELD_NAME_1, asList( - ENUM_VALUE_A.getKey(), - ENUM_VALUE_C.getKey(), - ENUM_VALUE_B.getKey(), - ENUM_VALUE_D.getKey() - )) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithAddedEnumValueInBetween_ShouldBuildChangeOrderAndAddActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ADBC - ); - - assertThat(updateActions).containsExactly( + ChangeLocalizedEnumValueOrder.of( + FIELD_NAME_1, + asList( + ENUM_VALUE_A.getKey(), + ENUM_VALUE_C.getKey(), + ENUM_VALUE_B.getKey(), + ENUM_VALUE_D.getKey()))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithAddedEnumValueInBetween_ShouldBuildChangeOrderAndAddActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ADBC); + + assertThat(updateActions) + .containsExactly( AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D), - ChangeLocalizedEnumValueOrder.of(FIELD_NAME_1, asList( - ENUM_VALUE_A.getKey(), - ENUM_VALUE_D.getKey(), - ENUM_VALUE_B.getKey(), - ENUM_VALUE_C.getKey() - )) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAddAndOrderEnumValueActions() { - final List> updateActions = buildLocalizedEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_CBD - ); - - assertThat(updateActions).containsExactly( + ChangeLocalizedEnumValueOrder.of( + FIELD_NAME_1, + asList( + ENUM_VALUE_A.getKey(), + ENUM_VALUE_D.getKey(), + ENUM_VALUE_B.getKey(), + ENUM_VALUE_C.getKey()))); + } + + @Test + void + buildLocalizedEnumUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAddAndOrderEnumValueActions() { + final List> updateActions = + buildLocalizedEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_CBD); + + assertThat(updateActions) + .containsExactly( AddLocalizedEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D), - ChangeLocalizedEnumValueOrder.of(FIELD_NAME_1, asList( - ENUM_VALUE_C.getKey(), - ENUM_VALUE_B.getKey(), - ENUM_VALUE_D.getKey() - )) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithDuplicateEnumValues_ShouldTriggerDuplicateKeyError() { - assertThatThrownBy(() -> buildLocalizedEnumValuesUpdateActions( - "field_definition_name", - ENUM_VALUES_ABC, - ENUM_VALUES_ABB - )).isInstanceOf(DuplicateKeyException.class) - .hasMessage("Enum Values have duplicated keys. Definition name: " - + "'field_definition_name', Duplicated enum value: 'b'. " - + "Enum Values are expected to be unique inside their definition."); - } + ChangeLocalizedEnumValueOrder.of( + FIELD_NAME_1, + asList(ENUM_VALUE_C.getKey(), ENUM_VALUE_B.getKey(), ENUM_VALUE_D.getKey()))); + } + + @Test + void buildLocalizedEnumUpdateActions_WithDuplicateEnumValues_ShouldTriggerDuplicateKeyError() { + assertThatThrownBy( + () -> + buildLocalizedEnumValuesUpdateActions( + "field_definition_name", ENUM_VALUES_ABC, ENUM_VALUES_ABB)) + .isInstanceOf(DuplicateKeyException.class) + .hasMessage( + "Enum Values have duplicated keys. Definition name: " + + "'field_definition_name', Duplicated enum value: 'b'. " + + "Enum Values are expected to be unique inside their definition."); + } } diff --git a/src/test/java/com/commercetools/sync/types/utils/BuildPlainEnumUpdateActionsTest.java b/src/test/java/com/commercetools/sync/types/utils/BuildPlainEnumUpdateActionsTest.java index c7f29a0968..2fd4df781e 100644 --- a/src/test/java/com/commercetools/sync/types/utils/BuildPlainEnumUpdateActionsTest.java +++ b/src/test/java/com/commercetools/sync/types/utils/BuildPlainEnumUpdateActionsTest.java @@ -1,187 +1,152 @@ package com.commercetools.sync.types.utils; +import static com.commercetools.sync.commons.utils.PlainEnumValueFixtures.*; +import static com.commercetools.sync.types.utils.PlainEnumValueUpdateActionUtils.buildEnumValuesUpdateActions; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.commercetools.sync.commons.exceptions.DuplicateKeyException; import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.types.Type; import io.sphere.sdk.types.commands.updateactions.AddEnumValue; import io.sphere.sdk.types.commands.updateactions.ChangeEnumValueOrder; +import java.util.List; import org.junit.jupiter.api.Test; -import java.util.List; +class BuildPlainEnumUpdateActionsTest { + private static final String FIELD_NAME_1 = "field1"; -import static com.commercetools.sync.commons.utils.PlainEnumValueFixtures.*; -import static com.commercetools.sync.types.utils.PlainEnumValueUpdateActionUtils.buildEnumValuesUpdateActions; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; + @Test + void + buildPlainEnumUpdateActions_WithEmptyPlainEnumValuesAndNoOldEnumValues_ShouldNotBuildActions() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, emptyList(), emptyList()); -class BuildPlainEnumUpdateActionsTest { - private static final String FIELD_NAME_1 = "field1"; - - @Test - void buildPlainEnumUpdateActions_WithEmptyPlainEnumValuesAndNoOldEnumValues_ShouldNotBuildActions() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - emptyList(), - emptyList() - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildPlainEnumUpdateActions_WithNewPlainEnumValuesAndNoOldPlainEnumValues_ShouldBuild3AddActions() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - emptyList(), - ENUM_VALUES_ABC - ); - - assertThat(updateActions).containsExactly( + assertThat(updateActions).isEmpty(); + } + + @Test + void + buildPlainEnumUpdateActions_WithNewPlainEnumValuesAndNoOldPlainEnumValues_ShouldBuild3AddActions() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, emptyList(), ENUM_VALUES_ABC); + + assertThat(updateActions) + .containsExactly( AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_A), AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_B), - AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_C) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithIdenticalPlainEnum_ShouldNotBuildUpdateActions() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ABC - ); - - assertThat(updateActions).isEmpty(); - } - - @Test - void buildPlainEnumUpdateActions_WithOnePlainEnumValue_ShouldBuildAddEnumValueAction() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ABCD - ); - - assertThat(updateActions).containsExactly( - AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithOneEnumValueSwitch_ShouldBuildAddEnumValueActions() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ABD - ); - - assertThat(updateActions).containsExactly( - AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithDifferentOrder_ShouldBuildChangeEnumValueOrderAction() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_CAB - ); - - assertThat(updateActions).containsExactly( - ChangeEnumValueOrder.of(FIELD_NAME_1, + AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_C)); + } + + @Test + void buildPlainEnumUpdateActions_WithIdenticalPlainEnum_ShouldNotBuildUpdateActions() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ABC); + + assertThat(updateActions).isEmpty(); + } + + @Test + void buildPlainEnumUpdateActions_WithOnePlainEnumValue_ShouldBuildAddEnumValueAction() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ABCD); + + assertThat(updateActions).containsExactly(AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D)); + } + + @Test + void buildPlainEnumUpdateActions_WithOneEnumValueSwitch_ShouldBuildAddEnumValueActions() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ABD); + + assertThat(updateActions).containsExactly(AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D)); + } + + @Test + void buildPlainEnumUpdateActions_WithDifferentOrder_ShouldBuildChangeEnumValueOrderAction() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_CAB); + + assertThat(updateActions) + .containsExactly( + ChangeEnumValueOrder.of( + FIELD_NAME_1, + asList(ENUM_VALUE_C.getKey(), ENUM_VALUE_A.getKey(), ENUM_VALUE_B.getKey()))); + } + + @Test + void buildPlainEnumUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAction() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_CB); + + assertThat(updateActions) + .containsExactly( + ChangeEnumValueOrder.of( + FIELD_NAME_1, asList(ENUM_VALUE_C.getKey(), ENUM_VALUE_B.getKey()))); + } + + @Test + void + buildPlainEnumUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ACBD); + + assertThat(updateActions) + .containsExactly( + AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D), + ChangeEnumValueOrder.of( + FIELD_NAME_1, asList( - ENUM_VALUE_C.getKey(), ENUM_VALUE_A.getKey(), - ENUM_VALUE_B.getKey() - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithRemovedAndDifferentOrder_ShouldBuildChangeOrderAction() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_CB - ); - - assertThat(updateActions).containsExactly( - ChangeEnumValueOrder.of(FIELD_NAME_1, asList( - ENUM_VALUE_C.getKey(), - ENUM_VALUE_B.getKey() - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithAddedAndDifferentOrder_ShouldBuildChangeOrderAndAddActions() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ACBD - ); - - assertThat(updateActions).containsExactly( - AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D), - ChangeEnumValueOrder.of(FIELD_NAME_1, asList( - ENUM_VALUE_A.getKey(), - ENUM_VALUE_C.getKey(), - ENUM_VALUE_B.getKey(), - ENUM_VALUE_D.getKey() - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithAddedEnumValueInBetween_ShouldBuildChangeOrderAndAddActions() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_ADBC - ); - - assertThat(updateActions).containsExactly( + ENUM_VALUE_C.getKey(), + ENUM_VALUE_B.getKey(), + ENUM_VALUE_D.getKey()))); + } + + @Test + void + buildPlainEnumUpdateActions_WithAddedEnumValueInBetween_ShouldBuildChangeOrderAndAddActions() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_ADBC); + + assertThat(updateActions) + .containsExactly( AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D), - ChangeEnumValueOrder.of(FIELD_NAME_1, asList( - ENUM_VALUE_A.getKey(), - ENUM_VALUE_D.getKey(), - ENUM_VALUE_B.getKey(), - ENUM_VALUE_C.getKey() - )) - ); - } - - @Test - void buildPlainEnumUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAddAndChangeOrderActions() { - final List> updateActions = buildEnumValuesUpdateActions( - FIELD_NAME_1, - ENUM_VALUES_ABC, - ENUM_VALUES_CBD - ); - - assertThat(updateActions).containsExactly( + ChangeEnumValueOrder.of( + FIELD_NAME_1, + asList( + ENUM_VALUE_A.getKey(), + ENUM_VALUE_D.getKey(), + ENUM_VALUE_B.getKey(), + ENUM_VALUE_C.getKey()))); + } + + @Test + void + buildPlainEnumUpdateActions_WithAddedRemovedAndDifOrder_ShouldBuildAddAndChangeOrderActions() { + final List> updateActions = + buildEnumValuesUpdateActions(FIELD_NAME_1, ENUM_VALUES_ABC, ENUM_VALUES_CBD); + + assertThat(updateActions) + .containsExactly( AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_D), - ChangeEnumValueOrder.of(FIELD_NAME_1, asList( - ENUM_VALUE_C.getKey(), - ENUM_VALUE_B.getKey(), - ENUM_VALUE_D.getKey() - )) - ); - } - - @Test - void buildLocalizedEnumUpdateActions_WithDuplicateEnumValues_ShouldTriggerDuplicateKeyError() { - assertThatThrownBy(() -> buildEnumValuesUpdateActions( - "field_definition_name", - ENUM_VALUES_ABC, - ENUM_VALUES_ABB - )) - .isInstanceOf(DuplicateKeyException.class) - .hasMessage("Enum Values have duplicated keys. Definition name: " + ChangeEnumValueOrder.of( + FIELD_NAME_1, + asList(ENUM_VALUE_C.getKey(), ENUM_VALUE_B.getKey(), ENUM_VALUE_D.getKey()))); + } + + @Test + void buildLocalizedEnumUpdateActions_WithDuplicateEnumValues_ShouldTriggerDuplicateKeyError() { + assertThatThrownBy( + () -> + buildEnumValuesUpdateActions( + "field_definition_name", ENUM_VALUES_ABC, ENUM_VALUES_ABB)) + .isInstanceOf(DuplicateKeyException.class) + .hasMessage( + "Enum Values have duplicated keys. Definition name: " + "'field_definition_name', Duplicated enum value: 'b'. " + "Enum Values are expected to be unique inside their definition."); - } + } } diff --git a/src/test/java/com/commercetools/sync/types/utils/FieldDefinitionFixtures.java b/src/test/java/com/commercetools/sync/types/utils/FieldDefinitionFixtures.java index 6644124a47..9df6700bf4 100644 --- a/src/test/java/com/commercetools/sync/types/utils/FieldDefinitionFixtures.java +++ b/src/test/java/com/commercetools/sync/types/utils/FieldDefinitionFixtures.java @@ -5,77 +5,64 @@ import io.sphere.sdk.types.FieldDefinition; import io.sphere.sdk.types.LocalizedStringFieldType; import io.sphere.sdk.types.StringFieldType; - import javax.annotation.Nonnull; import javax.annotation.Nullable; public final class FieldDefinitionFixtures { - private static final String RES_ROOT = - "com/commercetools/sync/types/utils/updatefielddefinitions/fields/"; - - static final String TYPE_WITH_FIELDS_AB = - RES_ROOT + "type-with-field-definitions-ab.json"; - static final String TYPE_WITH_FIELDS_ABB = - RES_ROOT + "type-with-field-definitions-abb.json"; - static final String TYPE_WITH_FIELDS_ABC = - RES_ROOT + "type-with-field-definitions-abc.json"; - static final String TYPE_WITH_FIELDS_ABCD = - RES_ROOT + "type-with-field-definitions-abcd.json"; - static final String TYPE_WITH_FIELDS_ABD = - RES_ROOT + "type-with-field-definitions-abd.json"; - static final String TYPE_WITH_FIELDS_CAB = - RES_ROOT + "type-with-field-definitions-cab.json"; - static final String TYPE_WITH_FIELDS_CB = - RES_ROOT + "type-with-field-definitions-cb.json"; - static final String TYPE_WITH_FIELDS_ACBD = - RES_ROOT + "type-with-field-definitions-acbd.json"; - static final String TYPE_WITH_FIELDS_ADBC = - RES_ROOT + "type-with-field-definitions-adbc.json"; - static final String TYPE_WITH_FIELDS_CBD = - RES_ROOT + "type-with-field-definitions-cbd.json"; - static final String TYPE_WITH_FIELDS_ABC_WITH_DIFFERENT_TYPE = - RES_ROOT + "type-with-field-definitions-abc-with-different-type.json"; + private static final String RES_ROOT = + "com/commercetools/sync/types/utils/updatefielddefinitions/fields/"; - static final String FIELD_A = "a"; - static final String FIELD_B = "b"; - static final String FIELD_C = "c"; - static final String FIELD_D = "d"; - static final String LABEL_EN = "label_en"; + static final String TYPE_WITH_FIELDS_AB = RES_ROOT + "type-with-field-definitions-ab.json"; + static final String TYPE_WITH_FIELDS_ABB = RES_ROOT + "type-with-field-definitions-abb.json"; + static final String TYPE_WITH_FIELDS_ABC = RES_ROOT + "type-with-field-definitions-abc.json"; + static final String TYPE_WITH_FIELDS_ABCD = RES_ROOT + "type-with-field-definitions-abcd.json"; + static final String TYPE_WITH_FIELDS_ABD = RES_ROOT + "type-with-field-definitions-abd.json"; + static final String TYPE_WITH_FIELDS_CAB = RES_ROOT + "type-with-field-definitions-cab.json"; + static final String TYPE_WITH_FIELDS_CB = RES_ROOT + "type-with-field-definitions-cb.json"; + static final String TYPE_WITH_FIELDS_ACBD = RES_ROOT + "type-with-field-definitions-acbd.json"; + static final String TYPE_WITH_FIELDS_ADBC = RES_ROOT + "type-with-field-definitions-adbc.json"; + static final String TYPE_WITH_FIELDS_CBD = RES_ROOT + "type-with-field-definitions-cbd.json"; + static final String TYPE_WITH_FIELDS_ABC_WITH_DIFFERENT_TYPE = + RES_ROOT + "type-with-field-definitions-abc-with-different-type.json"; - static final FieldDefinition FIELD_DEFINITION_A = - stringFieldDefinition(FIELD_A, LABEL_EN, false, TextInputHint.SINGLE_LINE); - static final FieldDefinition FIELD_DEFINITION_A_LOCALIZED_TYPE = - localizedStringFieldDefinition(FIELD_A, LABEL_EN, false, TextInputHint.SINGLE_LINE); - static final FieldDefinition FIELD_DEFINITION_B = - stringFieldDefinition(FIELD_B, LABEL_EN, false, TextInputHint.SINGLE_LINE); - static final FieldDefinition FIELD_DEFINITION_C = - stringFieldDefinition(FIELD_C, LABEL_EN, false, TextInputHint.SINGLE_LINE); - static final FieldDefinition FIELD_DEFINITION_D = - stringFieldDefinition(FIELD_D, LABEL_EN, false, TextInputHint.SINGLE_LINE); + static final String FIELD_A = "a"; + static final String FIELD_B = "b"; + static final String FIELD_C = "c"; + static final String FIELD_D = "d"; + static final String LABEL_EN = "label_en"; - static FieldDefinition stringFieldDefinition(@Nonnull final String fieldName, - @Nonnull final String labelEng, - boolean required, - @Nullable final TextInputHint hint) { - return FieldDefinition.of(StringFieldType.of(), - fieldName, - LocalizedString.ofEnglish(labelEng), - required, - hint); - } + static final FieldDefinition FIELD_DEFINITION_A = + stringFieldDefinition(FIELD_A, LABEL_EN, false, TextInputHint.SINGLE_LINE); + static final FieldDefinition FIELD_DEFINITION_A_LOCALIZED_TYPE = + localizedStringFieldDefinition(FIELD_A, LABEL_EN, false, TextInputHint.SINGLE_LINE); + static final FieldDefinition FIELD_DEFINITION_B = + stringFieldDefinition(FIELD_B, LABEL_EN, false, TextInputHint.SINGLE_LINE); + static final FieldDefinition FIELD_DEFINITION_C = + stringFieldDefinition(FIELD_C, LABEL_EN, false, TextInputHint.SINGLE_LINE); + static final FieldDefinition FIELD_DEFINITION_D = + stringFieldDefinition(FIELD_D, LABEL_EN, false, TextInputHint.SINGLE_LINE); + static FieldDefinition stringFieldDefinition( + @Nonnull final String fieldName, + @Nonnull final String labelEng, + boolean required, + @Nullable final TextInputHint hint) { + return FieldDefinition.of( + StringFieldType.of(), fieldName, LocalizedString.ofEnglish(labelEng), required, hint); + } - private static FieldDefinition localizedStringFieldDefinition(@Nonnull final String fieldName, - @Nonnull final String labelEng, - boolean required, - @Nullable final TextInputHint hint) { - return FieldDefinition.of(LocalizedStringFieldType.of(), - fieldName, - LocalizedString.ofEnglish(labelEng), - required, - hint); - } + private static FieldDefinition localizedStringFieldDefinition( + @Nonnull final String fieldName, + @Nonnull final String labelEng, + boolean required, + @Nullable final TextInputHint hint) { + return FieldDefinition.of( + LocalizedStringFieldType.of(), + fieldName, + LocalizedString.ofEnglish(labelEng), + required, + hint); + } - private FieldDefinitionFixtures() { - } + private FieldDefinitionFixtures() {} } diff --git a/src/test/java/com/commercetools/sync/types/utils/FieldDefinitionUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/types/utils/FieldDefinitionUpdateActionUtilsTest.java index 7bbb52da62..ac95e87f71 100644 --- a/src/test/java/com/commercetools/sync/types/utils/FieldDefinitionUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/types/utils/FieldDefinitionUpdateActionUtilsTest.java @@ -1,5 +1,14 @@ package com.commercetools.sync.types.utils; +import static com.commercetools.sync.types.utils.FieldDefinitionFixtures.stringFieldDefinition; +import static com.commercetools.sync.types.utils.FieldDefinitionUpdateActionUtils.buildActions; +import static com.commercetools.sync.types.utils.FieldDefinitionUpdateActionUtils.buildChangeLabelUpdateAction; +import static io.sphere.sdk.models.LocalizedString.ofEnglish; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.EnumValue; import io.sphere.sdk.models.LocalizedEnumValue; @@ -16,281 +25,300 @@ import io.sphere.sdk.types.commands.updateactions.ChangeEnumValueOrder; import io.sphere.sdk.types.commands.updateactions.ChangeFieldDefinitionLabel; import io.sphere.sdk.types.commands.updateactions.ChangeLocalizedEnumValueOrder; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import java.util.List; import java.util.Optional; - -import static com.commercetools.sync.types.utils.FieldDefinitionFixtures.stringFieldDefinition; -import static com.commercetools.sync.types.utils.FieldDefinitionUpdateActionUtils.buildActions; -import static com.commercetools.sync.types.utils.FieldDefinitionUpdateActionUtils.buildChangeLabelUpdateAction; -import static io.sphere.sdk.models.LocalizedString.ofEnglish; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; class FieldDefinitionUpdateActionUtilsTest { - private static final String FIELD_NAME_1 = "fieldName1"; - private static final String LABEL_1 = "label1"; - private static final String LABEL_2 = "label2"; - - - private static FieldDefinition old; - private static FieldDefinition newSame; - private static FieldDefinition newDifferent; - - private static final EnumValue ENUM_VALUE_A = EnumValue.of("a", "label_a"); - private static final EnumValue ENUM_VALUE_B = EnumValue.of("b", "label_b"); - - private static final LocalizedEnumValue LOCALIZED_ENUM_VALUE_A = LocalizedEnumValue.of("a", ofEnglish("label_a")); - private static final LocalizedEnumValue LOCALIZED_ENUM_VALUE_B = LocalizedEnumValue.of("b", ofEnglish("label_b")); - - /** - * Initialises test data. - */ - @BeforeAll - static void setup() { - old = stringFieldDefinition(FIELD_NAME_1, LABEL_1, false, TextInputHint.SINGLE_LINE); - newSame = stringFieldDefinition(FIELD_NAME_1, LABEL_1, false, TextInputHint.SINGLE_LINE); - newDifferent = stringFieldDefinition(FIELD_NAME_1, LABEL_2, true, TextInputHint.MULTI_LINE); - } - - @Test - void buildChangeLabelAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeLabelUpdateAction(old, newDifferent); - - assertThat(result).contains(ChangeFieldDefinitionLabel.of(old.getName(), newDifferent.getLabel())); - } - - @Test - void buildChangeLabelAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeLabelUpdateAction(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildActions_WithNewDifferentValues_ShouldReturnActions() { - final List> result = buildActions(old, newDifferent); - - assertThat(result).containsExactlyInAnyOrder( - ChangeFieldDefinitionLabel.of(old.getName(), newDifferent.getLabel()) - ); - } - - @Test - void buildActions_WithSameValues_ShouldReturnEmpty() { - final List> result = buildActions(old, newSame); - - assertThat(result).isEmpty(); - } - - @Test - void buildActions_WithNewPlainEnum_ShouldReturnAddEnumValueAction() { - final FieldDefinition oldFieldDefinition = FieldDefinition.of( - EnumFieldType.of(singletonList(ENUM_VALUE_A)), - FIELD_NAME_1, - LocalizedString.ofEnglish(LABEL_1), - false, - TextInputHint.SINGLE_LINE); - - - final FieldDefinition newFieldDefinition = FieldDefinition.of( - EnumFieldType.of(asList(ENUM_VALUE_A, ENUM_VALUE_B)), - FIELD_NAME_1, - LocalizedString.ofEnglish(LABEL_1), - false, - TextInputHint.SINGLE_LINE); - - final List> result = buildActions(oldFieldDefinition, newFieldDefinition); - - assertThat(result).containsExactly(AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_B)); - } - - @Test - void buildActions_WithoutOldPlainEnum_ShouldNotReturnAnyValueAction() { - - final FieldDefinition oldFieldDefinition = FieldDefinition.of( - EnumFieldType.of(singletonList(ENUM_VALUE_A)), - FIELD_NAME_1, - LocalizedString.ofEnglish(LABEL_1), - false, - TextInputHint.SINGLE_LINE); - - final FieldDefinition newFieldDefinition = FieldDefinition.of( - EnumFieldType.of(emptyList()), - FIELD_NAME_1, - LocalizedString.ofEnglish(LABEL_1), - false, - TextInputHint.SINGLE_LINE); - - - final List> result = buildActions(oldFieldDefinition, newFieldDefinition); - - assertThat(result).isEmpty(); - } - - @Test - void buildActions_WithNewLocalizedEnum_ShouldReturnAddLocalizedEnumValueAction() { - - final FieldDefinition oldFieldDefinition = FieldDefinition.of( - LocalizedEnumFieldType.of(singletonList(LOCALIZED_ENUM_VALUE_A)), - FIELD_NAME_1, - LocalizedString.ofEnglish(LABEL_1), - false, - TextInputHint.SINGLE_LINE); - - final FieldDefinition newFieldDefinition = FieldDefinition.of( - LocalizedEnumFieldType.of(asList(LOCALIZED_ENUM_VALUE_A, LOCALIZED_ENUM_VALUE_B)), - FIELD_NAME_1, - LocalizedString.ofEnglish(LABEL_1), - false, - TextInputHint.SINGLE_LINE); - - - final List> result = buildActions(oldFieldDefinition, newFieldDefinition); - - assertThat(result).containsExactly(AddLocalizedEnumValue.of(FIELD_NAME_1, LOCALIZED_ENUM_VALUE_B)); - } - - @Test - void buildActions_WithStringFieldTypesWithLabelChanges_ShouldBuildChangeLabelAction() { - final FieldDefinition oldFieldDefinition = FieldDefinition.of(StringFieldType.of(),"fieldName1", - ofEnglish("label1"), false); - - final FieldDefinition newFieldDefinition = FieldDefinition.of(StringFieldType.of(),"fieldName1", - ofEnglish("label2"), false); - - final List> result = - buildActions(oldFieldDefinition, newFieldDefinition); - - assertThat(result).containsExactly( + private static final String FIELD_NAME_1 = "fieldName1"; + private static final String LABEL_1 = "label1"; + private static final String LABEL_2 = "label2"; + + private static FieldDefinition old; + private static FieldDefinition newSame; + private static FieldDefinition newDifferent; + + private static final EnumValue ENUM_VALUE_A = EnumValue.of("a", "label_a"); + private static final EnumValue ENUM_VALUE_B = EnumValue.of("b", "label_b"); + + private static final LocalizedEnumValue LOCALIZED_ENUM_VALUE_A = + LocalizedEnumValue.of("a", ofEnglish("label_a")); + private static final LocalizedEnumValue LOCALIZED_ENUM_VALUE_B = + LocalizedEnumValue.of("b", ofEnglish("label_b")); + + /** Initialises test data. */ + @BeforeAll + static void setup() { + old = stringFieldDefinition(FIELD_NAME_1, LABEL_1, false, TextInputHint.SINGLE_LINE); + newSame = stringFieldDefinition(FIELD_NAME_1, LABEL_1, false, TextInputHint.SINGLE_LINE); + newDifferent = stringFieldDefinition(FIELD_NAME_1, LABEL_2, true, TextInputHint.MULTI_LINE); + } + + @Test + void buildChangeLabelAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = buildChangeLabelUpdateAction(old, newDifferent); + + assertThat(result) + .contains(ChangeFieldDefinitionLabel.of(old.getName(), newDifferent.getLabel())); + } + + @Test + void buildChangeLabelAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildChangeLabelUpdateAction(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildActions_WithNewDifferentValues_ShouldReturnActions() { + final List> result = buildActions(old, newDifferent); + + assertThat(result) + .containsExactlyInAnyOrder( + ChangeFieldDefinitionLabel.of(old.getName(), newDifferent.getLabel())); + } + + @Test + void buildActions_WithSameValues_ShouldReturnEmpty() { + final List> result = buildActions(old, newSame); + + assertThat(result).isEmpty(); + } + + @Test + void buildActions_WithNewPlainEnum_ShouldReturnAddEnumValueAction() { + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( + EnumFieldType.of(singletonList(ENUM_VALUE_A)), + FIELD_NAME_1, + LocalizedString.ofEnglish(LABEL_1), + false, + TextInputHint.SINGLE_LINE); + + final FieldDefinition newFieldDefinition = + FieldDefinition.of( + EnumFieldType.of(asList(ENUM_VALUE_A, ENUM_VALUE_B)), + FIELD_NAME_1, + LocalizedString.ofEnglish(LABEL_1), + false, + TextInputHint.SINGLE_LINE); + + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); + + assertThat(result).containsExactly(AddEnumValue.of(FIELD_NAME_1, ENUM_VALUE_B)); + } + + @Test + void buildActions_WithoutOldPlainEnum_ShouldNotReturnAnyValueAction() { + + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( + EnumFieldType.of(singletonList(ENUM_VALUE_A)), + FIELD_NAME_1, + LocalizedString.ofEnglish(LABEL_1), + false, + TextInputHint.SINGLE_LINE); + + final FieldDefinition newFieldDefinition = + FieldDefinition.of( + EnumFieldType.of(emptyList()), + FIELD_NAME_1, + LocalizedString.ofEnglish(LABEL_1), + false, + TextInputHint.SINGLE_LINE); + + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); + + assertThat(result).isEmpty(); + } + + @Test + void buildActions_WithNewLocalizedEnum_ShouldReturnAddLocalizedEnumValueAction() { + + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( + LocalizedEnumFieldType.of(singletonList(LOCALIZED_ENUM_VALUE_A)), + FIELD_NAME_1, + LocalizedString.ofEnglish(LABEL_1), + false, + TextInputHint.SINGLE_LINE); + + final FieldDefinition newFieldDefinition = + FieldDefinition.of( + LocalizedEnumFieldType.of(asList(LOCALIZED_ENUM_VALUE_A, LOCALIZED_ENUM_VALUE_B)), + FIELD_NAME_1, + LocalizedString.ofEnglish(LABEL_1), + false, + TextInputHint.SINGLE_LINE); + + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); + + assertThat(result) + .containsExactly(AddLocalizedEnumValue.of(FIELD_NAME_1, LOCALIZED_ENUM_VALUE_B)); + } + + @Test + void buildActions_WithStringFieldTypesWithLabelChanges_ShouldBuildChangeLabelAction() { + final FieldDefinition oldFieldDefinition = + FieldDefinition.of(StringFieldType.of(), "fieldName1", ofEnglish("label1"), false); + + final FieldDefinition newFieldDefinition = + FieldDefinition.of(StringFieldType.of(), "fieldName1", ofEnglish("label2"), false); + + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); + + assertThat(result) + .containsExactly( ChangeFieldDefinitionLabel.of("fieldName1", newFieldDefinition.getLabel())); - } + } - @Test - void buildActions_WithSetOfStringFieldTypesWithDefinitionLabelChanges_ShouldBuildChangeLabelAction() { - final FieldDefinition oldFieldDefinition = FieldDefinition.of(SetFieldType.of(StringFieldType.of()), - "fieldName1", ofEnglish("label1"), false); + @Test + void + buildActions_WithSetOfStringFieldTypesWithDefinitionLabelChanges_ShouldBuildChangeLabelAction() { + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( + SetFieldType.of(StringFieldType.of()), "fieldName1", ofEnglish("label1"), false); - final FieldDefinition newFieldDefinition = FieldDefinition.of(SetFieldType.of(StringFieldType.of()), - "fieldName1", ofEnglish("label2"), false); + final FieldDefinition newFieldDefinition = + FieldDefinition.of( + SetFieldType.of(StringFieldType.of()), "fieldName1", ofEnglish("label2"), false); - final List> result = - buildActions(oldFieldDefinition, newFieldDefinition); + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); - assertThat(result).containsExactly( + assertThat(result) + .containsExactly( ChangeFieldDefinitionLabel.of("fieldName1", newFieldDefinition.getLabel())); - } - - @Test - void buildActions_WithSetOfSetOfStringFieldTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { - final FieldDefinition oldFieldDefinition = FieldDefinition.of( - SetFieldType.of(SetFieldType.of(StringFieldType.of())), "fieldName1", ofEnglish("label1"), false); - - final FieldDefinition newFieldDefinition = FieldDefinition.of( - SetFieldType.of(SetFieldType.of(StringFieldType.of())), "fieldName1", ofEnglish("label2"), false); - - final List> result = - buildActions(oldFieldDefinition, newFieldDefinition); - - assertThat(result).containsExactly( + } + + @Test + void + buildActions_WithSetOfSetOfStringFieldTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( + SetFieldType.of(SetFieldType.of(StringFieldType.of())), + "fieldName1", + ofEnglish("label1"), + false); + + final FieldDefinition newFieldDefinition = + FieldDefinition.of( + SetFieldType.of(SetFieldType.of(StringFieldType.of())), + "fieldName1", + ofEnglish("label2"), + false); + + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); + + assertThat(result) + .containsExactly( ChangeFieldDefinitionLabel.of("fieldName1", newFieldDefinition.getLabel())); - } - - @Test - void buildActions_WithSameSetOfEnumsFieldTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { - final FieldDefinition oldFieldDefinition = FieldDefinition.of(SetFieldType.of(EnumFieldType.of(emptyList())), - "fieldName1", ofEnglish("label1"), false); - - final FieldDefinition newFieldDefinition = FieldDefinition.of(SetFieldType.of(EnumFieldType.of(emptyList())), - "fieldName1", ofEnglish("label2"), false); - - final List> result = - buildActions(oldFieldDefinition, newFieldDefinition); - - assertThat(result).containsExactly( + } + + @Test + void buildActions_WithSameSetOfEnumsFieldTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( + SetFieldType.of(EnumFieldType.of(emptyList())), + "fieldName1", + ofEnglish("label1"), + false); + + final FieldDefinition newFieldDefinition = + FieldDefinition.of( + SetFieldType.of(EnumFieldType.of(emptyList())), + "fieldName1", + ofEnglish("label2"), + false); + + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); + + assertThat(result) + .containsExactly( ChangeFieldDefinitionLabel.of("fieldName1", newFieldDefinition.getLabel())); - } - - @Test - void buildActions_WithChangedSetOfEnumFieldTypes_ShouldBuildEnumActions() { - final FieldDefinition oldFieldDefinition = FieldDefinition.of( - SetFieldType.of(EnumFieldType.of( - asList( - ENUM_VALUE_A, - ENUM_VALUE_B - ))), - "fieldName1", ofEnglish("label1"), false); - - final FieldDefinition newFieldDefinition = FieldDefinition.of( + } + + @Test + void buildActions_WithChangedSetOfEnumFieldTypes_ShouldBuildEnumActions() { + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( + SetFieldType.of(EnumFieldType.of(asList(ENUM_VALUE_A, ENUM_VALUE_B))), + "fieldName1", + ofEnglish("label1"), + false); + + final FieldDefinition newFieldDefinition = + FieldDefinition.of( SetFieldType.of( - EnumFieldType.of( - asList( - ENUM_VALUE_B, - ENUM_VALUE_A, - EnumValue.of("c", "c") - ))), - "fieldName1", ofEnglish("label1"), false); + EnumFieldType.of(asList(ENUM_VALUE_B, ENUM_VALUE_A, EnumValue.of("c", "c")))), + "fieldName1", + ofEnglish("label1"), + false); - final List> result = - buildActions(oldFieldDefinition, newFieldDefinition); + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); - assertThat(result).containsExactly( + assertThat(result) + .containsExactly( AddEnumValue.of("fieldName1", EnumValue.of("c", "c")), ChangeEnumValueOrder.of("fieldName1", asList("b", "a", "c"))); - } + } - @Test - void buildActions_WithSameSetOfLEnumFieldTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { - // preparation - final FieldDefinition oldFieldDefinition = FieldDefinition.of( + @Test + void buildActions_WithSameSetOfLEnumFieldTypesWithDefLabelChanges_ShouldBuildChangeLabelAction() { + // preparation + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( SetFieldType.of(SetFieldType.of(LocalizedEnumFieldType.of(emptyList()))), - "fieldName1", ofEnglish("label1"), false); + "fieldName1", + ofEnglish("label1"), + false); - final FieldDefinition newFieldDefinition = FieldDefinition.of( + final FieldDefinition newFieldDefinition = + FieldDefinition.of( SetFieldType.of(SetFieldType.of(LocalizedEnumFieldType.of(emptyList()))), - "fieldName1", ofEnglish("label2"), false); - - // test - final List> result = - buildActions(oldFieldDefinition, newFieldDefinition); - - // assertion - assertThat(result).containsExactly(ChangeFieldDefinitionLabel.of("fieldName1", - newFieldDefinition.getLabel())); - } - - @Test - void buildActions_WithChangedSetOfLocalizedEnumFieldTypes_ShouldBuildEnumActions() { - // preparation - final FieldDefinition oldFieldDefinition = FieldDefinition.of( - SetFieldType.of(LocalizedEnumFieldType.of( - asList( - LOCALIZED_ENUM_VALUE_A, - LOCALIZED_ENUM_VALUE_B - ))), - "fieldName1", ofEnglish("label1"), false); - - final FieldDefinition newFieldDefinition = FieldDefinition.of( - SetFieldType.of(LocalizedEnumFieldType.of( - asList( - LOCALIZED_ENUM_VALUE_B, - LOCALIZED_ENUM_VALUE_A, - LocalizedEnumValue.of("c", ofEnglish("c")) - ))), - "fieldName1", ofEnglish("label1"), false); - - // test - final List> result = - buildActions(oldFieldDefinition, newFieldDefinition); - - // assertion - assertThat(result).containsExactly( + "fieldName1", + ofEnglish("label2"), + false); + + // test + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); + + // assertion + assertThat(result) + .containsExactly( + ChangeFieldDefinitionLabel.of("fieldName1", newFieldDefinition.getLabel())); + } + + @Test + void buildActions_WithChangedSetOfLocalizedEnumFieldTypes_ShouldBuildEnumActions() { + // preparation + final FieldDefinition oldFieldDefinition = + FieldDefinition.of( + SetFieldType.of( + LocalizedEnumFieldType.of(asList(LOCALIZED_ENUM_VALUE_A, LOCALIZED_ENUM_VALUE_B))), + "fieldName1", + ofEnglish("label1"), + false); + + final FieldDefinition newFieldDefinition = + FieldDefinition.of( + SetFieldType.of( + LocalizedEnumFieldType.of( + asList( + LOCALIZED_ENUM_VALUE_B, + LOCALIZED_ENUM_VALUE_A, + LocalizedEnumValue.of("c", ofEnglish("c"))))), + "fieldName1", + ofEnglish("label1"), + false); + + // test + final List> result = buildActions(oldFieldDefinition, newFieldDefinition); + + // assertion + assertThat(result) + .containsExactly( AddLocalizedEnumValue.of("fieldName1", LocalizedEnumValue.of("c", ofEnglish("c"))), - ChangeLocalizedEnumValueOrder.of("fieldName1", asList("b", "a", "c")) - ); - } + ChangeLocalizedEnumValueOrder.of("fieldName1", asList("b", "a", "c"))); + } } diff --git a/src/test/java/com/commercetools/sync/types/utils/TypeUpdateActionUtilsTest.java b/src/test/java/com/commercetools/sync/types/utils/TypeUpdateActionUtilsTest.java index ca664fe3c4..982f8df806 100644 --- a/src/test/java/com/commercetools/sync/types/utils/TypeUpdateActionUtilsTest.java +++ b/src/test/java/com/commercetools/sync/types/utils/TypeUpdateActionUtilsTest.java @@ -1,5 +1,11 @@ package com.commercetools.sync.types.utils; +import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildChangeNameUpdateAction; +import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildSetDescriptionUpdateAction; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import io.sphere.sdk.commands.UpdateAction; import io.sphere.sdk.models.LocalizedString; import io.sphere.sdk.types.ResourceTypeIdsSetBuilder; @@ -8,78 +14,66 @@ import io.sphere.sdk.types.TypeDraftBuilder; import io.sphere.sdk.types.commands.updateactions.ChangeName; import io.sphere.sdk.types.commands.updateactions.SetDescription; +import java.util.Optional; +import java.util.Set; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.util.Optional; -import java.util.Set; +class TypeUpdateActionUtilsTest { + private static Type old; + private static TypeDraft newSame; + private static TypeDraft newDifferent; -import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildChangeNameUpdateAction; -import static com.commercetools.sync.types.utils.TypeUpdateActionUtils.buildSetDescriptionUpdateAction; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; + /** Initialises test data. */ + @BeforeAll + static void setup() { + final String key = "category-custom-type-key"; + final LocalizedString name = LocalizedString.ofEnglish("type for standard categories"); + final LocalizedString desc = LocalizedString.ofEnglish("description for category custom type"); -class TypeUpdateActionUtilsTest { - private static Type old; - private static TypeDraft newSame; - private static TypeDraft newDifferent; - - /** - * Initialises test data. - */ - @BeforeAll - static void setup() { - final String key = "category-custom-type-key"; - final LocalizedString name = LocalizedString.ofEnglish("type for standard categories"); - final LocalizedString desc = LocalizedString.ofEnglish("description for category custom type"); - - final Set resourceTypeIds = ResourceTypeIdsSetBuilder.of() - .addCategories() - .build(); - - old = mock(Type.class); - when(old.getKey()).thenReturn(key); - when(old.getName()).thenReturn(name); - when(old.getDescription()).thenReturn(desc); - - newSame = TypeDraftBuilder.of(key, name, resourceTypeIds) - .description(desc) - .build(); - - - newDifferent = TypeDraftBuilder.of("category-custom-type-key-2", + final Set resourceTypeIds = ResourceTypeIdsSetBuilder.of().addCategories().build(); + + old = mock(Type.class); + when(old.getKey()).thenReturn(key); + when(old.getName()).thenReturn(name); + when(old.getDescription()).thenReturn(desc); + + newSame = TypeDraftBuilder.of(key, name, resourceTypeIds).description(desc).build(); + + newDifferent = + TypeDraftBuilder.of( + "category-custom-type-key-2", LocalizedString.ofEnglish("type for standard categories 2"), resourceTypeIds) - .description(LocalizedString.ofEnglish("description for category custom type 2")) - .build(); - } + .description(LocalizedString.ofEnglish("description for category custom type 2")) + .build(); + } - @Test - void buildChangeNameAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildChangeNameUpdateAction(old, newDifferent); + @Test + void buildChangeNameAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = buildChangeNameUpdateAction(old, newDifferent); - assertThat(result).contains(ChangeName.of(newDifferent.getName())); - } + assertThat(result).contains(ChangeName.of(newDifferent.getName())); + } - @Test - void buildChangeNameAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildChangeNameUpdateAction(old, newSame); + @Test + void buildChangeNameAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildChangeNameUpdateAction(old, newSame); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } - @Test - void buildSetDescriptionAction_WithDifferentValues_ShouldReturnAction() { - final Optional> result = buildSetDescriptionUpdateAction(old, newDifferent); + @Test + void buildSetDescriptionAction_WithDifferentValues_ShouldReturnAction() { + final Optional> result = buildSetDescriptionUpdateAction(old, newDifferent); - assertThat(result).contains(SetDescription.of(newDifferent.getDescription())); - } + assertThat(result).contains(SetDescription.of(newDifferent.getDescription())); + } - @Test - void buildSetDescriptionAction_WithSameValues_ShouldReturnEmptyOptional() { - final Optional> result = buildSetDescriptionUpdateAction(old, newSame); + @Test + void buildSetDescriptionAction_WithSameValues_ShouldReturnEmptyOptional() { + final Optional> result = buildSetDescriptionUpdateAction(old, newSame); - assertThat(result).isEmpty(); - } + assertThat(result).isEmpty(); + } } From 651c6b278f9a0b50fa8b19044338fd3615fc91e1 Mon Sep 17 00:00:00 2001 From: aoz Date: Mon, 11 Jan 2021 20:52:04 +0100 Subject: [PATCH 6/9] use this file to ignore the formatting changes in git blame. --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..d3d0110210 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# google style formattings added in this commit hash (revision) +0bb39eb7360c1863a3e3c7e50519683865748cfd From 1c1f77e522767a5e137feadbff3f824853cb6618 Mon Sep 17 00:00:00 2001 From: aoz Date: Tue, 12 Jan 2021 09:47:58 +0100 Subject: [PATCH 7/9] Revert "Fixes #34 - Make stricter PMD rule priority (#652)" This reverts commit ff1d27ed --- gradle-scripts/pmd.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle-scripts/pmd.gradle b/gradle-scripts/pmd.gradle index fb6736ea88..fe618601d8 100644 --- a/gradle-scripts/pmd.gradle +++ b/gradle-scripts/pmd.gradle @@ -1,10 +1,11 @@ /** * PMD tool + * If a rule priority, with a level greater than or equal to 2, is violated; the build should fail. */ pmd { toolVersion = pmdVersion ruleSetConfig = rootProject.resources.text.fromFile('config/pmd/pmd.xml') - rulePriority = 1 + rulePriority = 2 consoleOutput = true } From 914cdd6040006c0718b0beee7f7c9c97145ee2e1 Mon Sep 17 00:00:00 2001 From: aoz Date: Tue, 12 Jan 2021 11:48:36 +0100 Subject: [PATCH 8/9] Update CONTRIBUTING.md --- docs/CONTRIBUTING.md | 85 +++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index f2fd3c64e4..a2ef141f11 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -18,10 +18,15 @@ Thanks for taking the time to contribute :+1::tada: All contributions are welcom - [Install to local maven repo](#install-to-local-maven-repo) - [Publish JavaDoc](#publish-javadoc) - [Build and publish to Bintray](#build-and-publish-to-bintray) - - [Run Spotless Style Check](#run-spotless-style-check) - - [Fix Spotless style violations](#fix-spotless-style-violations) - [Integration Tests](#integration-tests) - [Running](#running) +- [Using the google java style and code formatter](#using-the-google-java-style-and-code-formatter) + - [IntelliJ, Android Studio, and other JetBrains IDEs](#intellij-android-studio-and-other-jetbrains-ides) + - [Eclipse](#eclipse) + - [Spotless commands](#spotless-commands) + - [Run Spotless Style Check](#run-spotless-style-check) + - [Fix Spotless style violations](#fix-spotless-style-violations) + - [Ignoring mass reformatting commits with git blame](#ignoring-mass-reformatting-commits-with-git-blame) @@ -33,16 +38,11 @@ If you have push access to the repository you can fix them directly otherwise ju ### Features or Bug Fixes -- Every PR should address an issue on the repository. If the issue doesn't exist, please create it first. -- Pull requests should always follow the following naming convention: -`[issue-number]-[pr-name]`. For example, -to address issue #65 which refers to a style bug, the PR addressing it should have a name that looks something like - `65-fix-style-bug`. -- Commit messages should always be prefixed with the number of the issue that they address. -For example, `#65: Remove redundant space.` -- After your PR is merged to master: - - Delete the branch. - - Mark the issue it addresses with the `merged-to-master` label. +- Every PR should address an issue on the repository. If the issue doesn't exist, please create it first and link PR with the issue. +- After your PR is approved by all reviewers and the build is green: + - Use `Squash and merge` option on a pull request on GitHub, with that the pull request's commits should be squashed into a single commit. + > Instead of seeing all of a contributor's individual commit messages, the commits should be combined into one commit message with a clear commit description. + - Delete the branch when the PR is closed. - Close the issue **only** if the change was released. ## Development @@ -87,31 +87,21 @@ are omitted because `javadoc` is previously created in `build` task, we just sho For more detailed information on the build and the release process, see [Build and Release](BUILD.md) documentation. -##### Run Spotless Style Check -````bash -./gradlew spotlessCheck -```` - -##### Fix Spotless style violations -````bash -./gradlew spotlessApply -```` - ### Integration Tests 1. The integration tests of the library require to have two CTP projects (a source project and a target project) where the -data will be tested to be synced on from the source to the target project. +data will be tested to be synced from the source to the target project. 2. Running the tests does the following: - - Clean all the data in both projects. + - Clean all the data on both projects. - Create test data in either/both projects depending on the test. - Execute the tests. - Clean all the data in both projects, leaving them empty. #### Running -To run the integration tests, CTP credentials are required. Credential can be obtained once you create a CTP project. -For details, please refer to following link: +To run the integration tests, CTP credentials are required. The credential can be obtained once you create a CTP project. +For details, please refer to the following link: https://docs.commercetools.com/merchant-center/projects.html#creating-a-project 1. Use credentials Java properties file `/src/integration-test/resources/it.properties`: @@ -143,9 +133,48 @@ https://docs.commercetools.com/merchant-center/projects.html#creating-a-project **Note**: `it.properties` file has precedence over environment variables. If the file exists - the environment variables are ignored. If the existing `it.properties` file is empty or one of the properties - is missing - exception will be thrown on the tests execution + is missing - exception will be thrown on the execution of the tests -If one of two options above is set - run the integration tests: +If one of the two options above is set - run the integration tests: ```bash ./gradlew integrationTest ``` + +## Using the google java style and code formatter + +We are using `google-java-format` to format Java source code to comply with [Google Java Style](https://google.github.io/styleguide/javaguide.html). + +### IntelliJ, Android Studio, and other JetBrains IDEs + +A [google-java-format IntelliJ plugin](https://plugins.jetbrains.com/plugin/8527) is available from the plugin repository. To install it, go to your IDE's settings and select the `Plugins` category. Click the `Marketplace` tab, search for the `google-java-format` plugin, and click the `Install` button. + +The plugin will be disabled by default. To enable it in the current project, go to `File→Settings...→google-java-format Settings` (or `IntelliJIDEA→Preferences...→Other Settings→google-java-format Settings` on macOS) and check the `Enable google-java-format` checkbox. (A notification will be presented when you first open a project offering to do this for you.) + +To enable it by default in new projects, use `File→Other Settings→Default Settings...`. +When enabled, it will replace the normal `Reformat Code` action, which can be triggered by the `Code` menu or with the Ctrl-Alt (by default) keyboard shortcut. + +### Eclipse + +[google-java-format Eclipse plugin](https://github.com/google/google-java-format/releases/download/google-java-format-1.6/google-java-format-eclipse-plugin_1.6.0.jar) can be downloaded from the releases page. Drop it into the Eclipse[drop-ins folder](http://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fmisc%2Fp2_dropins_format.html)to activate the plugin. + +The plugin adds a `google-java-format` formatter implementation that can be configured in `Window > Preferences > Java > Code Style > Formatter > Formatter Implementation`. + +### Spotless commands +##### Run Spotless Style Check +````bash +./gradlew spotlessCheck +```` + +##### Fix Spotless style violations +````bash +./gradlew spotlessApply +```` + +### Ignoring mass reformatting commits with git blame + +To exclude the formatting commits git blame supports writing the commit hashes into a file and then referencing the file with `--ignore-revs-file`. +To be able to archive that `git blame ./file.java --ignore-revs-file .git-blame-ignore-revs` command to ignore this revision to find a better git history. + +Also `git config blame.ignoreRevsFile .git-blame-ignore-revs` could be configured to ignore this revision always. + +> We create `.git-blame-ignore-revs` that could be found in the repository. From b85544f8a2a1d12bcc81348449383c030cbc2bbd Mon Sep 17 00:00:00 2001 From: aoz Date: Wed, 13 Jan 2021 11:08:08 +0100 Subject: [PATCH 9/9] Bump versions from 3.0.2 to 3.1.0 --- README.md | 18 +++++++++--------- docs/README.md | 16 ++++++++-------- docs/RELEASE_NOTES.md | 8 +++++--- docs/usage/CART_DISCOUNT_SYNC.md | 4 ++-- docs/usage/CATEGORY_SYNC.md | 4 ++-- docs/usage/CUSTOMER_SYNC.md | 4 ++-- docs/usage/CUSTOM_OBJECT_SYNC.md | 2 +- docs/usage/IMPORTANT_USAGE_TIPS.md | 2 +- docs/usage/INVENTORY_SYNC.md | 4 ++-- docs/usage/PRODUCT_SYNC.md | 4 ++-- docs/usage/PRODUCT_TYPE_SYNC.md | 4 ++-- docs/usage/QUICK_START.md | 4 ++-- docs/usage/SHOPPING_LIST_SYNC.md | 4 ++-- docs/usage/STATE_SYNC.md | 4 ++-- docs/usage/TAX_CATEGORY_SYNC.md | 2 +- docs/usage/TYPE_SYNC.md | 2 +- mkdocs.yml | 4 ++-- 17 files changed, 46 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 3bed52258f..4eca81dcfc 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ # commercetools sync [![Build Status](https://travis-ci.com/commercetools/commercetools-sync-java.svg?branch=master)](https://travis-ci.com/commercetools/commercetools-sync-java) [![codecov](https://codecov.io/gh/commercetools/commercetools-sync-java/branch/master/graph/badge.svg)](https://codecov.io/gh/commercetools/commercetools-sync-java) -[![Benchmarks 3.0.2](https://img.shields.io/badge/Benchmarks-3.0.2-orange.svg)](https://commercetools.github.io/commercetools-sync-java/benchmarks/) -[![Download from JCenter](https://img.shields.io/badge/Bintray_JCenter-3.0.2-green.svg) ](https://bintray.com/commercetools/maven/commercetools-sync-java/_latestVersion) -[![Download from Maven Central](https://img.shields.io/badge/Maven_Central-3.0.2-blue.svg)](https://search.maven.org/artifact/com.commercetools/commercetools-sync-java/3.0.2/jar) -[![Javadoc](http://javadoc-badge.appspot.com/com.commercetools/commercetools-sync-java.svg?label=Javadoc)](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/) +[![Benchmarks 3.1.0](https://img.shields.io/badge/Benchmarks-3.1.0-orange.svg)](https://commercetools.github.io/commercetools-sync-java/benchmarks/) +[![Download from JCenter](https://img.shields.io/badge/Bintray_JCenter-3.1.0-green.svg) ](https://bintray.com/commercetools/maven/commercetools-sync-java/_latestVersion) +[![Download from Maven Central](https://img.shields.io/badge/Maven_Central-3.1.0-blue.svg)](https://search.maven.org/artifact/com.commercetools/commercetools-sync-java/3.1.0/jar) +[![Javadoc](http://javadoc-badge.appspot.com/com.commercetools/commercetools-sync-java.svg?label=Javadoc)](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/) [![Known Vulnerabilities](https://snyk.io/test/github/commercetools/commercetools-sync-java/4b2e26113d591bda158217c5dc1cf80a88665646/badge.svg)](https://snyk.io/test/github/commercetools/commercetools-sync-java/4b2e26113d591bda158217c5dc1cf80a88665646) More at https://commercetools.github.io/commercetools-sync-java @@ -41,7 +41,7 @@ The library supports synchronising the following entities in commercetools - [Ivy](#ivy) - [Roadmap](#roadmap) - [Release Notes](/docs/RELEASE_NOTES.md) -- [Javadoc](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/) +- [Javadoc](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/) - [Benchmarks](https://commercetools.github.io/commercetools-sync-java/benchmarks/) @@ -83,24 +83,24 @@ Here are the most popular ones: com.commercetools commercetools-sync-java - 3.0.2 + 3.1.0 ```` #### Gradle ````groovy -implementation 'com.commercetools:commercetools-sync-java:3.0.2' +implementation 'com.commercetools:commercetools-sync-java:3.1.0' ```` #### SBT ```` -libraryDependencies += "com.commercetools" % "commercetools-sync-java" % "3.0.2" +libraryDependencies += "com.commercetools" % "commercetools-sync-java" % "3.1.0" ```` #### Ivy ````xml - + ```` diff --git a/docs/README.md b/docs/README.md index 6eb8524f94..e3cee8d680 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,10 +2,10 @@ # commercetools sync [![Build Status](https://travis-ci.org/commercetools/commercetools-sync-java.svg?branch=master)](https://travis-ci.org/commercetools/commercetools-sync-java) [![codecov](https://codecov.io/gh/commercetools/commercetools-sync-java/branch/master/graph/badge.svg)](https://codecov.io/gh/commercetools/commercetools-sync-java) -[![Benchmarks 3.0.2](https://img.shields.io/badge/Benchmarks-3.0.2-orange.svg)](https://commercetools.github.io/commercetools-sync-java/benchmarks/) -[![Download from JCenter](https://img.shields.io/badge/Bintray_JCenter-3.0.2-green.svg) ](https://bintray.com/commercetools/maven/commercetools-sync-java/_latestVersion) -[![Download from Maven Central](https://img.shields.io/badge/Maven_Central-3.0.2-blue.svg)](https://search.maven.org/artifact/com.commercetools/commercetools-sync-java/3.0.2/jar) -[![Javadoc](http://javadoc-badge.appspot.com/com.commercetools/commercetools-sync-java.svg?label=Javadoc)](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/) +[![Benchmarks 3.1.0](https://img.shields.io/badge/Benchmarks-3.1.0-orange.svg)](https://commercetools.github.io/commercetools-sync-java/benchmarks/) +[![Download from JCenter](https://img.shields.io/badge/Bintray_JCenter-3.1.0-green.svg) ](https://bintray.com/commercetools/maven/commercetools-sync-java/_latestVersion) +[![Download from Maven Central](https://img.shields.io/badge/Maven_Central-3.1.0-blue.svg)](https://search.maven.org/artifact/com.commercetools/commercetools-sync-java/3.1.0/jar) +[![Javadoc](http://javadoc-badge.appspot.com/com.commercetools/commercetools-sync-java.svg?label=Javadoc)](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/) [![Known Vulnerabilities](https://snyk.io/test/github/commercetools/commercetools-sync-java/4b2e26113d591bda158217c5dc1cf80a88665646/badge.svg)](https://snyk.io/test/github/commercetools/commercetools-sync-java/4b2e26113d591bda158217c5dc1cf80a88665646) @@ -59,18 +59,18 @@ Here are the most popular ones: com.commercetools commercetools-sync-java - 3.0.2 + 3.1.0 ```` #### Gradle ````groovy -implementation 'com.commercetools:commercetools-sync-java:3.0.2' +implementation 'com.commercetools:commercetools-sync-java:3.1.0' ```` #### SBT ```` -libraryDependencies += "com.commercetools" % "commercetools-sync-java" % "3.0.2" +libraryDependencies += "com.commercetools" % "commercetools-sync-java" % "3.1.0" ```` #### Ivy ````xml - + ```` diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index 7bd8170883..652993c5f8 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -25,8 +25,9 @@ - **Build Tools** - Convenient handling of env vars for integration tests. 7. Add Migration guide section which specifies explicitly if there are breaking changes and how to tackle them. +--> -### 3.1.0 - Jan XX, 2021 +### 3.1.0 - Jan 13, 2021 [Commits](https://github.com/commercetools/commercetools-sync-java/compare/3.0.2...3.1.0) | [Javadoc](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/) | [Jar](https://bintray.com/commercetools/maven/commercetools-sync-java/3.1.0) @@ -34,11 +35,12 @@ - 🎉 **New Features** (1) - Added clean up implementation for the outdated pending reference resolution custom objects. [#650](https://github.com/commercetools/commercetools-sync-java/issues/650) +- ✨ **Enhancements** (1) + - Added a graphQL pagination utility. [#627](https://github.com/commercetools/commercetools-sync-java/issues/627) + - ✨ **Documentation** (1) - Documentation added for the cleaning up of the unresolved references, check the [cleanup guide](https://github.com/commercetools/commercetools-sync-java/blob/master/docs/usage/CLEANUP_GUIDE.md) for more details. ---> - ### 3.0.2 - Dec 16, 2020 [Commits](https://github.com/commercetools/commercetools-sync-java/compare/3.0.1...3.0.2) | [Javadoc](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/) | diff --git a/docs/usage/CART_DISCOUNT_SYNC.md b/docs/usage/CART_DISCOUNT_SYNC.md index 8ae670bac1..2189f14f3d 100644 --- a/docs/usage/CART_DISCOUNT_SYNC.md +++ b/docs/usage/CART_DISCOUNT_SYNC.md @@ -36,7 +36,7 @@ against a [CartDiscountDraft](https://docs.commercetools.com/http-api-projects-c #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java @@ -67,7 +67,7 @@ Therefore, in order to resolve the actual ids of those references in the sync pr ##### Syncing from a commercetools project -When syncing from a source commercetools project, you can use [`mapToCartDiscountDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtils.html#mapToCartDiscountDrafts-java.util.List-) +When syncing from a source commercetools project, you can use [`mapToCartDiscountDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/com/commercetools/sync/cartdiscounts/utils/CartDiscountReferenceResolutionUtils.html#mapToCartDiscountDrafts-java.util.List-) method that maps from a `CartDiscount` to `CartDiscountDraft` in order to make them ready for reference resolution by the sync, for example: ````java diff --git a/docs/usage/CATEGORY_SYNC.md b/docs/usage/CATEGORY_SYNC.md index 4a64c2745c..c58c280841 100644 --- a/docs/usage/CATEGORY_SYNC.md +++ b/docs/usage/CATEGORY_SYNC.md @@ -36,7 +36,7 @@ against a [CategoryDraft](https://docs.commercetools.com/http-api-projects-categ #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java @@ -69,7 +69,7 @@ Therefore, in order to resolve the actual ids of those references in sync proces ##### Syncing from a commercetools project -When syncing from a source commercetools project, you can use [`mapToCategoryDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtils.html#mapToCategoryDrafts-java.util.List-) +When syncing from a source commercetools project, you can use [`mapToCategoryDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/com/commercetools/sync/categories/utils/CategoryReferenceResolutionUtils.html#mapToCategoryDrafts-java.util.List-) method that maps from a `Category` to `CategoryDraft` in order to make them ready for reference resolution by the sync, for example: ````java diff --git a/docs/usage/CUSTOMER_SYNC.md b/docs/usage/CUSTOMER_SYNC.md index e104704ce3..979ce1f32e 100644 --- a/docs/usage/CUSTOMER_SYNC.md +++ b/docs/usage/CUSTOMER_SYNC.md @@ -35,7 +35,7 @@ against a [CustomerDraft](https://docs.commercetools.com/api/projects/customers# ### Prerequisites #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java @@ -69,7 +69,7 @@ Therefore, in order to resolve the actual ids of those references in the sync pr ##### Syncing from a commercetools project -When syncing from a source commercetools project, you can use [`mapToCustomerDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtils.html#mapToCustomerDrafts-java.util.List-) +When syncing from a source commercetools project, you can use [`mapToCustomerDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/com/commercetools/sync/customers/utils/CustomerReferenceResolutionUtils.html#mapToCustomerDrafts-java.util.List-) a method that maps from a `Customer` to `CustomerDraft` to make them ready for reference resolution by the sync, for example: ````java diff --git a/docs/usage/CUSTOM_OBJECT_SYNC.md b/docs/usage/CUSTOM_OBJECT_SYNC.md index 02254b2e2a..03b86f792a 100644 --- a/docs/usage/CUSTOM_OBJECT_SYNC.md +++ b/docs/usage/CUSTOM_OBJECT_SYNC.md @@ -31,7 +31,7 @@ against a [CustomObjectDraft](https://docs.commercetools.com/http-api-projects-c #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java diff --git a/docs/usage/IMPORTANT_USAGE_TIPS.md b/docs/usage/IMPORTANT_USAGE_TIPS.md index fe353bf59e..6ebc78219c 100644 --- a/docs/usage/IMPORTANT_USAGE_TIPS.md +++ b/docs/usage/IMPORTANT_USAGE_TIPS.md @@ -31,7 +31,7 @@ productSync.sync(batch1) By design, scaling the sync process should **not** be done by executing the batches themselves in parallel. However, it can be done either by: - Changing the number of [max parallel requests](https://github.com/commercetools/commercetools-sync-java/tree/master/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L116) within the `sphereClient` configuration. It defines how many requests the client can execute in parallel. - - or changing the draft [batch size](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/com/commercetools/sync/commons/BaseSyncOptionsBuilder.html#batchSize-int-). It defines how many drafts can one batch contains. + - or changing the draft [batch size](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/com/commercetools/sync/commons/BaseSyncOptionsBuilder.html#batchSize-int-). It defines how many drafts can one batch contains. The current overridable default [configuration](https://github.com/commercetools/commercetools-sync-java/tree/master/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) of the `sphereClient` is the recommended good balance for stability and performance for the sync process. diff --git a/docs/usage/INVENTORY_SYNC.md b/docs/usage/INVENTORY_SYNC.md index d037e51f03..8ec19df0d1 100644 --- a/docs/usage/INVENTORY_SYNC.md +++ b/docs/usage/INVENTORY_SYNC.md @@ -36,7 +36,7 @@ against a [InventoryEntryDraft](https://docs.commercetools.com/http-api-projects #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java @@ -68,7 +68,7 @@ Therefore, in order to resolve the actual ids of those references in the sync pr ##### Syncing from a commercetools project -When syncing from a source commercetools project, you can use [`mapToInventoryEntryDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtils.html#mapToInventoryEntryDrafts-java.util.List-) +When syncing from a source commercetools project, you can use [`mapToInventoryEntryDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/com/commercetools/sync/inventories/utils/InventoryReferenceResolutionUtils.html#mapToInventoryEntryDrafts-java.util.List-) the method that maps from an `InventoryEntry` to `InventoryEntryDraft` in order to make them ready for reference resolution by the sync, for example: ````java diff --git a/docs/usage/PRODUCT_SYNC.md b/docs/usage/PRODUCT_SYNC.md index b4b61892e3..f531d94a99 100644 --- a/docs/usage/PRODUCT_SYNC.md +++ b/docs/usage/PRODUCT_SYNC.md @@ -39,7 +39,7 @@ against a [ProductDraft](https://docs.commercetools.com/http-api-projects-produc #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java @@ -80,7 +80,7 @@ resource on the target commercetools project and the library will issue an updat ##### Syncing from a commercetools project -When syncing from a source commercetools project, you can use [`mapToProductDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/com/commercetools/sync/products/utils/ProductReferenceResolutionUtils.html#mapToProductDrafts-java.util.List-) +When syncing from a source commercetools project, you can use [`mapToProductDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/com/commercetools/sync/products/utils/ProductReferenceResolutionUtils.html#mapToProductDrafts-java.util.List-) the method that maps from a `Product` to `ProductDraft` in order to make them ready for reference resolution by the sync, for example: ````java diff --git a/docs/usage/PRODUCT_TYPE_SYNC.md b/docs/usage/PRODUCT_TYPE_SYNC.md index d9f08e3919..f9493f8965 100644 --- a/docs/usage/PRODUCT_TYPE_SYNC.md +++ b/docs/usage/PRODUCT_TYPE_SYNC.md @@ -36,7 +36,7 @@ against a [ProductTypeDraft](https://docs.commercetools.com/http-api-projects-pr ### Prerequisites #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java @@ -66,7 +66,7 @@ Therefore, in order to resolve the actual ids of those references in the sync pr ##### Syncing from a commercetools project -When syncing from a source commercetools project, you can use [`mapToProductTypeDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtils.html#mapToProductTypeDrafts-java.util.List-) +When syncing from a source commercetools project, you can use [`mapToProductTypeDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/com/commercetools/sync/producttypes/utils/ProductTypeReferenceResolutionUtils.html#mapToProductTypeDrafts-java.util.List-) method that maps from a `ProductType` to `ProductTypeDraft` to make them ready for reference resolution by the sync, for example: ````java diff --git a/docs/usage/QUICK_START.md b/docs/usage/QUICK_START.md index 2a921597ac..2ad4c63c53 100644 --- a/docs/usage/QUICK_START.md +++ b/docs/usage/QUICK_START.md @@ -37,7 +37,7 @@ com.commercetools commercetools-sync-java - 3.0.2 + 3.1.0 ```` - For Gradle users: @@ -48,7 +48,7 @@ implementation 'com.commercetools.sdk.jvm.core:commercetools-java-client:1.56.0' implementation 'com.commercetools.sdk.jvm.core:commercetools-convenience:1.56.0' // Add commercetools-sync-java dependency. -implementation 'com.commercetools:commercetools-sync-java:3.0.2' +implementation 'com.commercetools:commercetools-sync-java:3.1.0' ```` ### 2. Setup Syncing Options diff --git a/docs/usage/SHOPPING_LIST_SYNC.md b/docs/usage/SHOPPING_LIST_SYNC.md index 8df7d42680..54f36fa4a3 100644 --- a/docs/usage/SHOPPING_LIST_SYNC.md +++ b/docs/usage/SHOPPING_LIST_SYNC.md @@ -37,7 +37,7 @@ against a [ShoppingListDraft](https://docs.commercetools.com/api/projects/shoppi ### Prerequisites #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java @@ -73,7 +73,7 @@ Therefore, in order to resolve the actual ids of those references in the sync pr ##### Syncing from a commercetools project -When syncing from a source commercetools project, you can use [`mapToShoppingListDraft`](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/com/commercetools/sync/shoppinglists/utils/ShoppingListReferenceResolutionUtils.html#mapToShoppingListDrafts-java.util.List-) +When syncing from a source commercetools project, you can use [`mapToShoppingListDraft`](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/com/commercetools/sync/shoppinglists/utils/ShoppingListReferenceResolutionUtils.html#mapToShoppingListDrafts-java.util.List-) the method that maps from a `ShoppingList` to `ShoppingListDraft` to make them ready for reference resolution by the shopping list sync, for example: ````java diff --git a/docs/usage/STATE_SYNC.md b/docs/usage/STATE_SYNC.md index e0c6ab7e31..e5fc9ea176 100644 --- a/docs/usage/STATE_SYNC.md +++ b/docs/usage/STATE_SYNC.md @@ -35,7 +35,7 @@ against a [StateDraft](https://docs.commercetools.com/http-api-projects-states#s #### Prerequisites #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java @@ -66,7 +66,7 @@ reference should return its `key`. ##### Syncing from a commercetools project -When syncing from a source commercetools project, you can use this utility which this library provides: [`mapToStateDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.0.2/com/commercetools/sync/states/utils/StateReferenceResolutionUtils.html#mapToStateDrafts-java.util.List-) +When syncing from a source commercetools project, you can use this utility which this library provides: [`mapToStateDrafts`](https://commercetools.github.io/commercetools-sync-java/v/3.1.0/com/commercetools/sync/states/utils/StateReferenceResolutionUtils.html#mapToStateDrafts-java.util.List-) that replaces the references id fields with keys, in order to make them ready for reference resolution by the sync: ````java diff --git a/docs/usage/TAX_CATEGORY_SYNC.md b/docs/usage/TAX_CATEGORY_SYNC.md index ea45e99e6d..be333cf555 100644 --- a/docs/usage/TAX_CATEGORY_SYNC.md +++ b/docs/usage/TAX_CATEGORY_SYNC.md @@ -31,7 +31,7 @@ against a [TaxCategoryDraft](https://docs.commercetools.com/http-api-projects-ta ### Prerequisites #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java diff --git a/docs/usage/TYPE_SYNC.md b/docs/usage/TYPE_SYNC.md index 8c1bb109a3..76cb6413c3 100644 --- a/docs/usage/TYPE_SYNC.md +++ b/docs/usage/TYPE_SYNC.md @@ -33,7 +33,7 @@ against a [TypeDraft](https://docs.commercetools.com/http-api-projects-types.htm ### Prerequisites #### SphereClient -Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.0.2/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. +Use the [ClientConfigurationUtils](https://github.com/commercetools/commercetools-sync-java/blob/3.1.0/src/main/java/com/commercetools/sync/commons/utils/ClientConfigurationUtils.java#L45) which apply the best practices for `SphereClient` creation. If you have custom requirements for the sphere client creation, have a look into the [Important Usage Tips](IMPORTANT_USAGE_TIPS.md). ````java diff --git a/mkdocs.yml b/mkdocs.yml index 25b533f57b..cc214d60b3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -61,8 +61,8 @@ nav: - Advanced: - Sync Options: usage/SYNC_OPTIONS.md - Usage Tips: usage/IMPORTANT_USAGE_TIPS.md - - Cleanup: usage/CLEANUP_GUIDE.md - - Javadoc: https://commercetools.github.io/commercetools-sync-java/v/3.0.2/ + - Cleanup Unresolved References: usage/CLEANUP_GUIDE.md + - Javadoc: https://commercetools.github.io/commercetools-sync-java/v/3.1.0/ - Release notes: RELEASE_NOTES.md - Roadmap: https://github.com/commercetools/commercetools-sync-java/milestones - Issues: https://github.com/commercetools/commercetools-sync-java/issues